I would not recommend that you use the NHibernate "bag" mapping option for a many-to-many association, for example:

image

Never mind the strange domain, it is for a upcoming NHibernate presentation and I was too bored with the normal Order > OrderLines example domains.

The reason why using a bag for many-to-many is not recommended is because of the poor update behavior you get. If you were to load an entity (RebelEncounter in this case) that has a ShipsLost many-to-many association and just add another StarDestroyer to the ShipsLost collection like this:

image

This would be issued to the database:

many_to_many_using_bag

As you can see all the existing ships where deleted and then reinserted (along with the single new one you added). You can probably guess that this is far from ideal from a performance stand point. However it is easy to fix. First we have to change the mapping from bag to set:

image

NHibernate will use the HashedSet type from the Iese.Collection framework as the collection type when using the set mapping, that means that the type for the property can no longer be IList<StarDestroyer> since HashedSet does not implement that interface, however it does implement ICollection<T>. So we can change the code to something like this:

image

With this change NHibernate will now only insert the newly added entity:

image

Series Index

  • Part 1: Reusable controller actions & model inheritance
  • Part 2: Custom meta data provider (coming soon)

One of the great new features in ASP.NET MVC 2 is the template system. It is very similar to the template form helper system introduced in MvcContrib for MVC V1. For more information on the MVC template system read Brad Wilsons excellent series

This template system allows you to create convention based views and forms. The scenario that I have been working with the last couple of days is forms for reports. These forms are all very similar, that is they are all built around a couple of report filters/parameters built using dropdowns, checkboxes and datetime pickers.

My idea was that each report would only consist of a new report model and the most of the views, and controller actions could be reused.

Example:

public class ReportController : Controller
{
    public ViewResult ViewRequestForm()
    {
        return View("ViewForm", new RequestReportForm());
    }

    public ViewResult ViewOrderForm()
    {
        return View("ViewForm", new OrderReportForm());
    }

    [HttpPost]
    public ActionResult ViewReport(ReportForm form)
    {
        return View(form.GetReportParameters());
    }
}
The idea is to be able to reuse the same root view and the same action for all forms. The root view is only going look something like this:
<% Html.BeginForm("ViewReport", "Report"); %>

    <%=Html.EditorFor(x => x)  %>
    <input type="submit" value="submit" />

<% Html.EndForm(); %>

The EditorFor helper is going to start the MVC template engine. This template engine is first going to try to find a matching editor template for the report form model, since no specific template exists it is going to fallback to the default template for object, this template loops through all properties defined on the model and applies an editor for each property.

This is how the RequestReportForm and OrderReportForm model looks like:

public class RequestReportForm : ReportForm
{
    [DisplayName("Include canceled requests")]
    public bool IncludeCanceledRequests { get; set; }

    [DisplayName("Some parameter")]
    public string SomeParameter { get; set; }
    
    public override string GetReportParameters()
    {
        return "RequestFilter=" + IncludeCanceledRequests;
    }
}

public class OrderReportForm : ReportForm
{
    [DisplayName("Group by status")]
    public bool GroupByStatus { get; set; }

    [DisplayName("Some other paramater")]
    public string SomeOrderParameter { get; set; }

    public override string GetReportParameters()
    {
        return "something";
    }
}   

With the default templates built into MVC these models will be rendered like this:

RequestReportForm:

image

OrderReportForm:

image

If we would like to override these templates all we have to do is create our own partial view and name it string.ascx or bool.ascx and place it in a view folder named EditorTemplates, hopefully more on that in a later part in this blog series.

So sharing the same view for both of these report models seems to be very easy with the template system built into MVC 2 but how do we use the same controller action? We want to use the the controller action ViewReport that takes the base type ReportForm as a parameter. The standard DefaultModelBinder will not know which concrete class to bind to. The solution to this problem should be to create a custom model binder that will figure out which concrete form model to instantiate based on some field coming in the post data from the browser.

Example:
public class ReportFormModelBinder : DefaultModelBinder
{
    public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        var reportFormTypeName = bindingContext.ValueProvider.GetValue("ReportFormTypeName");

        var reportFormType = Type.GetType("MvcApplication1.Models.ReportForms." + reportFormTypeName.AttemptedValue);

        var model = bindingContext.ModelMetadata.Model;
        
        bindingContext.ModelMetadata = new ModelMetadata(ModelMetadataProviders.Current, 
                bindingContext.ModelMetadata.ContainerType,
                () => model, reportFormType, bindingContext.ModelMetadata.PropertyName);
        
        return base.BindModel(controllerContext, bindingContext);
    }
}

This custom model binder looks for a form data field "ReportFormTypeName". With this name it is possible to get the .NET Type (based on some namespace convention) and thereby create a new ModelMetadata object, now we can call the base implementation. All we need to do now is to add this ReportFormTypeName field as a hidden field to our form. Lets add it to the base class ReportForm, like this:

public abstract class ReportForm
{
    [HiddenInput(DisplayValue = false)]
    public string ReportFormTypeName
    {
        get { return this.GetType().Name; }
    }

    public abstract string GetReportParameters();

}

This is actually all we have to do to get the MVC template system to generate a hidden field named ReportFormTypeName that will contain the name of the concrete Type. Since both RequestReportForm and OrderReportForm inherit from ReportForm and since the default template for object will loop through all properties (including inherited properties) the hidden field will be included for all report forms.

So what have we accomplished? We can generate different report forms by only creating a new form model, the view rendering is shared and the controller action to generate the report is also shared. The controller action that handles the viewing of the report is in my case only responsible for concatenating all report parameters into a querystring to then pass to the report system, since this is so generic it can be handled in the same controller action for all report forms. I hope some of you understand what I am trying to show here, and I also hope to expand on the ideas in this post in later posts. Specifically how to make the report parameters more rich and how to control and override the layout using custom editor templates and custom metadata.

kick it on DotNetKicks.com