Shazwazza

Shannon Deminick's blog all about .Net, Umbraco & Web development

Sharing Controller Actions with ControllerExtenders

October 4, 2011 12:17 by Shannon Deminick

Why?

If you wanted to be able to share Actions between controllers in .Net there is currently no official way to do it and that is probably because it might not be something that people have thought about doing very often before. Though once I started thinking about it I realized that this concept could be used for a variety of different purposes and that other people might potentially think of whole new ways to apply this concept. Here’s a couple use cases:

  • Separating logic out of large controllers
  • Ability to distribute Controllers in DLLs whose Actions that could then be consumed by other people’s Controllers
  • Support for a ‘Multiple inheritance’ class structure

Again I think that there is some potential here for people to run with this concept in their own ways. Its probably not a concept that everyone will want/need to use, but you should know that it definitely can be done, and here’s how…

ControllerExtender

In order to extend your Controller with actions from another Controller, you’ll need to register your extension with a new class called ControllerExtender. There’s a couple of ways to go about doing this, the nicest way is to use attributes on your Controller:

[ExtendedBy(typeof(TestExtenderController))]
public class ContentEditorController : Controller
{....}

Otherwise there’s a few overloads on the ControllerExtender class to directly do this:

//Where 'this' is the controller you are extending, generally called in the 
//constructor of your controller
ControllerExtender.RegisterExtender(this, typeof (TestExtenderController));

//Where 'this' is the controller you are extending, generally called in the 
//constructor of your controller
ControllerExtender.RegisterExtender<TestExtenderController>(this);

//This registration could be created in your global.asax 
ControllerExtender.RegisterExtender<ContentEditorController, TestExtenderController>();

One important thing to note is that the ControllerExtender uses the DependencyResolver to create an instance of the extending controller class so you’ll need to ensure that your controllers are registered properly in IoC.

Illegal Extenders

The ControllerExtender will not let you extend a controller by it’s same type of it’s same sub type. Also, nested extenders do not work (though the code could be modified to support this) therefore you cannot have Controller ‘A’ be extended by Controller ‘B’ which is extended by Controller ‘C’. In this scenario when rendering actions on Controller ‘A’, only actions on Controller ‘A’ and ‘B’ will be resolved. When rendering actions on Controller ‘B’ only actions on Controller ‘B’ and ‘C’ will be resolved.

Multiple Extenders

You can register multiple extenders for one Controller but because multiple extenders may have the same Action name/signature, only the first one that is found that is a match is used. It is therefor up to the developer to make sure they are aware of this.

Custom IActionInvoker

In order to facilitate the sharing of Actions a custom IActionInvoker of type ControllerExtenderActionInvoker needs to be registered on your controller. This is easy to do in the constructor of your controller:

public MyController()
{
    this.ActionInvoker = new ControllerExtenderActionInvoker();
}

Source Code

The source code for all of this is pretty straight forward and boils down to 3 classes:

  • The static ControllerExtender class
  • The custom ControllerExtenderActionInvoker
  • The ExtendedByAttribute

ControllerExtenderActionInvoker class

This class is responsible for finding and invoking the correct Action on a controller which takes into account any of the extenders registered for the currently executing Controller.

The most up to date code can be found in the Umbraco 5 source code HERE.

ControllerExtender class

This class is responsible for maintaining the extender registrations, it allows for creating registrations and returning registrations:

public static class ControllerExtender
{

    /// <summary>
    /// Internal ConcurrentDictionary to store all registrations
    /// </summary>
    private static readonly ConcurrentDictionary<Tuple<Type, Type>, Func<ControllerBase>> 
        Registrations
            = new ConcurrentDictionary<Tuple<Type, Type>, Func<ControllerBase>>();

    /// <summary>
    /// Registers the extender.
    /// </summary>
    /// <typeparam name="TController">The type of the controller.</typeparam>
    /// <typeparam name="TExtender">The type of the extender.</typeparam>
    public static void RegisterExtender<TController, TExtender>()
        where TController : ControllerBase
        where TExtender : ControllerBase
    {
        var t = new Tuple<Type, Type>(typeof(TController), typeof(TExtender));
        if (Registrations.ContainsKey(t))
            return;

        if (typeof(TExtender).IsAssignableFrom(typeof(TController)))
        {
            throw new InvalidOperationException
                ("Cannot extend a controller by it's same type");
        }

        Registrations.TryAdd(t, () => DependencyResolver.Current.GetService<TExtender>());
    }

    /// <summary>
    /// Registers the extender.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="controllerToExtend">The controller to extend.</param>
    public static void RegisterExtender<T>(
        ControllerBase controllerToExtend)
        where T : ControllerBase
    {
        RegisterExtender(controllerToExtend, () => DependencyResolver.Current.GetService<T>());
    }

    /// <summary>
    /// Registers the extender.
    /// </summary>
    /// <param name="controllerToExtend">The controller to extend.</param>
    /// <param name="controllerExtender">The controller extender.</param>
    public static void RegisterExtender(
        ControllerBase controllerToExtend, 
        Type controllerExtender)
    {
        var t = new Tuple<Type, Type>(controllerToExtend.GetType(), controllerExtender);
        if (Registrations.ContainsKey(t))
            return;

        if (controllerExtender.IsAssignableFrom(controllerToExtend.GetType()))
        {
            throw new InvalidOperationException
                ("Cannot extend a controller by it's same type");
        }

        Registrations.TryAdd(t, 
            () => DependencyResolver.Current.GetService(controllerExtender) as ControllerBase);
    }

    /// <summary>
    /// Registers the extender.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="controllerToExtend">The controller to extend.</param>
    /// <param name="extender">The extender.</param>
    public static void RegisterExtender<T>(
        ControllerBase controllerToExtend, 
        Expression<Func<T>> extender)
        where T : ControllerBase
    {
        var extenderType = typeof(T);
        var t = new Tuple<Type, Type>(controllerToExtend.GetType(), extenderType);
        if (Registrations.ContainsKey(t))
            return;

        if (extender.GetType().IsAssignableFrom(controllerToExtend.GetType()))
        {
            throw new InvalidOperationException
                ("Cannot extend a controller by it's same type");
        }

        Registrations.TryAdd(t, extender.Compile());
    }

    /// <summary>
    /// Returns all registrations as a readonly collection
    /// </summary>
    /// <returns></returns>
    public static IEnumerable<KeyValuePair<Tuple<Type, Type>, Func<ControllerBase>>> 
        GetRegistrations()
    {
        return Registrations;
    }

}

ExtendedByAttribute class

This Attribute is used by the ControllerExtenderActionInvoker to created Extender registrations dynamically.

