问题
My question is basicially the same as the situation presented in this stack overflow question where I find myself wanting to load the existing valid version of model from the DB, and update a portion of it as a certain sub-set of fields are exposed on my web form.
Is there anyway I can make the Model Binding process guarantee that my ID property will be bound first?
If I could guarantee this one thing, then, inside the setter of my ViewModel's ID property, I could trigger a 'load', so that the object is initially populated from the DB (or WCF service.. or Xml file.. or other repository of choice) and then the remaining properties submitted from the FORM post neatly merge into the object as MVC finishes up it's model binding process.
Then, my IValidatableObject.Validate method logic will nicely tell me if the resulting object is still valid.. and so forth and so on..
It just seems to me that having to write plumbing where I have two instances of a model(knownValidDomainModelInstanceFromStorage, postedPartialViewModelInstanceFromForm) and then manually mapping over desired properties is to repeat something that is really already handled by MVC... if I could only control the binding order of the identity.
EDIT - I discovered property binding order CAN be manipulated with a custom binder. Very easily. Read the answer I posted below. Would still welcome your feedback or observations.
回答1:
Ok, after reading up on how to customize the default model binder, I think this bit of code just may do the trick of sorting the properties and will give me the desired binding order every time. Basically allowing me to bind the identity properties first (and thus allowing me to have my View Model trigger a 'load') allowing the remainder of the model binding process to function essentially as a merge!
''' <summary>
''' A derivative of the DefaultModelBinder that ensures that desired properties are put first in the binding order.
''' </summary>
''' <remarks>
''' When view models can reliably expect a bind of their key identity properties first, they can then be designed trigger a load action
''' from their repository. This allows the remainder of the binding process to function as property merge.
''' </remarks>
Public Class BindIdFirstModelBinder
Inherits DefaultModelBinder
Private commonIdPropertyNames As String() = {"Id"}
Private sortedPropertyCollection As ComponentModel.PropertyDescriptorCollection
Public Sub New()
MyBase.New()
End Sub
''' <summary>
''' Use this constructor to declare specific properties to look for and move to top of binding order.
''' </summary>
''' <param name="propertyNames"></param>
''' <remarks></remarks>
Public Sub New(propertyNames As String())
MyBase.New()
commonIdPropertyNames = propertyNames
End Sub
Protected Overrides Function GetModelProperties(controllerContext As ControllerContext, bindingContext As ModelBindingContext) As ComponentModel.PropertyDescriptorCollection
Dim rawCollection = MyBase.GetModelProperties(controllerContext, bindingContext)
Me.sortedPropertyCollection = rawCollection.Sort(commonIdPropertyNames)
Return sortedPropertyCollection
End Function
End Class
Then, I can register this in place of my DefaultModelBinder, and supply the most common property names that I'd like to have 'floated' to the top of the ModelBinding process...
Sub Application_Start()
RouteConfig.RegisterRoutes(RouteTable.Routes)
BundleConfig.RegisterBundles(BundleTable.Bundles)
' etc... other standard config stuff omitted...
' override default model binder:
ModelBinders.Binders.DefaultBinder = New BindIdFirstModelBinder({"Id", "WorkOrderId", "CustomerId"})
End Sub
来源:https://stackoverflow.com/questions/19280598/best-way-to-do-partial-update-to-asp-net-mvc-model-merge-user-submitted-form