26 Sept 2011

jQuery Post a javascript array to ASP.NET MVC

I wanted to post a bunch of ID numbers to my MVC controller, which is sat waiting with the following signature:

public ActionResult UnParkLeads(IList<int> leadIds)


However, this post needed to be generated from jQuery - specifically we're sending through the data ids of entities that correspond to "checked" checkboxes in a list. So what we need was a way of iterating through the list of checked checkboxes, getting the IDs into an array, and sending that through ajax to the server, to be picked up with the correct signature action.

var leadIds = $('input.chkTileSelector:checked').map(function () {
                                                            return { name: 'leadIds', value: $(this).parents('div.Lead').data('id') };
                                                }).get();

$.ajax({
    url: '@Url.Action("UnParkLeads", "Lead")',
    type: 'POST',
    dataType: 'json',
    data: leadIds,
    success: function (result) {
        ReloadResultGrid();
    }
});    

The tricky bit was basically the construction of the javascript object literal in the map function: jQuery converts that array into the ajax querystring using its param() function, which requires name and value properties.

The result is that jQuery posts multiple values with the same name ('leadIds'), and MVC recognises that and converts them into an IList<int>.

8 Sept 2011

Using OutputCache with PartialView in MVC 3, with programmatic invalidation usingVaryByCustom too!

I've had a fun morning trying to cache partial views inside a dynamic page with MVC 3. Specifically, a partial view in a razor layout page (i.e. masterpage) that shows some dynamically generated data which I would like to hold off from regenerating for 60 seconds or so, even if the user refreshes or navigates around the site.

So I create my partial view like this - here we generate the time to show when the view is being cached

MyPartial.cshtml

Timestamp: @DateTime.Now.ToLongTimeString()

Then I create the controller action:

HomeController.cs

[OutputCache(Duration = 60)]
public ActionResult MyPartial()
{
    return PartialView();
}

Then I amend my layout page to include the call to the PartialView:

MyMaster.cshtml

Some header stuff
...
@Html.Action("MyPartial", "Home")
...
Some footer stuff

Well, that's all there is to it. The pitfalls I faced revolved around accidentally calling Html.Partial() from the layout page, which didn't work because it bypasses the controller attributes (i.e. it misses the OutputCache directive).

HOWEVER

Then I started wanting to reset the output cache when the underlying data changed, or when some user setting changed. So I knocked up this little solution using the OutputCache attribute's VaryByCustom property.

First a modification to the controller:

HomeController.cs

[OutputCache(Duration = 60, VaryByCustom = "WebApp.OutputCacheKey")]
public ActionResult MyPartial()
{
    return PartialView();
}

Every time OutputCache is called, it will check to see if that string has changed since last time. If it has changed, it invalidates the cache. "But it's a static string! How can it change?" I hear you cry. Yup, here's some voodoo magic. You can override a special method in global.asax that intercepts the call and returns ANOTHER string, wooo!

global.asax

public override string GetVaryByCustomString(HttpContext context, string arg)
{
    if (arg == "WebApp.OutputCacheKey")
    {
        return WebApp.OutputCacheKey;
    }
    else
    {
        return base.GetVaryByCustomString(context, arg);
    }
}

Now, we need to implement this WebApp thing. It's just an approach I like to have a static class knocking around my web application that allows me to access stuff from one central place, like strongly-typed accessors for session variables, or my per-request EF datacontext. But for these purposes it would just look like this:

WebApp.cs

public static class WebApp
{
    internal static string OutputCacheKey
    {
        get
        {
            if (HttpContext.Current.Session["WebApp.OutputCacheKey"] == null)
            {
                ResetOutputCache();
            }
            return HttpContext.Current.Session["WebApp.OutputCacheKey"].ToString();
        }
    }

    internal static void ResetOutputCache()
    {
        HttpContext.Current.Session["WebApp.OutputCacheKey"] = Guid.NewGuid().ToString();
    }    
}

So, what we have now is an easy way to programatically tell the OutputCache to clear itself, with a simple call from anywhere in your controller code:

WebApp.ResetOutputCache();

Simples!

2 Sept 2011

MVC 3 DropDownListFor not selecting an item (Title property of model)

Have you got a problem with MVC 3 where your model has a property that is definitely set, but when you use it in a DropDownListFor, the value doesn't get selected from your SelectList? Me too, and I've solved it.

I had a viewmodel like so:

[Required]
public string Title { get; set; }

public List<SelectListItem> TitleSelectList
{
 get
 {
  var selectList = new List<SelectListItem>();
  selectList.AddRange(new string[] { "Mr", "Mrs", "Miss", "Ms", "Dr", "Other" }.Select(item => new SelectListItem() { Text = item, Value = item }));
  selectList.Insert(0, new SelectListItem { Text = "Choose...", Value = "" });
  return selectList;
 }
}

and in my View, I was showing it like this:

@Html.DropDownListFor(model => model.Title, Model.TitleSelectList)

HOWEVER, in my View, I also had this lurking:

@{
    ViewBag.Title = "My Lovely Page";
}

Yup, with DropDownListFor at least, ViewBag properties are used in preference to model properties when MVC tries to find the selected value. It couldn't find "My Lovely Page" in the SelectList, and so nothing was selected. When I changed my ViewBag property to ViewBag.PageTitle instead, everything started working properly.
If I helped you out today, you can buy me a beer below. Cheers!