[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public class ExtendedByAttribute : Attribute
{
    public Type ControllerExtenderType { get; private set; }

    public ExtendedByAttribute(Type controllerExtenderType)
    {
        if (!typeof(ControllerBase).IsAssignableFrom(controllerExtenderType))
        {
            throw new ArgumentException
                ("controllerExtenderType must be of type Controller");
        }
        ControllerExtenderType = controllerExtenderType;       
    }
}

Sharing views between specific controllers

July 21, 2011 01:29 by Shannon Deminick

There’s no native way in MVC to share views between controllers without having to put those views in the ‘Shared’ folder which can be annoying especially if you have an inherited controller class structure, or your simply wish to just have one particular controller look for views in the folder of a different controller.

With a little help from DotPeek to see what’s going on inside of the VirtualPathProviderViewEngine, I’ve created a fairly elegant solution to the above problem which consists of a custom ViewEngine and a custom controller Attribute. Lets start with the custom Attribute, its really simple and only contains one property which is the name of the controller that you wish the attributed controller to reference views.  An example:

[AlternateViewEnginePath("ContentEditor")]
public class MediaEditorController : Controller
{  ...  }

The example above is saying that we have a MediaEditor controller that should reference views in the ContentEditor controller’s view folder, pretty simple right! A cool part about how the underlying ViewEngine works is that if a view isn’t found in the ContentEditor controller’s folder, then it will check back to the current controller’s folder, so it has built in fall back support.

The custom ViewEngine is actually pretty simple as well once you know what’s going on inside of the VirtualPathProviderViewEngine. There’s two particular methods: FindView and FindPartialView which the VirtualPathProviderViewEngine figures out which controller/folder to look in. It figures this out by looking at the current ControllerContext’s RouteValues and simply does a lookup of the controller name by using this statement:

controllerContext.RouteData.GetRequiredString("controller");

So all we’ve got to do is override the FindView and FindPartialView methods, create a custom ControllerContext with the “controller” route value to be the value specified in our custom attribute, and then pass this custom ControllerContext into the underlying base FindView/FindPartial view methods:

/// <summary>
/// A ViewEngine that allows a controller's views to be shared with other 
/// controllers without having to put these shared views in the 'Shared' folder.
/// This is useful for when you have inherited controllers.
/// </summary>
public class AlternateLocationViewEngine : RazorViewEngine
{
    public override ViewEngineResult FindPartialView(
        ControllerContext controllerContext, 
        string partialViewName, 
        bool useCache)
    {
        var altContext = GetAlternateControllerContext(controllerContext);
        if (altContext != null)
        {
            //see if we can get the view with the alternate controller 
            //specified, if its found return the result, if its not found
            //then return the normal results which will try to find 
            //the view based on the 'real' controllers name.
            var result = base.FindPartialView(altContext, partialViewName,useCache);
            if (result.View != null)
            {
                return result;
            }
        }
        return base.FindPartialView(controllerContext, partialViewName,useCache);
    }

    public override ViewEngineResult FindView(
        ControllerContext controllerContext, 
        string viewName, 
        string masterName, 
        bool useCache)
    {
        var altContext = GetAlternateControllerContext(controllerContext);
        if (altContext!= null)
        {
            var result = base.FindView(altContext, viewName, masterName, useCache);
            if (result.View != null)
            {
                return result;
            }
        }
        return base.FindView(controllerContext, viewName, masterName, useCache);
    }

    /// <summary>
    /// Returns a new controller context with the alternate controller name in the route values
    /// if the current controller is found to contain an AlternateViewEnginePathAttribute.
    /// </summary>
    /// <param name="currentContext"></param>
    /// <returns></returns>
    private static ControllerContext GetAlternateControllerContext(
        ControllerContext currentContext)
    {
        var controller = currentContext.Controller;
        var altControllerAttribute = controller.GetType()
            .GetCustomAttributes(typeof(AlternateViewEnginePathAttribute), false)
            .OfType<AlternateViewEnginePathAttribute>()
            .ToList();
        if (altControllerAttribute.Any())
        {
            var altController = altControllerAttribute.Single().AlternateControllerName;
            //we're basically cloning the original route data here...
            var newRouteData = new RouteData
                {
                    Route = currentContext.RouteData.Route,
                    RouteHandler = currentContext.RouteData.RouteHandler
                };
            currentContext.RouteData.DataTokens
                .ForEach(x => newRouteData.DataTokens.Add(x.Key, x.Value));
            currentContext.RouteData.Values
                .ForEach(x => newRouteData.Values.Add(x.Key, x.Value));

            //now, update the new route data with the new alternate controller name
            newRouteData.Values["controller"] = altController;

            //now create a new controller context to pass to the view engine
            var newContext = new ControllerContext(
                currentContext.HttpContext, 
                newRouteData, 
                currentContext.Controller);
            return newContext;
        }

        return null;
    }
}

/// <summary>
/// An attribute for a controller that specifies that the ViewEngine 
/// should look for views for this controller using a different controllers name.
/// This is useful if you want to share views between specific controllers 
/// but don't want to have to put all of the views into the Shared folder.
/// </summary>
public class AlternateViewEnginePathAttribute : Attribute
{
    public string AlternateControllerName { get; set; }

    public AlternateViewEnginePathAttribute(string altControllerName)
    {
        AlternateControllerName = altControllerName;
    }
}
Lastly, you’ll need to just register this additional ViewEngine in your global.asax, or IoC, or however you are doing that sort of thing in your application.

Umbraco Jupiter Plugins - Part 2 - Routing

May 11, 2011 08:26 by Shannon Deminick

This is the second blog post in a series of posts relating to building plugins for Umbraco v5 (Jupiter).

Related Posts:

  1. Umbraco Jupiter Plugins – Part 1

Disclaimer

This post is about developing for Umbraco v5 (Jupiter) which at the time of this post is still under development. The technical details described below may change by the time Umbraco Jupiter is released. If you have feedback on the technical implementation details, please comment below.

Routing & URLs

As mentioned in the previous post Umbraco Jupiter will consist of many types of plugins, and of those plugins many of them exist as MVC Controllers.  Each controller has an Action which a URL is routed to, this means that each Controller plugin in Jupiter requires it’s own unique URLs. The good news is that you as a package developer need not worry about managing these URLs and routes, Jupiter will conveniently do all of this for you.

Packages & Areas

My previous post mentioned that a ‘Package’ in Jupiter is a collection of ‘Plugins’ and as it turns out, Plugins can’t really function unless they are part of a Package. In it’s simplest form, a Package in v5 is a folder which contains Plugins that exists in the ~/Plugins/Packages sub folder. The folder name of the package becomes very important because it is used in setting up the routes to  create the unique URLs which map to the MVC Controller plugins. Package developers should be aware that they should name their folder to something that is reasonably unique so it doesn’t overlap with other Package folder names. During package installation, Jupiter will check for uniqueness in Package folder names to ensure there is no overlap (there will be an entirely separate post on how to create deployment packages and how to install them).

Here’s a quick example: If I have a Package that contains a Tree plugin called TestTree (which is in fact an MVC Controller) and I’ve called my Package folder ‘Shazwazza’, which exists at ~/Plugins/Packages/Shazwazza then the URL to return the JSON for the tree is: http://localhost/Umbraco/Shazwazza/Trees/TestTree/Index 

Similarly, if I have a Editor plugin called TestEditor with an action called Edit, then a URL to render the Edit Action is:

http://localhost/Umbraco/Shazwazza/Editors/TestEditor/Edit

If you’ve worked with MVC, you’ll probably know what an MVC Area is. The way that Jupiter creates the routes for Packages/Plugins is by creating an MVC Area for each Package. This is how it deals with the probability that different Package developers may create MVC Controllers with the same name. MVC routes are generally based just on a Controller name and an Action name which wouldn’t really work for us because there’s bound to be overlap amongst Package developers, so by creating an Area for each Package the route becomes unique to a combination of Controller name, Action name and Area name.  MVC also determines where to look for Views based on Area name which solves another issue of multiple Packages installed with the same View names.

Whats next?

In the coming blog posts I’ll talk about

  • how plugins are installed and loaded
  • how and where the Views are stored that the plugins reference
  • how to create all of the different types of plugins

Code Garden 2011

I’ll be doing a talk on Plugins for Umbraco Jupiter at Code Garden 2011 this year which will go in to a lot more detail than these blog posts. If you are attending Code Garden already, then hopefully this series will give you a head start on Umbraco v5. If you haven’t got your tickets to Code Garden yet, what are you waiting for?! We have so much information to share with you :)

