0

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 is abstract
  • GetOverviewRequest : BaseRequest
  • GetOverviewRequest has properties of type string, int or any other complex type and even Lists or Dictionaries

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?

KingKerosin
  • 3,639
  • 4
  • 38
  • 77
  • Can you post the model that you are using on the page? – Robert Jul 24 '15 at 18:55
  • @Robert added the project. Is this helping? – KingKerosin Jul 25 '15 at 08:00
  • possible duplicate of [Submitting a model to action that is different from view model's type](http://stackoverflow.com/questions/27070547/submitting-a-model-to-action-that-is-different-from-view-models-type) –  Jul 25 '15 at 14:04
  • @StephenMuecke this would do the trick. But I'm looking for something more dynamic. Beacuse if one changes the name `TheProperty` but did not change the bind-prefix, this will also fail. Any ideas how to do this dynamically? Something like `[Bind(Prefix=new ResponseObject().GetPropertyName(TheProperty))]` or something similar? – KingKerosin Jul 25 '15 at 14:24
  • No, here is nothing built in which will do that - you would have to write your own custom `ModelBinder`. –  Jul 25 '15 at 23:07

2 Answers2

0

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.

Robert
  • 4,306
  • 11
  • 45
  • 95
  • See my edited question. Maybe I simply don't get it or it was not clear what I want to achieve – KingKerosin Jul 24 '15 at 18:41
  • @KingKerosin Are you trying to flatten the result into an object with primitive types? – Robert Jul 24 '15 at 18:48
  • No. The Request can have primitive types and complex types. Event Lists and Dictionaries of complex types. So binding `response => response.ReqObject.PrimitiveType` would correctly bind to the Request-object expected by the POST-Action of the form, so the input-name would be `ReqObjectsName.PrimitiveType` instead of `ResponseObjectsName.ReqObjectsName.PrimitiveType`. Edited question – KingKerosin Jul 24 '15 at 18:51
0

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

Community
  • 1
  • 1
KingKerosin
  • 3,639
  • 4
  • 38
  • 77