nhibernateasp.net-mvc-2drop-down-menuupdatemodelmodelbinder

ASP.NET MVC 2 RC model binding with NHibernate and dropdown lists


I have problem with model binding in my ASP.NET MVC 2 RC application that uses NHibernate for data access. We are trying to build the application in a Ruby on Rails way and have a very simple architecture where the domain entities are used all the way from the database to the view.

The application has a couple of domain entities which can be illustrated by the following two classes:

public class Product {
    ...

    public Category Category { get; set; }      
}

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

In the view that renders the edit form has the following statement to display a dropdown list:

<%= Html.DropDownListFor(model => model.Category.Id, 
       new SelectList(ViewData["categories"] as IList<Category>, "Id", "Name"), 
       "-- Select Category --" ) %>

Please disregard the use of "non-typed" view data to hold the category collection.

The action method that receives the form post is similar to to the following. Note that the TransactionFilter attribute adds NHibernate transaction handling and commits the transaction if no exceptions occur and validation succeeds.

[HttpPost]
[TransactionFilter]
public ActionResult Edit(int id, FormCollection collection) {
    var product = _repository.Load(id);

    // Update the product except the Id
    UpdateModel(product, null, null, new[] {"Id"}, collection);

    if (ModelState.IsValid) {
      return RedirectToAction("Details", new {id});
    }
    return View(product);
}

My issue is that the product.Category.Id is set with the value selected in the form, e.g. Category.Id = "2". Using the default model binder results in the following type of NHibernate exception:

identifier of an instance of Name.Space.Entities.Category was altered from 4 to 2

That makes a lot of sense since the product already has a category assigned and only the primary key of that existing category is being changed. Another category instance should have been assigned instead.

I guess a custom ModelBinder can be created to handle this issue but is there a simpler way to make this work? Can (and should) the domain entities be modified to handle this?


Solution

  • The solution we chose at the time was something similar to this:

    TryUpdateModel(product, null, null, new[] {"Category"}, collection);
    int categoryId;
    if (int.TryParse(collection["Category.Id"], NumberStyles.Integer, CultureInfo.InvariantCulture, out categoryId) && categoryId > 0) {
        product.Category = _categoryRepository.Load(categoryId);
    }
    else {
        product.Category = null;
    }
    

    We simply tell the model binder to exclude the association property and handle that manually. Not pretty but worked at the time....