ModelState.isValid is false because the model's Id property collides with the passed parameter

I am very new to MVC and am having a hard time figuring out the best way to get around this issue.

I have a modified version of an auto-scaffolded action "Create" (GET)

that takes an optional parameter (the default for the dropdown). Then it passes the form into action "Create" (HTTPPost)

.

public ActionResult Create(string id = null)
{ 
    //use id to pick a default item in a drop down list.
}

[HttpPost]
public ActionResult Create(Registration registration)
{ 
    //this method doesn't care one iota what was passed to the GET method
    // but registration.id is invalid because it trying to set it to the value that was passed to the get method.  If id in the get method is not passed, then everything is fine
}

      

The problem is that if a value was passed to the GET action create, then it ModelState.isValid

is false in the action POST create

, because it is trying to fill the value that was passed to the GET action into the primary key (Id) of the model field in the POST action (which should be omitted since it is automatically generated.

I know I can just change the primary key column name to something other than Id, but I have no control over the database and am trying to find another remedy for this problem.

If I try to rename the parameter in the GET action from "id" to something else, then I need to change the links from CREATE/paramValue

to CREATE/?paramName=paramValue

, and I don't want to. (although I will if this is the only easy way).

Is there a way to make the POST action ignore the values ​​passed to the GET action?

+3


source to share


2 answers


I think what you are trying to do is parameterize your controller's Create method with the id of the type of the object it should create.

While there is no hard and fast parameter naming convention in ASP.NET MVC, I think most people expect a named parameter to id

refer to the object being processed, not its attribute.

With this in mind, my suggestion would be to do the following:

  • Rename the parameter in the GET version of the Create method to something appropriate like typeId or whatever, so the signature then looks like public ActionResult Create(string typeId = null)

  • Add a custom route to MvcApplication

    that allows you to have nicely formatted /Create/typeId

    rather than force to have/Create?typeId=value

You can do this by adding the following lines of code before the default route:

routes.MapRoute
(
    name: "CreateGet",
    url: "Controller/Create/{typeId}",
    defaults: new
    {
        controller = "Controller",
        action = "Create",
        typeId = UrlParameter.Optional
    }
);

      

The default route looks something like this:

routes.MapRoute
(
    name: "Default",
    url: "{controller}/{action}/{id}",
    defaults: new
    {
        controller = "Site",
        action = "Index",
        id = UrlParameter.Optional
    }
);

      

I think this will fix your problem, although make sure the custom route I suggest precedes the default route.

Update:

You have a Get method and a Post method.



The Get method (possibly) creates a view model object and sends it to the view, which displays it. Alternatively, your view can have various inputs, hardcoded into HTML or injected via an AJAX call. Regardless of how they got there, your rendered view that goes out to the web browser looks something like this:

<html>
<head />
<body>
<form>
<input id="Input1" ... />
<select id="Select1" ... />
</form>
</body>
</html>

      

The above structure simplifies the structure of the HTML code.

The input IDs correspond to the names of the properties in the view model object that will be created when this page is posted back, that is, an instance of the class Registration

.

On the way back (to the Post method), the model binding takes values ​​from the request (query string parameters, form values, etc.) and binds them back to the model object that is passed to the Post method.

In your case, what most likely happened is that there was a mismatch between what it id

meant in the Get method (and in the rendered view) and what it id

meant in the object Registration

specified by the Post method.

The reason for the inconsistency was that the name of the property Registration.Id

was dictated by the database and Create(string id)

dictated by the default route, but they are actually different things, so it should be named differently.

This left you needing to have a clean / Create / id form url, which is easy to do with a custom route.

Now, if you really want to exclude a parameter from your model binding logic, you have to go with another answer that used an attribute in a Registration

Post method parameter to explicitly DefaultModelBinder

exclude that a parameter from the binding logic.

Once you have done this, the property that it is required will retain its default value given to it by the CLR after instantiation and running constructors.

ASP.NET MVC also supports custom model bindings, which can significantly change the logic of model binding if needed, although this is not applicable in your case.

+1


source


Try using BindAttribute

:

[HttpPost]
public ActionResult Create([Bind(Exclude = "RegistrationId")]Registration registration)
{ 

}

      



It will omit RegistrationId

POSTed with the form when binding to the model ...

+2


source







All Articles