Setting ajax url for jQuery in JS file using ASP.NET MVC

39,560

Solution 1

This way fully uses MVC Routing so you can fully take advantage of the MVC framework. Inspired by stusmith's answer.

Here I have an action in ApplicationController for dynamic javascript for this URL :

 /application/js

I'm including static files here because I want just one master javascript file to download. You can choose to just return the dynamic stuff if you want:

     /// <summary>
    /// Renders out javascript
    /// </summary>
    /// <returns></returns>
    [OutputCache(CacheProfile = "Script")]
    [ActionName("js")]
    public ContentResult RenderJavascript()
    {
        StringBuilder js = new StringBuilder();

        // load all my static javascript files                    
        js.AppendLine(IO.File.ReadAllText(Request.MapPath("~/Scripts/rr/cart.js")));
        js.AppendLine(";");

        // dynamic javascript for lookup tables
        js.AppendLine(GetLookupTables());
        js.AppendLine(";");

        return new ContentResult()
        {
            Content = js.ToString(),
            ContentType = "application/x-javascript"
        };
    }

This is the helper function that creates our lookup table. Just add in a line for each RouteUrl you want to use.

    [NonAction]
    private string GetLookupTables() 
    {
        StringBuilder js = new StringBuilder();

        // list of keys that correspond to route URLS
        var urls = new[] {
            new { key = "updateCart", url = Url.RouteUrl("cart-route", new { action = "updatecart" }) },
            new { key = "removeItem", url = Url.RouteUrl("cart-route", new { action = "removeitem" }) }
        };

        // lookup table function
        js.AppendLine("// URL Lookuptable");
        js.AppendLine("$.url=function(url) {");
        js.AppendLine("var lookupTable = " + new JavaScriptSerializer().Serialize(urls.ToDictionary(x=>x.key, x=>x.url)) + ";");
        js.AppendLine("return lookupTable[url];");
        js.AppendLine("}");

        return js.ToString();
    }

This generates the following dynamic javascript, which is basically just a lookup table from an arbitrary key to the URL I need for my action method :

// URL Lookuptable
$.url=function(url) {
var lookupTable = {"updateCart":"/rrmvc/store/cart/updatecart","removeItem":"/rrmvc/store/cart/removeitem"};
return lookupTable[url];
}

In cart.js I can have a function like this. Note that the url parameter is taken from the lookup table :

 var RRStore = {};
 RRStore.updateCart = function(sku, qty) {

    $.ajax({

        type: "POST",
        url: $.url("updateCart"),
        data: "sku=" + sku + "&qty=" + qty,
        dataType: "json"

        // beforeSend: function (){},
        // success: function (){},
        // error: function (){},
        // complete: function (){},
    });

    return false;

};

I can call it from anywhere with just :

 RRStore.updateCart(1001, 5);

This seemed to be the only way I could come up with that would allow me to use routing in a clean way. Dynamically creating URLS in javascript is icky and hard to test. Testing types can add in a layer somewhere in here to easily facilitate testing.

Solution 2

The way I do it is generate the URL server-side and store in the generated HTML using an HTML5 data attribute, eg: (Razor syntax)

<li class='customClass' data-url='@Url.Action("DisplayItems", "Home", new { id = Model.Id })'>...</li>

Then you can use the jQuery attr() function to pick up the url, eg:

$(".customClass").click(function () {
    $.ajax({
        url: $(this).attr("data-url"),
        success: function (data) {
            // do stuff
        }
    });
});

If you're generating HTML client-side in response to AJAX calls, you can include the relevant URLs in your JSON payload and populate the data- attribute the same way.

Solution 3

Wrap the AJAX call in a function that takes the URL (and any other data) as a parameter(s) and returns the response. Then in your view, call the function instead of calling the AJAX call directly.

function doAjax( url, data, elem, callback )
{
    return $.ajax({
        url: url,
        data: { ajax: data },
        cache: false,
        success: function(response) { callback(response, elem, xhr); }
    });
}

...

<input type='button' value='Go get it' onclick='doAjax( <%= Url.Action ...

I'm not sure that this is any better than having the Ajax call on the page instead of in a JS file, unless you use the exact same pattern frequently.

Solution 4

Use the module pattern.


// separate js file
var PAGE_MODULE = (function () {
  var url = {},
      init = function(url) { ... },
      load = function() {
      $.ajax({
           url: url,
           ...
           });
      }

      return { init: init };
})();


