问题
Follwing convention(s) are given. Each Action has a single parameter of type BaseRequest with data depending on the Action. The ViewModel is always of type BaseResponse.
What I'm trying to do is, that if a View contains a form, the POST-Action requires some sort of BaseRequest.
How can I achieve correct-model binding as of the ViewModel is BaseResponse?
I already tried to add a property of XYZRequest in XYZResponse so I could bind like
@Html.ChecBoxFor(m => m.RequestObject.SomeBooleanProperty)
but this will generate name RequestObject.SomeBooleanProperty which will not bind correctly to the POST-Action which accepts the XYZRequest.
Is there something completely wrong with this kind of conventions or am I missing something?
Update #1
I also tried creating a new temporary object of type XYZRequest and bind to it like
@Html.CheckBoxFor(m = tmpReq.SomeBooleanProperty)
which will render a name of tmp.SomeBooleanProperty which also could not be bound.
Update #2 - additional information
Following structure is set.
BaseRequestisabstractGetOverviewRequest:BaseRequestGetOverviewRequesthas properties of typestring,intor any other complex type and evenListsorDictionaries
If the GetOverviewResponse, which inherits from BaseResponse, is given back to the View and provides a property named TheProperty of type GetOverviewRequest binding fails as of
@Html.TextBoxFor(m => m.TheProperty.SomeBooleanValue)
will try to bind to TheProperty-property in the GetOverviewRequest object, which just not exists.
This may would work if GetOverviewRequest has a property called TheProperty to bind. But if it is named differently, binding will also fail.
I just want something like
<input name="SomeBooleanValue">
<input name="SomeComplexType.SomeStringValue">
instead of
<input name="TheProperty.SomeBooleanValue">
<input name="TheProperty.SomeComplexType.SomeStringValue">
Update #3 - added sample project
Sample project via dropbox.com
Update #4 - explanation, why solution from @StephenMuecke is not working
As mentioned in comments, the solution in other question needs to know the name of the property in the GetOverviewResponse-object. The property is named TheProperty, therefore I have to add [Bind(Prefix = "TheProperty)] to enable correct binding. I really don't like magic strings. And "TheProperty" is a magic string. If one changes the name of the TheProperty to RenamedProperty, the whole binding will fail.
So. I'm now looking for a way to set the prefix some-kind dynamically.
[Bind(Prefix = GetOverviewResponse.NameOf(m => m.TheProperty))]
would be really awesome. Maybe some kind of a custom attribute? As of BindAttribute is sealed, there is no chance to create one inherited from this.
Any ideas?
回答1:
The binding in MVC works on a Name / Value dictionary. So if you have:
public class BaseRequest
{
public string prop1 {get; set;}
public string prop2 {get; set;}
public string prop3 {get; set;}
}
cshtml:
@Html.TextBoxFor(x => x.prop1)
Then the object that the controller accepts doesn't matter:
public ActionResult MyAction(NotBaseRequest request)
{
//do something
}
new object taken in:
public class NotBaseRequest
{
public string prop1 {get; set;}
}
MVC would bind this with no problem.
So if you want a child object to be bound then you have to have the base object in the object that the controller takes in:
public class BaseRequest
{
public NotBaseRequest NotBaseRequest {get; set;}
}
cshtml
@Html.TextBoxFor(x => x.NotBaseRequest.prop1)
//<input type="text" name="NotBaseReqest.prop1" value />
MVC will use the name attribute to send the value to the controller.
controller
public ActionResult MyAction(OtherRequest request)
{
//do something
}
The object you take in can be called anything as long as it has NotBaseRequest in it.
new object:
public class OtherRequest
{
public NotBaseRequest NotBaseRequest {get; set;}
}
The name attribute of the html object will create the child object and assign its values.
回答2:
As stated by comments I've created some new structure to achieve what I needed.
I created a new class BaseRequestWrapperResponse<TRequest> to encapsulate the Request-object in a Response
public abstract class BaseRequestWrapperResponse<TRequest> : BaseResponse where TRequest : IRequestFromResponse
This class has currently one property:
public TRequest Request { get; set; }
Next I've created an interface:
public interface IRequestFromResponse
from which my Requset-object will inherit -> will be used for binding.
In my custom modelbinder override of protected override object CreateModel
I check if (modelType.GetInterfaces().Contains(typeof(IRequestFromResponse))) to see if my Request needs special handling. If so I create the BindAttribute dynamically and set it before the normal binder will do the rest:
var bindAttribute = new BindAttribute {Prefix = GetPropertyName<BaseRequestWrapperResponse<IRequestFromResponse>, IRequestFromResponse>(r => r.Request)};
TypeDescriptor.AddAttributes(modelType, bindAttribute);
where GetPropertyName is defined:
private static string GetPropertyName<TSource, TProperty>(Expression<Func<TSource, TProperty>> propertyLambda)
{
var member = (MemberExpression)propertyLambda.Body;
return member.Member.Name;
}
See also: Get name of property in abstract generic class
来源:https://stackoverflow.com/questions/31616609/asp-net-mvc-4-property-renaming-for-posting