Umbraco Jupiter Plugins - Part 1

April 29, 2011 09:55 by Shannon Deminick

This is the first blog post in a series of posts relating to building plugins for Umbraco v5 (Jupiter)

Disclaimer

This post is about developing for Umbraco v5 (Jupiter) which at the time of this post is still under development. The technical details described below may change by the time Umbraco Jupiter is released. If you have feedback on the technical implementation details, please comment below.

What is a plugin

Since Umbraco Jupiter has been built from the ground up, we first need to define some v5 terminology:

  • Plugin = A single functional component that extends Umbraco such as a Tree, an Editor, a Menu Item, etc…
  • Package = A group of Plugins installed into Umbraco via the ~/Plugins/Packages/[Package Name] folder

The Umbraco v5 back-office has been architected to run entirely on Plugins, in fact the core trees, editors, etc… that are shipped with Umbraco are Plugins in v5.

Types of plugins

The list of Plugin types will most likely increase from the time of this blog post to when Jupiter is launched but as it stands now here are the types of Plugins supported in v5:

  • Property Editors
    • This term is new to v5. In v4.x there was no differentiation between a Data Type and it’s underlying ‘Data Type’ editor. In v5 we’ve made a clear distinction between a ‘Data Type’ (as seen in the Data Type tree and used as properties for Document Types) and the underlying editor and pre-value editor that it exposes.  An example of a Property Editor would be uComponents’ Multi-Node Tree Picker. An example of a Data Type would be when an Umbraco administrator creates a new Data Type node in the Data Type tree and assigns the Multi-Node Tree Picker as it’s Property Editor.
    • So uComponents Team, this is where you need to focus your efforts for version 5!
  • Menu Items
    • Context menu items such as Create, Publish, Audit Trail, etc… are all plugins.
    • See this post in regards to creating menu items in v5, though there have been some new features added since that article was created which I’ll detail in coming posts in this series.
  • Editors
    • Editor plugins are all of the interactions performed in the right hand editor panel and in modal dialogs in the back-office.
    • For example, the Content Editor core plugin in v5 manages the rendering for all views such as editing content, sorting, copying, and moving nodes, and nearly all of the other views that the context menu actions render.
    • Editors are comprised mostly of MVC Controllers, Views, JavaScript & CSS.
  • Trees
    • All trees are plugins, for example, the Content tree, Media tree, Document Type tree are all different plugins.
    • Trees are actually MVC controllers.
    • Tree nodes use Menu Items to render Editors
  •  Presentation Addins (Yet to be officially named)
    • Another type of plugin are Controllers that are installed via packages that interact with the presentation layer, an example of this might be the Controller Action that accepts the post values from a Contour form.

Whats next?

In the coming blog posts I’ll talk about

  • how plugins are installed and loaded
  • how the Umbraco routing system works with all of these controllers
  • how and where the Views are stored that the plugins reference
  • how to create all of the different types of plugins

Code Garden 2011

I’ll be doing a talk on Plugins for Umbraco Jupiter at Code Garden 2011 this year which will go in to a lot more detail than these blog posts. If you are attending Code Garden already, then hopefully this series will give you a head start on Umbraco v5. If you haven’t got your tickets to Code Garden yet, what are you waiting for?! We have so much information to share with you :)

HtmlHelper Table methods

April 22, 2011 09:29 by Shannon Deminick

I was hoping that these were built already but couldn’t see any descent results with a quick Google search so figured I’d just write my own… these are now part of the Umbraco v5 codebase. Here’s 2 HtmlHelper methods that allow you to very quickly create an Html table output based on an IEnumerable data structure. Luckily for us, Phil Haack showed us what Templated Razor Delegates are which makes stuff like this very elegant.

There are 2 types of tables: a table that expands vertically (you set a maximum number of columns), or a table that expands horizontally (you set a maximum amount of rows). So I’ve created 2 HtmlHelper extensions for this:

Html.TableHorizontal Html.TableVertical

Say you have a partial view with a declaration of something like:

@model IEnumerable<MyDataObject>

To render a table is just one call and you can customize exactly how each cell is rendered and styled using Razor delegates, in the following example the MyDataObject class contains 3 properties: Name, Enabled and Icon and each cell is going to render a check box with a CSS image as it’s label and there will be a maximum of 8 columns:

@Html.TableVertical(Model, 8, @<text> @Html.CheckBox(@item.Name, @item.Enabled) <label for="@Html.Id(@item.Name)" class="@item.Icon"> <span>@item.Name</span> </label> </text>)

Nice! that’s it, far too easy. One thing to note is the @<text> </text> syntax as this is special Razor parser syntax that declares a Razor delegate without having to render additional html structures. Generally Razor delegates will require some Html tags such as this: @<div> </div> but if you don’t require surrounding Html structures, you can use the special @<text> </text> tags.

Lastly here’s all the HtmlHelper code… I’ve created these extensions as HtmlHelper extensions only for consistency but as you’ll be able to see, the HtmlHelper is never actually used and these extensions could simply be extensions of IEnumerable<T>.