// calling init goes on the page itself
PAGE_MODULE.init(" %: Url.Action(...) %>");

In general the inline onclick handler is not good javascript as you are using a global function.


onclick='doAjax( 

I recommend reading http://jqfundamentals.com/book/index.html#N20D82 to get a better handle on the module pattern.

Share:
39,560

Related videos on Youtube

Schotime
Author by

Schotime

Senior Developer from Melbourne, Australia. Interested in all things Dev with particular interest in C#, MVC, PetaPoco, Spark and FluentValidation. Outside of work I enjoy Golf, Basketball and Ice Hockey!

Updated on July 09, 2022

Comments

  • Schotime
    Schotime almost 2 years

    When doing a Ajax call to an MVC action currently I have my javascript inside the View, not inside its own JS file.

    It is then very easy to do this:

    var xhr = $.ajax({
         url: '<%= Url.Action("DisplayItem","Home") %>/' + el1.siblings("input:hidden").val(),
         data: { ajax: "Y" },
         cache: false,
         success: function(response) { displayMore(response, el1, xhr) }
    });
    

    ...then including the URL in the ajax call using Url.Action() inside the JS is pretty easy. How could i move this do its own JS file when without hard coding the URL?

  • Schotime
    Schotime over 15 years
    isn't this the inline JS we are trying to remove by hooking up the events on document.ready???
  • BobbyShaftoe
    BobbyShaftoe over 15 years
    Your solution is technically right, but I agree with you that this is not really better.
  • aruno
    aruno almost 15 years
    it depends what you're trying to abstract away into the library function. in this case you're not abstracting any business logic away, just hiding the fact that you're using jquery
  • mare
    mare over 13 years
    this is so nice and very clean indeed!
  • Dan Atkinson
    Dan Atkinson over 13 years
    -1 for using PageLoad in ASP.NET MVC. Although it could be changed to use something else...
  • stusmith
    stusmith over 13 years
    I must confess I didn't read the question properly... I thought it was an ASP.NET one. Apologies.
  • aruno
    aruno over 13 years
    just be aware that you're revealing all your paths to your action methods here. make sure you have adequate security for action methods that need security - and that you may potentially get more spidering requests to those methods (unverified)
  • sotto
    sotto about 13 years
    you might counter that with a robots.txt file?
  • Valamas
    Valamas about 12 years
    This should be the #1 answer. For those that are adding html attributes serverside.... data_url translates in the html source to data-url.
  • Sam
    Sam over 10 years
    A minor update: later versions of jQuery support accessing data attributes directly using the data() function (e.g. $(this).data('url')).
  • tonyapolis
    tonyapolis about 9 years
    Slick! Helped me out almost 4 years later
  • crush
    crush almost 9 years
    @Valamas-AUS This is a good answer, and will suffice for many situations, but I'm not sure it's the BEST answer. I think the idea of generating a lookup table is still a better answer, though I don't agree with the implementation presented above. My issue with the data-url approach is that it assumes there is a DOM element attached to every needed route. That's often not the case in my experience.
  • crush
    crush almost 9 years
    Security by omission, or security by obscurity, is never a good policy anyways. Even if you weren't sending all available routes to the browser, one should never assume that they couldn't be "discovered". I'm sure you will agree, but just wanted to add that point.
  • Sam
    Sam almost 9 years
    @crush - there’s no such thing as best :) ‘Modern’ js would probably do things differently. No doubt there’s a framework for that.
  • crush
    crush almost 9 years
    @Sam It's not the "best" because it doesn't cover all usage cases, like the one I presented - Unless you are condoning adding hidden elements to the DOM just to insert route data into the page. Has nothing to do with "modern" js. I gave you a compliment. I'm not sure why you felt the need to come in and try to defend yourself here, but I didn't mean any disrespect. I was simply pointing out a deficiency in the answer so future users would consider it in their individual usage case.
  • Sam
    Sam almost 9 years
    Sorry, I’m not trying to be defensive, it’s just a bugbear of mine when people equate ‘best’ with ‘horribly over-engineered’, or think that solutions need to be able to solve problems they don’t (yet) have. The simplest solution that solves your problem is usually the best. I appreciate your point that this won’t work well for scenarios (that I’ve clumsily described as ‘modern’) that are more complex than hanging event handlers off DOM elements as we did in the days of yore.