0 Comments

Couple of changes released today, one to Meerkat.Security and the other to Meerkat.Caching.

The first was to introduce handling for grant/deny of actions for unauthenticated users. I’d missed this use case before as to date, the project was just used in a corporate Windows environment and so everyone was authenticated by the time we saw them.

As I’m now working on a project using Azure B2C Active Directory we are now back to the point where users need to authenticate, so you want to filter the actions they can see/perform before and after they authenticate, e.g. you want to allow unauthenticated users access to Home.Index and error pages but probably not much more, and as the principle of the project was to centralize the security checking, we need a way to allow unauthenticated users without using AllowAnonymous on the controller action.

This lead to discovering a bug in Meerkat.Caching, where we tried to store a null in the cache and the underlying implementation, MemoryObjectCache, throws an exception in this case.  The fix was to trap the nulls and to ignore attempts to persist null to cache; don’t want to have to check on every call to the cache that we don’t have a null!

0 Comments

I was working on the design of a small site where we have public display of information in one format and a set of administrative screens that allow authorised users to create and edit the information.

“I know”, I thought, “this is a perfect case for MVC Areas” – and boy was I wrong!

I should have realised that the problems had started when all the routing started playing up..

  1. Route names must be unique across *all* areas
  2. If there are controller names in common in different areas, all routes need to be updated to bind the namespace of the controller you want!

Yuck! – did the MVC team think about the open-closed principle?

Then I realised I’d made a category error – mis-applying the area concept. I was making the same sort of error that people make when implementing SOA, breaking down services according to functional granularity rather than business function.

So, instead of having two Product controllers, one in the main area, the other in the admin area, I should have one Product controller, still securing the appropriate methods, and still using routes to present some of the views under ~/admin/product and some under ~/product.

Areas should be reserved for coherent chunks of business functionality e.g. Sales, Marketing etc and I’m still not particularly happy that if I have a concept in common e.g. Customer, then the routes are going to clash, but I’m glad I realised my error before I’d built even more in the wrong direction.

0 Comments
  •   Posted in: 
  • MVC

I’ve been using the MvcContrib.TestHelper class for a while now to get rather nice fluent testing of the routes in my MVC applications.

A typical test class would look like this

public class CategoryRoutesFixture : RoutesFixture
{
    private const int categoryId = 34;

    private static string BaseUrl
    {
        get { return "~/admin/Category"; }
    }

    [Test]
    public void Index()
    {
        BaseUrl.ShouldMapTo<CategoryController>(x => x.Index());
    }

    [Test]
    public void Create()
    {
        (BaseUrl + "/create").ShouldMapTo<CategoryController>(x => x.Create());
    }

    [Test]
    public void Show()
    {
        (BaseUrl + "/" + categoryId).ShouldMapTo<CategoryController>(x => x.Show(categoryId));
    }

    [Test]
    public void Edit()
    {
        (BaseUrl + string.Format("/{0}/edit", categoryId)).ShouldMapTo<CategoryController>(x => x.Edit(categoryId));
    }
}

The base class just takes care of initialising the routes collection from the MvcApplication.

I’ve just had a need to use the new Areas functionality in MVC 2.0, and one interesting wrinkle (there are a few!) is that that the RegisterAllAreas functionality wasn’t built with testing particularly in mind as it seems to need a running instance of ASP.NET to function correctly. After trying a few things and wading through even more blog posts I finally found an answer on StackOverflow.

The TestHelper class is not area aware, so the workaround is to configure the RoutesCollection appropriately for the class you are testing, so now for my main area I have

protected override void InitializeRoutes(RouteCollection routes)
{
    MvcApplication.RegisterRoutes(routes);
}

Whereas in my admin area I have

protected override void InitializeRoutes(RouteCollection routes)
{
    RouteTable.Routes.Clear();
    var adminReg = new AdminAreaRegistration();
    adminReg.RegisterArea(new AreaRegistrationContext(adminReg.AreaName, RouteTable.Routes));
}

Not particularly tricky once you know about it, but a pain to discover if you don't

0 Comments

Sometimes doing very basic things can be quite tricky in new technologies; case in point presenting a DropDownList (a ComboBox to those, including me, with a VB background) using the new EditorForModel syntax in ASP.NET MVC 2.

I had a little class in my MVP framework called IdLabel which as the name implied just held an Id and a Label so that I wasn’t forced to have a separate display and edit model for every trivial case, so I wanted to do the same sort of thing when writing my shiny new MVC apps. There’s a class called SelectListItem which fits the bill, but unfortunately a decision late in the MVC 2 Beta meant that by default, the object display/edit templates will not process anything other than basic types.

There is a slight hacky workaround which is to implement your own TypeConverter to tell the framework that it can be turned into a string, but this is normally then coupled by using an Attribute at compile time – and we don’t own SelectListItem. Fortunately, there’s a way around this using a little bit of reflection

public static class TypeUtility
{
    /// <summary>
    /// Registers a type converter against a type we don't own
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <typeparam name="TConverter"></typeparam>
    public static void RegisterTypeConverter()
        where TConverter : TypeConverter
    {
        var attr = new Attribute[1];

        var vConv = new TypeConverterAttribute(typeof(TConverter));
        attr[0] = vConv;
        TypeDescriptor.AddAttributes(typeof(T), attr);
    }
}

So now starting with our very simple domain model

public class Supplier
{
    public int Id { get; set; }
    public string Name { get; set; }
}

public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public Supplier Supplier { get; set; }
}

we can have an equally simple view model