/// <summary> /// Creates an Html table based on the collection /// which has a maximum numer of rows (expands horizontally) /// </summary> public static HelperResult TableHorizontal<T>(this HtmlHelper html, IEnumerable<T> collection, int maxRows, Func<T, HelperResult> template) where T : class { return new HelperResult(writer => { var items = collection.ToArray(); var itemCount = items.Count(); var maxCols = Convert.ToInt32(Math.Ceiling(items.Count() / Convert.ToDecimal(maxRows))); //construct a grid first var grid = new T[maxCols, maxRows]; var current = 0; for (var x = 0; x < maxCols; x++) for (var y = 0; y < maxRows; y++) if (current < itemCount) grid[x, y] = items[current++]; WriteTable(grid, writer, maxRows, maxCols, template); }); } /// <summary> /// Creates an Html table based on the collection /// which has a maximum number of cols (expands vertically) /// </summary> public static HelperResult TableVertical<T>(this HtmlHelper html, IEnumerable<T> collection, int maxCols, Func<T, HelperResult> template) where T : class { return new HelperResult(writer => { var items = collection.ToArray(); var itemCount = items.Count(); var maxRows = Convert.ToInt32(Math.Ceiling(items.Count() / Convert.ToDecimal(maxCols))); //construct a grid first var grid = new T[maxCols, maxRows]; var current = 0; for (var y = 0; y < maxRows; y++) for (var x = 0; x < maxCols; x++) if (current < itemCount) grid[x, y] = items[current++]; WriteTable(grid, writer, maxRows, maxCols, template); }); } /// <summary> /// Writes the table markup to the writer based on the item /// input and the pre-determined grid of items /// </summary> private static void WriteTable<T>(T[,] grid, TextWriter writer, int maxRows, int maxCols, Func<T, HelperResult> template) where T : class { //create a table based on the grid writer.Write("<table>"); for (var y = 0; y < maxRows; y++) { writer.Write("<tr>"); for (var x = 0; x < maxCols; x++) { writer.Write("<td>"); var item = grid[x, y]; if (item != null) { //if there's an item at that grid location, call its template template(item).WriteTo(writer); } writer.Write("</td>"); } writer.Write("</tr>"); } writer.Write("</table>"); }

Using metadata with MEF in Medium Trust

March 3, 2011 18:20 by Shannon Deminick

One of the awesome parts of MEF is that you can attach metadata to your objects. This allows you to use the new Lazy<T, TMetadata> object so you can inspect the meta data about an object without instantiating it. In order to attach metadata to an object, you need (should have) some entity that contains properties to support the meta data such as an interface (good tutorial site here ). For example, if you have a class of type MyObjectType and an interface to store metadata about the class of type IMyMetadata, you can then resolve/import Lazy<MyObjectType, IMyMetadata> which will expose a Metadata property of type IMyMetadata with the information filled in. You might already be able to tell that a bit of magic is happening behind the scenes since there is no concrete class for IMyMetadata but MEF has magically found a way to instantiate a class of type IMyMetadata and fill in the values. This is the problem area if you are running in Medium Trust, you will get a SecurityException stemming form the System.ComponentModel.Composition.MetadataViewGenerator. This same issue will occur when using Autofac to register metadata with the .WithMetadata extension method.

Solution

To work around this problem in Medium Trust, you’ll need to create a concrete class to store your metadata in instead of just referencing an interface. You’ll also need to ensure that the constructor of this class takes in one parameter of type IDictionary<string, object> and using this object you’ll need to manually do the heavy lifting of assigning the values to the properties of this class based on the values in the dictionary. Better yet, you can just make a base class that all your metadata classes can inherit from. In Umbraco v5, we’ve called this abstract class MetadataComposition just to keep inline with the naming conventions of the MEF namespace:

public abstract class MetadataComposition { /// <summary> /// constructor, sets all properties of this object based /// on the dictionary values /// </summary> /// <param name="obj"></param> protected MetadataComposition(IDictionary<string, object> obj) { var t = GetType(); var props = t.GetProperties(); foreach (var a in obj) { var p = props.Where(x => x.Name == a.Key).SingleOrDefault(); if (p != null) { p.SetValue(this, a.Value, null); } } } }

Hope this helps someone!

Developing a plugin framework in ASP.NET MVC with medium trust

January 7, 2011 09:06 by Shannon Deminick

I’ve recently spent quite a lot of time researching and prototyping different ways to create a plugin engine in ASP.NET MVC3 and primarily finding a nice way to load plugins (DLLs) in from outside of the ‘bin’ folder. Although this post focuses on MVC3, I am sure that the same principles will apply for other MVC versions.

The Issues

Loading DLLs from outside of the ‘bin’ folder isn’t really anything new or cutting edge, however when working with MVC this becomes more difficult. This is primarily due to how MVC loads/finds types that it needs to process including controllers, view models (more precisely the generic argument passed to a ViewPage or used with the @model declaration in Razor), model binders, etc… MVC is very tied to the BuildManager which is the mechanism for compiling views, and locating other services such as controllers. By default the BuildManager is only familiar with assembies in the ‘bin’ folder and in the GAC, so if you start putting DLLs in folders outside of the ‘bin’ then it won’t be able to locate the MVC services and objects that you might want it to be referencing.

Another issue that needs to be dealt with is DLL file locking. When a plugin DLL is loaded and is in use the CLR will lock the file. This becomes an an issue if developers want to update the plugin DLL while the website is running since they won’t be able to unless they bump the web.config or take the site down. This holds true for MEF and how it loads DLLs as well.

.Net 4 to the rescue… almost

One of the new features in .Net 4 is the ability to execute code before the app initializes which compliments another new feature of the BuildManager that lets you add assembly references to it at runtime (which must be done on application pre-init). Here’s a nice little reference to these new features from Phil Haack: http://haacked.com/archive/2010/05/16/three-hidden-extensibility-gems-in-asp-net-4.aspx.  This is essential to making a plugin framework work with MVC so that the BuildManager knows where to reference your plugin DLLs outside of the ‘bin’. However, this isn’t the end of the story.

Strongly typed Views with model Types located in plugin DLLs

Unfortunately if you have a view that is strongly typed to a model that exists outside of the ‘bin’, then you’ll find out very quickly that it doesn’t work and it won’t actually tell you why. This is because the RazorViewEngine  uses the BuildManager to compile the view into a dynamic assembly but then uses Activator.CreateInstance to instantiate the newly compiled object. This is where the problem lies, the current AppDomain doesn’t know how to resolve the model Type for the strongly typed view since it doesn’t exist in the ‘bin’ or GAC.  An even worse part about this scenario is that you don’t get any error message telling you why this isn’t working, or where the problem is. Instead you get the nice MVC view not found error: “…or its master was not found or no view engine supports the searched locations. The following locations were searched: ….” telling you that it has searched for views in all of the ViewEngine locations and couldn’t find it… which is actually not the error at all.  Deep in the MVC3 source, it tries to instantiate the view object from the dynamic assembly and it fails so it just keeps looking for that view in the rest of the ViewEngine paths.

NOTE: Even though in MVC3 there’s a new IViewPageActivator which should be responsible for instantiating the views that have been compiled with the BuildManager, implementing a custom IViewPageActivator to handle this still does not work because somewhere in the MVC3 codebase fails before the call to the IViewPageActivator which has to do with resolving an Assembly that is not in the ‘bin’.

Full trust

When working in Full Trust we have a few options for dealing with the above scenario:

