Question

I've got a fairly simple question for all the Razor experts out there. I'm trying to make a jQuery $.ajax() call to a URL, using Url.Content() to translate the home-relative path into an root-relative path. In so doing, Razor is getting a bit confused about where the end of my @section is located. I'd prefer to be able to specify the URL inline, but when I do that, Razor thinks that the end of the $.ajax() parameter list is the end of my @section. I'm using @section because I want to use layouts to place my javascript at the bottom of each file. Why is Razor getting so confused? I've even tried using @(Url.Content(...)), but that doesn't work either.

Also, is this the best way to approach the problem? I'm using the ASP.NET MVC 4 Preview.

This works:

@section Scripts {
    <script type="text/javascript">        
        var getMessagesUrl = '@Url.Content("~/Logging/GetMessages")';

        $(document).ready(function () {
            $.ajax({
                url: getMessagesUrl,
                dataType: 'html',
                success: function (result) {
                    $('tbody').html(result);
                }
            });
        });
    </script>
}

This doesn't:

@section Scripts {
    <script type="text/javascript">        
        $(document).ready(function () {
            $.ajax({
                url: '@Url.Content("~/Logging/GetMessages")',
                dataType: 'html',
                success: function (result) {
                    $('tbody').html(result);
                }
            }); //Razor thinks this is the curly-brace that ends the section!
        });
    </script>
}
Was it helpful?

Solution 5

Thanks for all of the help, folks.

It looks like it must have been a bug in the ASP.NET MVC 4 Preview. I just upgraded to the ASP.NET MVC 4 Beta which came out on February 15, and the problem is now completely gone.

OTHER TIPS

This is likely down to the behaviour of the parser. When it encounters the @ symbol, the parser switches to code mode and will read the implicit expression Url.Content("~/Logging.GetMessages") (well, actually it will read until the ' symbol, determine it is not a valid character in an expression and trackback to return until the end of the ). It's after this stage that the parser is getting a little confused with your view because it's likely in code mode when it encounters the final } character, and thinks it is the end of of a code span.

The reality is, you have to be quite careful when using javascript within a razor view with C#. The transitions to code are explicit, e.g. @ and after a {, but the transitions to markup are a little harder to determine.

Razor aside, my recommendation would be to stick your javascript application code in an external file, and take advantage of data-* attributes to convey meta information to your application code, e.g:

<script id="messageScript" type="text/javascript"
    src="/Scripts/messages.js" 
    data-messages="@Url.Content("~/Logging/GetMessages")"></script>

Which you can access as:

(function($) {

    $(function() {
        var $this = $("#messageScript");
        $.ajax({
            url: $this.attr("data-messages"),
            type: "html",
            success: function(result) {
                $("tbody").html(result);
            }
        });
    });

})(window.jQuery);

Update: Dave's less than symbol was not causing the problem, he only added it in his question for illustrative purposes.

On MVC4 I was able to isolate the issue. This would not compile:

<script type="text/javascript">
  $(document).ready(function () {
    $.ajax({
      url: '@Url.Content("~/Logging/GetMessages")',
      dataType: 'html',
      success: function (result) {
        $('tbody').html(result);
      }
    }); //<-- test
  });
</script>

But this would:

<script type="text/javascript">
  $(document).ready(function () {
    $.ajax({
      url: '@Url.Content("~/Logging/GetMessages")',
      dataType: 'html',
      success: function (result) {
        $('tbody').html(result);
      }
    }); //-- test
  });
</script>

Seems like it was just the < in the comment that was throwing it.

Matthew's answer pretty much explains the behaviour (although, to be honest, I can't reproduce your problem - nor see why it wouldn't work - both your examples run just fine here). For a different approach, you could dedicate an action/view to generated javascript variables (urls, settings, localized texts, whatever), i.e.:

// Could/should be OutputCached depending on the scenario
public ActionResult Globals()
{
   var model = new ClientGlobalsModel();

   // ClientGlobalsModel has a single (could be more) Dictionary<string, string>
   // (Urls) and a ToJSON() method which uses JavaScriptSerializer to serialize 
   // the object:
   model.Urls.Add("GetMessages", Url.Content("~/Logging/GetMessages"));

   // I mostly use this method for e.g. actions:
   model.Urls.Add("UploadImage", Url.Action("Upload", "Image"));

   Response.ContentType = "text/javascript";

   return View(model);
}

Globals.cshtml:

@model ClientGlobalsModel
@{
    Layout = null; // If you have a layout supplied in e.g. _ViewStart
}
var GLOBALS = @Model.ToJSON()

Yeah, this could have been a simple Content() result rather than a view - but when you have more globals (e.g. settings + urls + texts), you may want easier control over the script output and maybe serialize each dictionary individually. May also want to namespace that "GLOBALS" variable in some shared application namespace to avoid polluting the global scope.

(e.g.) Index.cshtml:

<script src="@Url.Action("Globals", "Client")"></script>
<script src="@Url.Content("~/Scripts/main.js")"></script>

... which simply includes the output from /Client/Globals. And "main.js", into which we have now moved the rest of your script:

main.js (static):

$(document).ready(function () {
   $.ajax({
      url: GLOBALS.Urls.GetMessages,
      dataType: 'html',
      success: function (result) {
         $('tbody').html(result);
      }
   });
});

You can, of course, use the same kind of approach to output a few user/context/view-specific settings directly into the view. For a few URL's or data, the data-* attribute approach may be better depending on your tastes. I'm not a fan of stuffing tons of what's basically settings into attributes on every HTML page, though.

Seems to still be a problem with final MVC4 / Visual Studio 2010, but here is my fix:

@section jQueryDocumentReady
{
  @{
  <text>
     // All the javascript and bracers you want here
  </text>
  }
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top