Due to some refection-fu I want to use the MVC ModelBinders to bind the request to an object with a name and Type that is only known at runtime.
e.g.
public ActionResult BindSomething()
{
Type type = typeof(Some.Type);
string parameterName = "someParameter"; //this corresponds to a particular form input name
var binder = Binders.GetBinder(desiredType, true);
var x = binder.BindModel(this.ControllerContext, ???) //??? should be a ModelBindingContext. Where can I get this from
return View(x);
}
I think I need to get hold of the ModelBindingContext, or create a new, valid one, but how do I do this?
edit: apologies if I wasn't clear enough - I already know about TryUpdateModel, but, as far as I understand it that binds ALL the posted values to properties of the model object you pass in. I just want to get the corresponding object for a single posted parameter.
You can use TryUpdateModel as rouen suggested, you could also implement a custom model binder that can bind the correct type. This approach has the advantage of letting you deal with Interfaces or Abstract models and keeps your binding code out of your actions. It's a little neater but I would only really recommend it if it's going to be reused in other parts of your code.
Use controller method TryUpdateModel
http://msdn.microsoft.com/en-us/library/system.web.mvc.controller.tryupdatemodel.aspx
It will choose appropriate binder according argument type and perform model binding for you.
Related
I have such action:
[ObjectRequired]
public ActionResult Campaign(int? id, SomeClass object = null)
{
...
}
What i need is to route to this action:
a) with only int parameter (.../Campaign/12345)
b) with no parametes (optionally) (.../Campaign)
MVC error says, that there is no nonparametric constructor (if delete "object" parameter - it's ok). But i cant delete "object" parameter, because i need to check some values and pass value from [ObjectRequired] attribute like this:
filterContext.ActionParameters["object"] = _someObject;
I don't want to use constructions like ViewData. Where's the right way?
I'm not 100% on what I'm about to say but I'll say it as if I am...
You can't pass an object through to a get. You could pass back parameters and use a custom route handler to build the object for you.
You could also hold the object in data and use Entity Framework to get the model.
You could also use TempData, but that's similar to ViewData.
You could also hold it in their Session.
I'm using EF database first and with MVC.
I'm wanting to add some validation on a property to compare its old value to its new one and report a validation error to the MVC ModelState if there is a problem.
This would be easy enough using code first and validating using 'set' on the property. However I can't do this using database first because its auto generated.
I've looked at using IValidatableObject and the validate() method however by then the value has already been changed on the property so I can't see the old one anymore to compare to.
Short of creating a method to pass the new value into first to check it, I can't think of another way.
Any suggestions?
Thanks
If you want to compare a new value to an old value then you are going to have to grab the values from the database first (before updating) and compare them:
[HttpPost]
public ActionResult Update(MyObject myObject)
{
var oldObject = db.Objects.FirstOrDefault(o => o.Id == myObject.Id);
//Compare oldObject.Value to myObject.Value
}
You could still use IValidatebleObject and pass in the objects that you need to keep that logic outside the controller.
Its not the ideal and this has started illustrating some of the weaknesses in Model and DB first but here's how I ended up doing it.
I decided to change the property in my model so that set was private and then create a separate method in the partial class to set the value. The validation is then all done in that method.
Thanks for your help anyway
public ActionResult RenderMyThing(IList<String> strings)
{
return View("RenderMyView");
}
How do I pass in strings?
routes.MapRoute("MyRoute", "RenderMyThing.aspx", new { controller = "My", action = "RenderMyThing" });
Is there a way I could pass in strings here?
Secondly, how does ASP.NET MVC know that action is my action, and controller is my controller. Like I saw this in samples, and it does work, but isn't it just an anonymous object with no type?
This is the provenance of model binding: the framework needs to have some instruction as to how to turn a "request", which comes out of the routing context, query string, forms collection, etc., into the parameters that your action method wants.
The DefaultModelBinder will generate a list if it sees that you have multiple key-value pairs with the same key (and appropriately typed/convertible values) - for the details, Phil wrote a good post about this:
If you need fancier binding requirements, you can implement a custom model binder and explicitly define how route values and the other bits get translated into objects (or collections of objects).
Short: how does modelbinding pass objects from view to controller?
Long:
First, based on the parameters given by the user through a search form, some objects are retrieved from the database.
These objects are given meta data that are visible(but not defining) to the customer (e.g: naming and pricing of the objects differ from region to region).
Later on in the site, the user can click links that should show details of these objects.
Because these meta data are important for displaying, but not defining, I need to get the previously altered object back in the controller.
When I use the default asp.net mvc modelbinding, the .ToString() method is used. This off course doesn't return a relevant string for recreating the complete object.
I would have figured the ISerializable interface would be involved, but this is not so.
How should I go about to get the desired effect? I can't imagine I'm the first one to be faced with this question, so I guess I'm missing something somewhere...
The default model binding takes form parameters by name and matches them up with the properties of the type specified in the argument list. For example, your model has properties "Price" and "Name", then the form would need to contain inputs with ids/names "Price" and "Name" (I suspect it does a case insensitive match). The binder uses reflection to convert the form values associated with these keys into the appropriate type and assigns it to the properties of a newly created object of the type specified by the parameter (again derived by reflection).
You can actually look at (and download) the source for this at http://www.codeplex.com/aspnet, although you'll have to drill down into the MVC source from there. I'd give a link to the DefaultModelBinder source, but the way they are constructed, I believe the link changes as revisions are introduced.
So, to answer your question, you need to have parameters (could be hidden) on your form that correspond to the properties of the object that you want to recreate. When you POST the form (in the view) to the controller, the binder should reconstitute an object of the specified type using the form parameters. If you need to do translation from the values in the form parameter to the object properties, you'll probably need to implement your own custom model binder.
[EDIT] In response to your second post:
Let's say that we want to have a link back to an action that uses a customized object. We can store the customized object in TempData (or the Session if we need it to last more through more than one postback) with a particular key. We can then construct the action link and provide the key of the object as value to the ActionLink in an anonymous class. This will pass back the key as a Request parameter. In our action we can use the key from this parameter to retrieve the object from TempData.
<%= Html.ActionLink( ViewData["CustomObject1",
"Select",
new { TempDataKey = ViewData["CustomObject1_Key"] }
) %>
public ActionResult Select()
{
Entity custObj = null;
string objKey = Request.Params["TempDataKey"];
if (!string.IsNullOrEmpty(objKey))
{
custObj = (Entity)TempData[objKey];
}
... continue processing
}
#tvanfosson
Thanks for your explanation, but what about links? (no forms involved)
Currently the Html.ActionLink(c=>c.Action(parameter), "label") takes objects as parameter. These have to be translated into URL parts. For this, MVC ALWAYS goes to the .ToString() method. I don't want to serialize my object in the ToString method.
Shouldn't I be able to somehow help the framework serialize my object? Say through the ISerialize interface or something?
i have a simple form for an ASP.NET MVC application. I have a form property that is named differently (for whatever reason) to the real property name.
I know there's [Bind(Exlcude="", Include="")] attribute, but that doesn't help me in this case.
I also don't want to have a (FormsCollection formsCollection) argument in the Action method signature.
is there another way I can define the mapping?
eg.
<%= Html.ValidationMessage("GameServer", "*")%>
results in ..
<select id="GameServer" name="GameServer">
<option value="2">PewPew</option>
</select>
this needs to map to..
myGameServer.GameServerId = 2; // PewPew.
cheers!
i believe you will need to define it in your controller arguments or else it wouldnt have any clue what to accept.
public ActionResult GameServer(string GameServer){
GServer myGameServer = new GServer();
myGameServer.GameServerId.ToString() = GameServer;
return View("GameServer");
}
you can pass in the name/id of the parameter your trying to go for on your view page, it will automagically know the value to recieve based on the id on your view.
What's wrong with having a FormCollection passed as argument? I had to do the same thing as you and I just excluded the GameServer property in the BindAttribute.
Another thing you have to note is that Html.ValidationMessage("GameServer", "*") won't work because the underlying model doesn't contain a GameServer property. You have to add it to the model. I don't know if there is a better way to do it, I didn't find but it was required to make ValidationMessage works
You can create you own ModelBinder (see how to) to do the custom mapping.
An overkill IMO, but you might have your reasons...