  • Use the AppDomain’s ResolveAssembly event
    • By subscribing to this event, you are able to instruct the AppDomain where to look when it can’t find a reference to a Type.
    • This is easily done by checking if your plugin assemblies match the assembly being searched for, and then returning the Assembly object if found:
static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args) { var pluginsFolder = new DirectoryInfo(HostingEnvironment.MapPath("~/Plugins")); return (from f in pluginsFolder.GetFiles("*.dll", SearchOption.AllDirectories) let assemblyName = AssemblyName.GetAssemblyName(f.FullName) where assemblyName.FullName == args.Name || assemblyName.FullName.Split(',')[0] == args.Name select Assembly.LoadFile(f.FullName)).FirstOrDefault(); }
  • Shadow copy your plugin DLLs into the AppDomain’s DynamicDirectory.
    • This is the directory that the BuildManager compiles it’s dynamic assemblies into and is also a directory that the AppDomain looks to when resolving Type’s from Assemblies.
    • You can shadow copy your plugin DLLs to this folder on app pre-init and everything ‘should just work’
  • Replace the RazorViewEngine with a custom razor view engine that compiles views manually but makes references to the appropriate plugin DLLs
    • I actually had this working in an Umbraco v5 prototype but it is hugely overkill and unnecessary plus you actually would have to replace the RazorViewEngine which is pretty absurd.

The burden of Medium Trust

In the MVC world there’s only a couple hurdles to jump when loading in plugins from outside of the ‘bin’ folder in Full Trust. In Medium Trust however, things get interesting. Unfortunately in Medium Trust it is not possible to handle the AssemblyResolve event and it’s also not possible to access the DynamicDirectory of the AppDomain so the above two solutions get thrown out the window. Further to this it seems as though you can’t use CodeDom in Medium Trust to custom compile views.

Previous attempts

For a while I began to think that this wasn’t possible and I thought I tried everything:

  • Shadow copying DLLs from the plugins folder into the ‘bin’ folder on application pre-init
    • This fails because even during app pre-init, the application pool will still recycle. Well, it doesn’t actually ‘fail’ unless you keep re-copying the DLL into the bin. If you check if it already exists and don’t copy into the bin than this solution will work for you but it’s hardly a ‘solution’ since you might as well just put all your DLLs into the ‘bin’ in the first place.
  • Trying to use sub folders of the ‘bin’ folder to load plugins.
    • Turns out that ASP.Net doesn’t by default load in DLLs that exist in sub folders of the bin, though from research it looks like standard .Net apps actually do.
    • Another interesting point was that if you try to copy a DLL into a sub folder of the bin during application pre-init you get a funky error:  “Storage scopes cannot be created when _AppStart is executing”. It seems that ASP.Net is monitoring all changes in the bin folder regardless of whether or not they are in sub folders but still doesn’t load or reference those assemblies.

An easy solution

So, the easy solution is to just set a ‘privatePath’ on the ‘probing’ element in your web.config to tell the AppDomain to also look for Assemblies/Types in the specified folders. I did try this before when trying to load plugins from sub folders in the bin and couldn’t get it to work. I’m not sure if I was ‘doing it wrong’ but it definitely wasn’t working then, either that or attempting to set this in sub folders of the bin just doesn’t work.

<runtime> <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> <probing privatePath="Plugins/temp" />

DLL file locking

Since plugin DLLs get locked by the CLR when they are loaded, we need to work around this. The solution is to shadow copy the DLLs to another folder on application pre-init. As mentioned previously, this is one of the ways to get plugins loaded in Full Trust and in my opinion is the nicest way to do it since it kills 2 birds with one stone. In Medium Trust however, we’ll have to jump through some hoops and shadow copy the DLLs to a temp folder that exists within the web application. IMPORTANT: When you’re copying DLLs you might be tempted to modify the name of the DLL by adding a version number or similar, but this will NOT work and you’ll get a “The located assembly's manifest definition … does not match the assembly reference.” exception.

Solution

UPDATE: The latest version of this code can be found in the Umbraco v5 source code. The following code does work but there’s been a lot of enhancements to it in the Umbraco core. Here’s the latest changeset as of 28/10/2011 Umbraco v5 PluginManager.cs

Working in Full Trust, the simplest solution is to shadow copy your plugin DLLs into your AppDomain DynamicDirectory. Working in Medium Trust you’ll need to do the following:

  • On application pre-init:
    • Shadow copy all of your plugin DLLs to a temporary folder in your web application (not in the ‘bin’)
    • Add all of the copied DLLs to be referenced by the BuildManager
  • Add all folder paths to the privatePath attribute of the probing element in your web.config to point to where you will be copying your DLLs
    • If you have more than one, you need to semi-colon separate them

Thanks to Glenn Block @ Microsoft who gave me a few suggestions regarding DLL file locking with MEF, Assembly load contexts and probing paths! You put me back on track after I had pretty much given up.

Here’s the code to do the shadow copying and providing the Assemblies to the BuildManager on application pre-init (make sure you set the privatePath on the probing element in your web.config first!!)