public class ProductViewModel
{
    public int Id { get; set; }
    public string Name { get; set; }
    [DropDownList]
    public SelectListItem Supplier { get; set; }
}
Now the custom DropDownList attribute and associated classes are taken from Kazi's blog with the slight modification of adding a couple of constructor overloads so that the defaults work for SelectListItem.

Having done all that we can proceed to what the controller has to do, what’s shown below is a simplification of my current controller which I'll tell you about shortly.

public ProductController 
{
     ...
     public ActionResult Edit(int id)
     {
          var entity = repository.FindOne(id);
          var model = builder.Convert(entity);
          
          var types = repository.FindAll();
          ViewData["SupplierList"] = builder.Convert(types);

          return VIew(model);
     }
     ...
}

The builder abstracts all of the work mapping the domain and view models and is based on AutoMapper.

Then we come to the display template, idea here is to show the label rather than the idea – one extension I have in mind is to convert this to a URL to allow display/edit of the related data.

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %>
<script runat="server">
    string FormattedModelValue
    {
        get
        {
            // TODO: Might want to test for DropDownList attribute for other complex models.
            if (Model == null)
            {
                return null;
            }
            return Model is SelectListItem ? ((SelectListItem) Model).Text : Model.ToString();
        }        
    }
</script>
<%= Html.Encode(FormattedModelValue) %>

The next bit is the edit template. I’m using majic strings to couple the list definition of the DropDownList to avoid having to make the views strongly-typed on SelectLists; I view this as outside of the scope of the view model, though you may disagree. If there’s a need to alter this, the first parameter of the DropDownList attribute takes the ViewData key name.

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %>
<%@ Import Namespace="Hippo.Web.Mvc.Templating" %>
<script runat="server">
    DropDownListAttribute GetDropDownListAttribute()
    {
        var metaData = ViewData.ModelMetadata as FieldTemplateMetadata;

        return (metaData != null) ? metaData.Attributes.OfType<DropDownListAttribute>().SingleOrDefault() : null;
    }
    
    IEnumerable<SelectListItem> GetSelectList()
    {
        var metaData = ViewData.ModelMetadata as FieldTemplateMetadata;        
        if (metaData == null)
        {
            return null;
        }

        var attribute = metaData.Attributes.OfType<DropDownListAttribute>().SingleOrDefault();
        if (attribute == null)
        {
            return null;
        }

        var key = attribute.ViewDataKey ?? (metaData.PropertyName + "List");
        var selected = attribute.GetSelectedValue(Model);
        // This makes it work for both SelectList and any other enumerable
        var sl = new SelectList(ViewData[key] as IEnumerable, attribute.DataValueField, attribute.DataTextField, selected);

        return sl;
    }
</script>
<% var attribute = GetDropDownListAttribute();%>
<% if (attribute != null) {%>
    <%= Html.DropDownList(null, GetSelectList(), attribute.OptionLabel, attribute.HtmlAttributes) %>
<% }%>
<% else {%>
    <%= Html.DisplayForModel()%>
<% }%>

The non-obvious problem here is that due to a bug in MVC 2, the Html.DropDownList builder ignores any selected item that you pass it and also does not look at the model (the offending line appears to be line 188 in SelectExtensions). The trick I found (today) was to push the Model value into the ViewData against the PropertyName, it does pick that up and pushes it as the selected item – there won’t be anything else there as we have a strongly-typed view, apart from the SelectLists.

One other thing to note is that Kazi’s DropDownList attribute can be given a list of anything and you can specify the Value and Text properties to be pushed into the SelectList. Also, your model doesn’t have to use SelectListItem, but if you don’t be prepared to write a TypeConverter each time you want a DropDownList!

0 Comments
  •   Posted in: 
  • MVC

I'm trying to write some templates for MVC 2 and one of the most useful things I've found is being able to step into the source code and see how it is actually interpreting what you've written.

It's easy enough to acquire the source, but what you might not have done before is set up a symbol server. A symbol server is a location that the Windows and Visual Studio debugging tools can use to obtain pdb files, so that you can debug almost anything, including drivers and the operating system!

Setup Symbol Server

  1. Create a directory, preferably in a shared location if there's more than one of you as this stuff can get quite big.
  2. Change the directory to allow compression as pdb's will typically compress by > 50%.
  3. Add a new system environment variable _NT_SYMBOL_PATH and set it to SRV*<Shared Directory>*http://referencesource.microsoft.com/symbols;http://msdl.microsoft.com/download/symbols
    vssrc2

Enable Debug Symbols in Visual Studio

  1. Install the debugging tools for Windows and run Start Menu|Programs|Microsoft Windows SDK 7.1|Visual Studio Registration|Windows SDK Configuration Tool; this allows Visual Studio to use the symbols.
  2. Run Visual Studio and go to Tools -> Option -> Debugging -> General
  3. Uncheck Enable Just My Code
  4. Check Enable .NET Framework source stepping
    vssrc1
  5. Change to Symbols
  6. Update "Cache symbols from symbol servers to this directory" to point at your shared symbol directory, then hit "Load symbols from Microsoft symbol servers", this will take a while and chew up about 111Mb (~48Mb compressed)
  7. Put a break point in one of you controllers and run through to that point.
  8. Pick a stack frame from System.Web.Mvc from Call Stack and select Load Symbols if it is not disabled and then Go To Source Code, picking the location you have put MVC 2
    vssrc3

One other interesting angle on this is Symbol Source who are providing symbol/source service for a bunch of open source projects including NHibernate, Castle and MVC Contrib