问题
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.
BaseRequest
isabstract
GetOverviewRequest
:BaseRequest
GetOverviewRequest
has properties of typestring
,int
or any other complex type and evenLists
orDictionaries
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