views:

19

answers:

1

Below, on initial load (Edit), everything displays fine. However, when I POST (Action below), it looks like it goes and tries to grab the ProductAttribute models separately, with the Product's id, which promptly fails. How to keep my Binder implementation from trying to re-bind Collections as separate entities?

Thanks!

Model

public class Product {
    virtual public long Id { get; set; }

    virtual public string Name { get; set; }

    private IList<ProductAttribute> _productAttributes;

    public virtual IList<ProductAttribute> ProductAttributes { 
        get{
        if(_productAttributes == null){
            _productAttributes = new List<ProductAttribute>();
        }
        return _productAttributes;
        }
        set{
        _productAttributes = value;
        }
    }
}

View

<%using (Html.BeginForm(new {id = Model.Id > 0 ? (long?)Model.Id : null})) {%>
<table class="form">
  <% for(var i=0; i < Model.ProductAttributes.Count; i++){ 
    var pa = Model.ProductAttributes[i]; %>
  <tr>
    <th><%: Model.ProductAttributes[i].Name %></th>
    <td>
    <%: Html.Hidden("ProductAttributes.Index", i) %>
    <% if(pa.CanSpecifyValueDirectly()){ %>
    <%: Html.TextBoxFor(model => model.ProductAttributes[i].Value) %>
    <% } else { %>
    <%: Html.DropDownListFor(model => model.ProductAttributes[i].Value, new SelectList(pa.MarketAttribute.AttributeLevels, "id", "Name", pa.AttributeLevel)) %>
    <% } %>
    </td>
  </tr>
  <input type="submit" value="Save" />
</table>
<%}%>

Controller

public ActionResult Edit(long id, Product product) {
    ViewData.Model = product;
    if (ModelState.IsValid) {
        var results = product.Update();
        ViewData["results"] = results;
        if (!results.Error) {
            return RedirectToAction("Show", new { id = id });
        }
    }

    return View();
}

Binder

public class StackModelBinder : DefaultModelBinder {
    protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType) {
        var modelInterface = modelType.GetInterface("IModel");
        if (modelInterface == null) {
            return base.CreateModel(controllerContext, bindingContext, modelType);
        }

        var value = bindingContext.ValueProvider.GetValue("id").RawValue.ToString();
        if (string.IsNullOrEmpty(value)) {
            return base.CreateModel(controllerContext, bindingContext, modelType);
        }

        var id = Convert.ChangeType(value, typeof (long));
        var assembly = Assembly.GetAssembly(modelType);
        var dao = assembly.GetType(string.Concat(assembly.GetName().Name, ".core.GlobalDao`1[", modelType.FullName, "]"));

        if (dao == null) {
            return base.CreateModel(controllerContext, bindingContext, modelType);
        }

        var method = dao.GetMethod("Get");
        return method.Invoke(null, new[] {id});
    }
}
A: 

Typically, it is a bad idea to push entities through the model binder--they tend to be a bit too complex for it to handle, never mind the exciting stuff that goes on in modern ORMs, such as dynamic proxies, that can give the ModelBinder or the ORM fits.

Your best bet here is to change the rules and build a dedicated class for taking the edits and transferring that to the controller. This class can be ModelBinder-friendly and you get the added benefit of separating the UI from the domain entities.

Wyatt Barnett
In the interests of DRY, I am compelled not to maintain two objects that essentially represent the same thing. Any other solutions?
hoffmanc
Oh, I feel ya in terms of DRY. Except it isn't the same thing--one is responsible for taking the data into the controller, the other is responsible for being your domain entity. Think CQRS not duplicated code.
Wyatt Barnett