using System.Linq; using System.Web; using System.IO; using System.Web.Hosting; using System.Web.Compilation; using System.Reflection; [assembly: PreApplicationStartMethod(typeof(PluginFramework.Plugins.PreApplicationInit), "Initialize")] namespace PluginFramework.Plugins { public class PreApplicationInit { static PreApplicationInit() { PluginFolder = new DirectoryInfo(HostingEnvironment.MapPath("~/plugins")); ShadowCopyFolder = new DirectoryInfo(HostingEnvironment.MapPath("~/plugins/temp")); } /// <summary> /// The source plugin folder from which to shadow copy from /// </summary> /// <remarks> /// This folder can contain sub folderst to organize plugin types /// </remarks> private static readonly DirectoryInfo PluginFolder; /// <summary> /// The folder to shadow copy the plugin DLLs to use for running the app /// </summary> private static readonly DirectoryInfo ShadowCopyFolder; public static void Initialize() { Directory.CreateDirectory(ShadowCopyFolder.FullName); //clear out plugins) foreach (var f in ShadowCopyFolder.GetFiles("*.dll", SearchOption.AllDirectories)) { f.Delete(); } //shadow copy files foreach (var plug in PluginFolder.GetFiles("*.dll", SearchOption.AllDirectories)) { var di = Directory.CreateDirectory(Path.Combine(ShadowCopyFolder.FullName, plug.Directory.Name)); // NOTE: You cannot rename the plugin DLL to a different name, it will fail because the assembly name is part if it's manifest // (a reference to how assemblies are loaded: http://msdn.microsoft.com/en-us/library/yx7xezcf ) File.Copy(plug.FullName, Path.Combine(di.FullName, plug.Name), true); } // Now, we need to tell the BuildManager that our plugin DLLs exists and to reference them. // There are different Assembly Load Contexts that we need to take into account which // are defined in this article here: // http://blogs.msdn.com/b/suzcook/archive/2003/05/29/57143.aspx // * This will put the plugin assemblies in the 'Load' context // This works but requires a 'probing' folder be defined in the web.config foreach (var a in ShadowCopyFolder .GetFiles("*.dll", SearchOption.AllDirectories) .Select(x => AssemblyName.GetAssemblyName(x.FullName)) .Select(x => Assembly.Load(x.FullName))) { BuildManager.AddReferencedAssembly(a); } // * This will put the plugin assemblies in the 'LoadFrom' context // This works but requires a 'probing' folder be defined in the web.config // This is the slowest and most error prone version of the Load contexts. //foreach (var a in // ShadowCopyFolder // .GetFiles("*.dll", SearchOption.AllDirectories) // .Select(plug => Assembly.LoadFrom(plug.FullName))) //{ // BuildManager.AddReferencedAssembly(a); //} // * This will put the plugin assemblies in the 'Neither' context ( i think ) // This nearly works but fails during view compilation. // This DOES work for resolving controllers but during view compilation which is done with the RazorViewEngine, // the CodeDom building doesn't reference the plugin assemblies directly. //foreach (var a in // ShadowCopyFolder // .GetFiles("*.dll", SearchOption.AllDirectories) // .Select(plug => Assembly.Load(File.ReadAllBytes(plug.FullName)))) //{ // BuildManager.AddReferencedAssembly(a); //} } } }

MVC Attributes - ReadOnly vs Editable vs Bind & the DefaultModelBinder

December 6, 2010 21:13 by Shannon Deminick

I’ve been learning quite a lot about the DefaultModelBInder and it’s internal behaviour because of the complexities involved with some of the model binding in Umbraco v5 and wanted to point out the behaviour of the following attributes when it comes to how the DefaultModelBinder works:

Each of these attributes ‘seems’ like they serve the same purpose by making properties of your model read only, not-bindable, not-editable or vice versa. In my mind they all seem like they are the same but that’s just my opinion, as there could be some other hidden secret in the .Net framework which differentiates between these explicitly. The DefaultModelBinder definitely doesn’t treat each of these the same, in fact the DefaultModelBinder doesn’t check for the Editable attribute on properties. Before the DefaultModelBinder binds all of your model’s properties (If you have a complex model), it creates a new ModelBindingContext which contains a new PropertyFilter to exclude any properties that have been defined as ReadOnly or not bindable.

There’s a few internal/private methods of the DefaultModelBinder that do these lookups:

This one creates the new model binding context and checks if any BindAttributes have been declared for the model, if so, then it creates a new property filter which combines the BindAttribute filter and the standard model binder filter:

internal ModelBindingContext CreateComplexElementalModelBindingContext(ControllerContext controllerContext, ModelBindingContext bindingContext, object model) { BindAttribute bindAttr = (BindAttribute)GetTypeDescriptor(controllerContext, bindingContext).GetAttributes()[typeof(BindAttribute)]; Predicate<string> newPropertyFilter = (bindAttr != null) ? propertyName => bindAttr.IsPropertyAllowed(propertyName) && bindingContext.PropertyFilter(propertyName) : bindingContext.PropertyFilter; ModelBindingContext newBindingContext = new ModelBindingContext() { ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, bindingContext.ModelType), ModelName = bindingContext.ModelName, ModelState = bindingContext.ModelState, PropertyFilter = newPropertyFilter, ValueProvider = bindingContext.ValueProvider }; return newBindingContext; }

This method is called to do the actual binding of each property, but as you can see it only binds properties that have been filtered:

private void BindProperties(ControllerContext controllerContext, ModelBindingContext bindingContext) { IEnumerable<PropertyDescriptor> properties = GetFilteredModelProperties(controllerContext, bindingContext); foreach (PropertyDescriptor property in properties) { BindProperty(controllerContext, bindingContext, property); } }

The GetFilteredModelProperties method gets all of the unfiltered PropertyDescriptor’s of the model, and then calls ShouldUpdateProperty to perform the actual filtering (which passes in the new ModelBindingContext’s PropertyFilter which is now based on the Bind attribute if one was declared on the model):

protected IEnumerable<PropertyDescriptor> GetFilteredModelProperties(ControllerContext controllerContext, ModelBindingContext bindingContext) { PropertyDescriptorCollection properties = GetModelProperties(controllerContext, bindingContext); Predicate<string> propertyFilter = bindingContext.PropertyFilter; return from PropertyDescriptor property in properties where ShouldUpdateProperty(property, propertyFilter) select property; }

The ShouldUpdateProperty method does the following:

  1. Check if the PropertyDescriptor is read only. This is determined by whether or not the ReadOnly attribute was specified on the model property, the Editable attribute doesn’t seem to affect this value. The first check also makes a call to CanUpdateReadonlyTypedReference which checks for value types such as guid, int, string, and ensures they are excluded if IsReadOnly is true.
  2. Check if the property filter allows the property to be bound which is based on the Bind attribute (if supplied on the model)
private static bool ShouldUpdateProperty(PropertyDescriptor property, Predicate<string> propertyFilter) { if (property.IsReadOnly && !CanUpdateReadonlyTypedReference(property.PropertyType)) { return false; } // if this property is rejected by the filter, move on if (!propertyFilter(property.Name)) { return false; } // otherwise, allow return true; }

So the result of what can actually be bound in the DefaultModelBinder is:

  • Any property that doesn’t have [ReadOnly(true)] specified
  • Any property that is referenced as an Include using the Bind attribute on the model containing the property:
    • [Bind(Include = “MyProperty”)]
  • Any property that is NOT referenced as an Exclude using the Bind attribute on the model containing the property:
    • [Bind(Exclude = “NotThisProperty”)]

So it appears that even if you set the an editable attribute as [Editable(false)], this property will still be included for binding.

The DefaultModelBinder is quite a crazy beast and contains a lot of functionality under the hood. What I’ve mentioned above is based purely on experience and looking at the source code so if you have any feedback or feel that any of this is in error please comment!

MVC Controllers as plugins with MEF and Autofac

December 1, 2010 00:38 by Shannon Deminick

Disclaimer

This blog posts talks about Umbraco v5 (Jupiter), however since the state of the codebase is in it’s infancy, the details about Umbraco v5 in this article may not be accurate once Umbraco v5 is released.

Plugins

In Umbraco v5 we’re introducing the concept of plugins. Currently in Umbraco, we have the notion of plugins such as Trees, Actions (used on context menus), Data types, etc… Each of these objects is found using a class called TypeFinder to search for specific types in all assemblies that are loaded in the ‘bin’ folder. Though this works, it is definitely not the most efficient approach to plugins. For Umbraco v5, we’re now using MEF as the plugin framework which is now part of .Net framework 4.  There will be many coming blog posts about the different types of plugins, how they work and how to create them using MEF, but this blog post is about overcoming a small hurdle we encountered when trying to integrate MEF, Autofac and MVC controllers as 3rd party plugins….

MVC Controllers as plugins

For Umbraco v5, we’re using Autofac as the IoC container which works really well for injecting dependencies into MVC controllers (amongst a ton of other things). Autofac also plays really nicely with MEF through it’s MEF integration library and requires all of 2 lines of code to get the MEF plugins into our IoC container:

var catalog = new DirectoryCatalog( m_HttpServer.MapPath("~/Plugins/Trees")); builder.RegisterComposablePartCatalog(catalog);

The above code tells Autofac to register all exportable MEF components that are found in all assemblies in the ~/Plugins/Trees folder. In our case, the plugins in this folder are actually MVC Controllers which will get registered in to the IoC container and since we are currently using the AutofacControllerFactory for our MVC controller factory, it ‘should’ be able to create the requested controller and inject it’s dependencies because we’ve stuck it in the container.

Problem 1

So far this is all very easy, but as soon as we want to route a request to this controller we end up with a 404 error stating that the page/controller cannot be found.  The reason for this is because the AutofacControllerFactory inherits from the DefaultControllerFactory which searches for controllers using the BuildManager which only searches for types registered in the ‘bin’ folder. I was hoping that the AutofacControllerFactory would have also searched within it’s registrations but this turns out to not be the case. (In fact, once Autofac releases it’s MVC3 implementation, it will no longer be shipped with an AutofacControllerFactory and instead be using MVC3’s new DependencyResolver).

Solution 1

In order for this to work we have to create our own ControllerFactory  (source code at the end of this article) and provide our own implementation for the methods:

IController GetControllerInstance(RequestContext context, Type controllerType) Type GetControllerType(RequestContext requestContext, string controllerName)

The GetControllerType implementation does the following:

  • Checks if the requested controllerName has already been resolved and exists in our factory’s internal cache
  • If not, try to find the controller by name from the underlying DefaultControllerFactory
  • If not found, search the IoC container’s registrations for the type,
    • if found, store the reference Type in the factory’s internal cache

The GetControllerInstance does the following:

  • Check if the controllerType has been registered in the factory’s internal cache
    • If so, get the return the resolved IController instance from the container
  • If not, try to create the controller from the underlying DefaultControllerFactory

Problem 2

The next problem we encountered was that Autofac couldn’t inject dependent objects into the controllers that weren’t registered in the container using the Autofac MEF extensions. A quick post on to the Autofac Google Group and I had my answer (read the post if you want examples)!

Solution 2

Essentially, if you want to expose objects to MEF in Autofac, you have to explicitly register them in the container with the ‘Exported’ extension method. Example:

//register the umbraco settings builder.Register<UmbracoSettings>(x => UmbracoSettings.GetSettings()) .As<IUmbracoSettings>() //ensure it's registered for MEF too .Exported(x => x.As<IUmbracoSettings>()) //only have one instance ever .SingleInstance();

Problem 3

The last problem was that by default it seems that exported MEF components are instantiated as singletons, meaning that each time you try to resolve a MEF component of a certain type it will always be the exact same instance. MVC really doesn’t like this when it comes to controllers, in fact MVC gives you a very informative error message regarding this and explains that if you are using dependency injection to create controllers to make sure the container is configured to resolve new controller objects, not the same instance.

Solution 3

All we need to do is add a PartCreationPolicyAttribute to the exported MEF type and set it to CreationPolicy.NonShared which tells the framework to create a new instance each time it is resolved. Example:

[Tree(typeof(ContentTreeController))] [PartCreationPolicy(CreationPolicy.NonShared)] public class ContentTreeController : DemoDataTreeController

UmbracoControllerFactory source

Here’s the source code for the custom controller factory, it currently inherits from AutofacControllerFactory but once we upgrade to use the new MVC 3 Autofac implementation, this will simply inherit from DefaultControllerFactory.

using System; using System.Web; using System.Web.Mvc; using System.Linq; using System.Web.Routing; using Autofac; using Autofac.Core; using Autofac.Integration.Web; using Autofac.Integration.Web.Mvc; using System.Collections.Generic; using Autofac.Features.Metadata; using UmbracoProjects.CMS.Web.Trees.Controllers; using System.Text.RegularExpressions; namespace UmbracoProjects.CMS.Web.Mvc.Controllers { public class UmbracoControllerFactory : AutofacControllerFactory { protected IContainer m_Container; private static readonly object m_Locker = new object(); /// <summary> /// Used to cache found controllers in the IoC container /// </summary> private static readonly Dictionary<string, ServiceTypeCache> m_IoCControllerCache = new Dictionary<string, ServiceTypeCache>(); private class ServiceTypeCache { public Service Service { get; set; } public Type ComponentType { get; set; } } /// <summary> /// Initializes a new instance of the <see cref="AutofacControllerFactory"/> class. /// </summary> /// <param name="containerProvider">The container provider.</param> public UmbracoControllerFactory(IContainerProvider containerProvider) : base(containerProvider) { m_Container = containerProvider.ApplicationContainer; } /// <summary> /// Creates the controller based on controller type /// </summary> /// <param name="context">The context.</param> /// <param name="controllerType">Type of the controller.</param> /// <returns>The controller.</returns> /// <remarks> /// This first checks our IoC service internal cache to see if it exists, if it does, we'll try to resolve the controller /// from IoC, otherwise we'll try to resolve the controller from the underlying factory /// </remarks> protected override IController GetControllerInstance(RequestContext context, Type controllerType) { if (context == null) throw new ArgumentNullException("context"); //first, check if this service by this type is in our cache if (m_IoCControllerCache.Where(x => x.Value.ComponentType.Equals(controllerType)).Any()) { var controllerService = m_IoCControllerCache.Where(x => x.Value.ComponentType.Equals(controllerType)).SingleOrDefault().Value.Service; object controller; if (m_Container.TryResolveService(controllerService, out controller)) { //if the controller is created by MEF, then resolve if (controller is System.ComponentModel.Composition.Primitives.Export) { return (IController)((System.ComponentModel.Composition.Primitives.Export)controller).Value; } else if (controller is IController) { return (IController)controller; } } throw new HttpException(404, string.Format("Controller type " + controllerType + " could not be resolved", controllerService, controllerType.FullName, context.HttpContext.Request.Path)); } //otherwise, try to create from the underlying factory return base.GetControllerInstance(context, controllerType); } /// <summary> /// Finds a controller type based on it's name. /// </summary> /// <param name="requestContext"></param> /// <param name="controllerName"></param> /// <returns></returns> /// <remarks> /// This searches using the DefaultControllerFactory's implementation and /// also searches the IoC container for registered types since types that are registered in the container /// may exist outside the bin folder /// </remarks> protected override Type GetControllerType(RequestContext requestContext, string controllerName) { var controllerKeyName = controllerName.ToUpper(); //first, check if this is already in our internal cache if (m_IoCControllerCache.ContainsKey(controllerKeyName)) { return m_IoCControllerCache[controllerKeyName].ComponentType; } //next, try to resolve it from the underlying factory var type = base.GetControllerType(requestContext, controllerName); //if the controller can't be resolved by name using the standard method, then we should check our container if (type == null) { //we need to search the registered components in IoC for a match foreach(var reg in m_Container.ComponentRegistry.Registrations) { foreach (var s in reg.Services) { //match namespaces/classe that then end with "controllerName + Controller" if (Regex.IsMatch(s.Description, @"(?:\w|\.)+\." + controllerName + "Controller$", RegexOptions.Compiled | RegexOptions.IgnoreCase)) { var controller = m_Container.ResolveService(s); var cType = controller.GetType(); //add to the internal cache if (!m_IoCControllerCache.ContainsKey(controllerKeyName)) { lock (m_Locker) { //double check if (!m_IoCControllerCache.ContainsKey(controllerKeyName)) { m_IoCControllerCache.Add(controllerKeyName, new ServiceTypeCache() { Service = s, ComponentType = cType }); } } } return cType; } } } } return type; } } }

Custom MVC ModelBinder with Complex Models/Objects/Interfaces using built in MVC Validation

September 28, 2010 22:49 by Shannon Deminick

I’ve been creating some cool stuff using ASP.Net MVC 3 lately and came across a situation where I’d like to have quite a complex model/object bound to an Action on my Controller based on a set of posted values from a form. In order to do this, a custom ModelBinder is necessary to collect the data from the posted values, turn it into my custom object, and bind that object to my Action’s parameter. The easy part is to write code to turn the posted values into my custom object and return it, the tricky part is trying to get the in-built back-end MVC validation working for my model… which is currently using DataAnnotations. I really didn’t feel like writing my own logic to validate my model against DataAnnotations and also didn’t want to write the logic to take into account that DataAnnotations might not be the current developers validation provider of choice. So after much digging through the source of MVC and using Reflector, I finally found the solution.  To better describe the concept, here’s an example of the issue:

Each IMyObject has many properties: IProperty. Each IProperty is of a type: IType and each IType has a model which is used to render out the editor in MVC (EditorFor)

public interface IMyObject { int Id { get; } string Name { get; } IEnumerable<IProperty> Properties { get; } } public interface IProperty { Guid Id { get; set; } string Alias { get; set; } string Name { get; set; } IType DataType { get; } } public interface IType { Guid Id { get; } string Name { get; } string Alias { get; } object EditorModel { get; } }

So my controller’s Action looks something like this:

public override ActionResult Edit(IMyObject obj) { if (!ModelState.IsValid) return View(obj); //Save some data and do other stuff... }

Initially my model binder looks like this which is the ‘easy’ part that converts the posted values into an IMyObject object with all of it’s values filled in:

[ModelBinderType(typeof(IMyObject))] public class EditorModelBinder : DefaultModelBinder { protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType) { if (modelType.Equals(typeof(IMyObject))) { //get the id from the posted values var id = (int)controllerContext .RouteData .Values .GetRequiredObject("id"); //get the object from my data repository by id var model = GetMyObjectFromMyDataRepository(id); foreach (var p in item.Properties) { //it's the editor model that created the editor //for each property in mvc which means that //it's this data that is being posted back var editorModel = p.DataType.EditorModel; // ... Go convert all of the posted values using the bindingContext // ValueProvider and build up the MyObject object created above // ... (A bunch of code goes here to do the conversion) // Except, now that it's created, how the heck do i run it through // MVC validation? } return model; } return base.CreateModel(controllerContext, bindingContext, modelType); } }

In order for the call in your controller to check if your ModelState.IsValid, something needs to do the validation of the model and put the validation results inside the ModelState object. This of course already exists in the MVC framework and is done with the DefaultModelBinder. The DefaultModelBinder is pretty smart and can figure out how to automagically parse and transform the posted values into the specified model and also run it through the MVC validators so long as the model is simple enough to figure out. When your model consists of interfaces, it generally can’t do much about it because it doesn’t know how to create your interface. It also has problems when the model is complex and contains sub objects of sub objects (like the IMyObject). So how do we tap in to this underlying functionality?

[ModelBinderType(typeof(IMyObject))] public class EditorModelBinder : DefaultModelBinder { protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType) { if (modelType.Equals(typeof(IMyObject))) { //get the id from the posted values var id = (int)controllerContext .RouteData .Values .GetRequiredObject("id"); //get the object from my data repository by id var model = GetMyObjectFromMyDataRepository(id); foreach (var p in item.Properties) { //it's the editor model that created the editor //for each property in mvc which means that //it's this data that is being posted back var editorModel = p.DataType.EditorModel; //get a binder for the EditorModel IModelBinder binder = this.Binders .GetBinder(model.GetType()); //create a new context for it ModelBindingContext customBindingContext = new ModelBindingContext(); //get the meta data for it customBindingContext.ModelMetadata = ModelMetadataProviders .Current .GetMetadataForType(() => model, model.GetType()); //ensure we use our correct field 'prefix' //(this is optional and depends on if you are using a custom prefix) customBindingContext.ModelName = p.Id.ToString(); //use our existing model state customBindingContext.ModelState = bindingContext.ModelState; //use our existing value provider customBindingContext.ValueProvider = bindingContext.ValueProvider; //do the binding! this will also validate and put the errors into the ModelState for us. model = (object)binder.BindModel(controllerContext, customBindingContext); } return model; } return base.CreateModel(controllerContext, bindingContext, modelType); } }

The concept above is pretty much how a Controller’s TryUpdateModel method works and how it does the underlying validation.