b.logrythmik

{ independency injection }

A Script-Block Templated Delegate for Inline-Scripts in Razor Partials

One of the best-practices recommendations I have struggled with is to have all your <script> tags at the bottom of the page. This is easily accomplished from a standard view; Create a section in your layout called "scripts" that renders just below where you load the libraries and use this section wherever you need scripting. No problem.

Notice I said, "standard view"? What happens if you have a partial that requires some script to run? Some would argue that the scripts should not be in the partial, but I have had a few special cases where this simply isn't possible.

I have a common "Message" partial, who's job is to render messages to the page. Most of these messages are HTML and display normally, but some messages are actually "growl" type notification messages and the partial needs to execute a function on rendering. HERE is the problem. You can't write to a section from a partial, plus partials are sometimes rendered via AJAX.

I needed a way to get the script block from my partial rendered after the scripts are loaded if part of an entire view, but I want to load the scripts inline, should I render the partial using AJAX (since the libraries will be loaded already at this point).

Sure, I could use a Head.js or some other script loading library, but some of these break the "unobtrusive" libraries that come with MVC and I didn't want to lose that functionality.

Templated Delegates to the Rescue

I created a ScriptBlock extension-method that accepts a templated delegate from anywhere in a view. The extensions method determines if the request is AJAX or not and writes the script back to the view if it is. If the partial is part of an entire view page, the script is captured in a StringBuilder, stored in HttpContext. A counterpart extension method "WriteScriptBlocks" can then be included in my layout, wherever I want the scripts to render.

Check it out.

Extention Methods:

Using the templated delegate as a parameter is great, because any model-view-binding is executed before the content is delivered to this function. Since it's a template, Intellisense and code coloring also works.  Here it is, in use:

Partial:

@model ViewMessage
 
@if (Model.Type == MessageType.Success || Model.UseNotification)
{     
    var message = Model.Message.FixForJson();
    if(Model.HasLink)
    {
        message += "<br/><a href='{0}'>{1}</a>".ToFormat(Model.LinkUrl, Model.LinkText);
    }   
    // My Script Block Extension Method, notice all the code-coloring and binding is still functional
    @this.ScriptBlock(
    @<script  type='text/javascript'>
        $(document).ready(function () {
            notify('@message''@Model.Type.ToString()''@Model.Type.ToString().ToLower()');
        });
    </script>)
 
} else {
<div class="message @Model.DisplayType clear">
 
    @if (this.Model.ShowCloseButton) { <span class="jq-close">close [x]</span>  } 
    
    <img src="/content/images/icons/@Model.Type.ToString()_Large.png" alt="@Model.Type.ToString()" />
    <h2 class="messageTitle">@Model.Message</h2>
    @if (Model.HasLink) { <p><a href="@Model.LinkUrl">@Model.LinkText</a></p> }        
    @Html.Raw(Model.ErrorList)
</div>
<div class="clear"></div>

Layout:

   ... Content

    <script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.min.js"></script>
    <script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jqueryui/1.8.13/jquery-ui.min.js"></script>
    <script type="text/javascript" src="//ajax.microsoft.com/ajax/jquery.validate/1.7/jquery.validate.min.js"></script>
    <script type="text/javascript" src="//ajax.aspnetcdn.com/ajax/jquery.templates/beta1/jquery.tmpl.min.js"></script>
    <script type="text/javascript" src="//ajax.aspnetcdn.com/ajax/mvc/3.0/jquery.validate.unobtrusive.min.js"></script>
    <script type="text/javascript" src="//ajax.aspnetcdn.com/ajax/mvc/3.0/jquery.unobtrusive-ajax.min.js"></script>
    
 
    <script type="text/javascript" src="@Links.Scripts.Site_js"></script>
 
    @RenderSection("Scripts"false)
    @this.WriteScriptBlocks()
</body>
</html>

Enjoy!

A Light-Weight MVC Route Debugging App

I have a huge MVC project that I am working with that has a pretty complex routing schema. Debugging routes gets pretty hairy, so I was delighted to find the RouteDebug assembly that Phil Haack put together. It helped me immensely, but my project takes too long to compile for quick route testing. 

I needed a quick way of creating routes and testing the route values, so I created a super-light-weight small website to use his assembly with nothing else.  You can put your route creation code right into the Global.asax and instantly get the results. I found this super useful and I hope you do too.

 

RouteDebug-Web.zip (12.19 kb)

Expression-Based URLs in MVC - Using a Specific Route with LinkBuilder

In the MVC Future Assembly there lives a super handy class called "LinkBuilder". This class takes an expression and builds a URL using your configured routes.

Creating an extension method in your own project to use this class is pretty straight forward:

public static string Action<TController>(this UrlHelper helper, Expression<Action<TController>> action)

    where TController : Controller

{

    return Microsoft.Web.Mvc.LinkBuilder.BuildUrlFromExpression(helper.RequestContext, helper.RouteCollection, action);

}

You can also use this library to create your own "ActionLink" extensions too. BUT, this isn't why I wrote this article.

I wanted to extend this method to specify an EXACT route to use, as I had a special route to format the URLs and the above method uses the first route it finds that could work - usually the default route.

After struggling for hours trying to parse the expression myself (UGH!), a simple solution presented itself:

public static string Action<TController>(this UrlHelper helper, Expression<Action<TController>> action, string routeName)

    where TController : Controller

{

    var routeCollection = helper.RouteCollection;

 

    // Check to ensure this route exists first. If not, don't filter the routes.

    if (helper.RouteCollection[routeName] != null)
         routeCollection  = new RouteCollection { helper.RouteCollection[routeName] };

 

    return Microsoft.Web.Mvc.LinkBuilder.BuildUrlFromExpression(helper.RequestContext, routeCollection, action);

}

This method filters the RouteCollection down to the exact route you are want to use and passes the filtered collection to the LinkBuilder... ahhh, routing-bliss.

Now you can use it in your views like this:

<%= Url.Action<CredentialsController>(c => c.Edit(credID), "CredentialActions") %>