开发者

How to prevent validation of relationships in model binder?

开发者 https://www.devze.com 2023-01-07 09:26 出处:网络
An example, if I have a class named Order with a field referencing a Customer, and then an Order form with an drop down list (<%= Html.DropDownListFor(e => e.Customer.ID, 开发者_如何转开发new Selec

An example, if I have a class named Order with a field referencing a Customer, and then an Order form with an drop down list (<%= Html.DropDownListFor(e => e.Customer.ID, 开发者_如何转开发new SelectList(...)) %>) for setting the Customer the model binder will create an empty Customer with only the ID set. This works fine with NHibernate but when validation is added to some of the fields of the Customer class, the model binder will say these fields are required. How could I prevent the model binder from validating these references?

Thanks!


Ages old question, but I figured I'd answer anyways for posterity. You need a custom model binder in this situation to intercept that property before it attempts to bind it. The default model binder will recursively attempt to bind properties using their custom binder, or the default one if not set.

The override you're looking for in DefaultModelBinder is GetPropertyValue. This is called over all properties in the model, and by default it calls back to DefaultModelBinder.BindModel - the entry point to the whole process.

Simplified model:

public class Organization
{
    public int Id { get; set; }

    [Required]
    public OrganizationType Type { get; set; }
}

public class OrganizationType
{
    public int Id { get; set; }

    [Required, MaxLength(30)]
    public string Name { get; set; }
}

View:

<div class="editor-label">
    @Html.ErrorLabelFor(m => m.Organization.Type)
</div>
<div class="editor-field">
     @Html.DropDownListFor(m => m.Organization.Type.Id, Model.OrganizationTypes, "-- Type")
</div>

Model Binder:

public class OrganizationModelBinder : DefaultModelBinder
{
    protected override object GetPropertyValue(
        ControllerContext controllerContext, 
        ModelBindingContext bindingContext, 
        System.ComponentModel.PropertyDescriptor propertyDescriptor, 
        IModelBinder propertyBinder)
    {
        if (propertyDescriptor.PropertyType == typeof(OrganizationType))
        {
            var idResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName + ".Id");
            if (idResult == null || string.IsNullOrEmpty(idResult.AttemptedValue))
            {
                return null;   
            }

            var id = (int)idResult.ConvertTo(typeof(int));
            // Can validate the id against your database at this point if needed...
            // Here we just create a stub object, skipping the model binding and
            // validation on OrganizationType
            return new OrganizationType { Id = id };
        }

        return base.GetPropertyValue(controllerContext, bindingContext, propertyDescriptor, propertyBinder);
    }
}

Note that in the View we create the DropDownListFor the model.foo.bar.Id. In the model binder, ensure that this is added to the model name as well. You can leave it off of both, but then DropDownListFor has a few issues finding the selected value without pre-selecting it in the SelectList you send it.

Finally, back in the controller, be sure to attach this property in your database context (if you're using Entity Framework, others might handle differently). Otherwise, it isn't tracked and the context will attempt to add it on save.

Controller:

public ActionResult Create(Organization organization)
{
    if (ModelState.IsValid)
    {
        _context.OrganizationTypes.Attach(organization.Type);
        _context.Organizations.Add(organization);
        _context.SaveChanges();

        return RedirectToAction("Index");
    }

    // Build up view model...
    return View(viewModel);
}
0

精彩评论

暂无评论...
验证码 换一张
取 消