Modify post data with a custom MVC extension? - asp.net-mvc

So I'm looking into writing some custom MVC extensions and the first one I'm attempting to tackle is a FormattedTextBox to handle things such as currency, dates, and times. I have the rendering of it working perfectly, formatting it, working with strong types and everything all golden. However, the problem I'm now running into is cleaning up the formatted stuff when the page posts the data back.
Take for example, a currency format. Let's use USD for these examples. When an object has a property as a decimal, the value would be 79.95. Your edit view would be something like:
<%= Html.FormattedTextBox(model => Model.Person.HourlyWage, "{0:C}") %>
This is all well and good for the GET request, but upon POST, the value is going to be $79.95, which when you assign to that decimal, gets unhappy very quickly and ends up shoving a 0 in there.
So my question is, how do I get code working somewhere to work with that value before the MVC Framework goes and starts shoving it back into my ViewModel? I'd much rather this be done server-side than client-side.
Thanks!!

One solution would be to write a custom model binder which will be able to convert bind $79.95 to a decimal field in your model. Another solution is to do it on the client side. In a jQuery form.submit handler you could modify the values of the input fields to be posted.

You can use custom Model Binders to shove the posted data into an object.
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(
[ModelBinder(typeof(YourModelBinderAttribute))]YourClass obj)
{
// your code here
return View();
}
public class MessageModelBinderAttribute : IModelBinder
{
#region IModelBinder Members
public object BindModel(
ControllerContext controllerContext,
ModelBindingContext bindingContext)
{
YourClass obj = new YourClass();
ValueProviderResult result;
if (bindingContext.ValueProvider.TryGetValue(
"YourPostedInputName", out result))
{
obj.Price = result.AttemptedValue;
}
return message;
}
#endregion
}

Related

MVC - Inherited view model members [duplicate]

My Post call does not return the correct Model type. It always use the baseObject instead of the correct derived object that I passed in from the Get
RestaurantViewModel.cs
public class RestaurantViewModel{
public Food BaseFoodObject{get;set;}
}
Food.cs
public class Food{
public string Price{get;set;)
}
Bread.cs -- Inherit from Food
public class Bread:Food{
public int Unit{get;set;}
}
Milk.cs -- Inherit from Food
public class Milk:Food{
public string Brand{get;set}
}
Editor For Template for Bread. Display the unit and allow user to edit
Index.html
#Model RestaurantViewModel
#using(Html.BeginForm("SaveFood", "Food"))
{
#Html.EditorFor(m=>m.BaseFoodObject)
<input type="submit" value="Process"/>
}
Bread.cshtml
#Model Bread
<div>
#Html.TextboxFor(bread=>bread.Unit)
</div>
FoodController.cs
public ActionResult Index(){
Bread bread = new Bread(){
Price = "$10",
Unit = 1
}
RestaurantViewModel viewModel = new RestaurantViewModel(){
BaseFoodObject = bread
}
return View(viewModel);
}
public ActionResult Post(RestaurantViewModel viewModelPost)
{
// When I inspect the viewModelPost, there is no attribute for unit
}
Final Result:
1. The display looks correct. EditorFor is smart enough to pick the correct editor template and display the value correctly
2. The Save does not work. The Unit attribute of Bread Object does not get passed in with the RestaurantViewModel. The reason for that is the RestaurantViewModel used the Food object instead of Bread
I hope there is away to modify the EditorFor and tell it to use the Model in the View or the Object Type that I passed in when I display it.
Thanks
Update 1: I solved this problem by using the custom binder and using a factory to decide which object I really want. This helps construct the correct Model which I want
MVC is stateless. A couple of references.
There's a couple of statements in your question that conflict with this, and how MVC binding works eg:
My Post call does not return the correct Model type.
Possibly just terminology, but your Post call does not 'return a model type' - it goes into the model that's defined in the post action, in this case RestaurantViewModel.
instead of the correct derived object that I passed in from the Get
because it is stateless, it knows nothing about the model you passed in from the get... absolutely nothing.
The final html rendered via the getaction+view.cshtml+model is not linked to the postaction. You could just as easily take the rendered html, save it, reboot your PC, reload the rendered html and it will work exactly the same way.
a way to modify the EditorFor and tell it to use the Model in the View or the Object Type that I passed in when I display it
When you use EditorFor it sets an ID and name attribute based on the model it was bound to, so it already does this, but perhaps you are not binding to the model you want to bind to to get the correct id.
So, to the question, if, in 'normal' C# code you were to instantiate a new instance of RestaurantViewModel, what would you expect the type of BaseFoodObject to be?
This is what the ModelBinder is doing - it's creating a new RestaurantViewModel.
As your post action method's signature does not include anything to do with Bread - all the bread properties are ignored.
Some options:
Check for the food properties after binding and read them manually (probably the quickest+easiest but not very "mvc-ish")
public ActionResult Post(RestaurantViewModel viewModelPost)
{
if (!string.IsNullOrEmpty(Request.Form["Unit"]))
// it's a bread form
to make this easier, you could provide a hidden field with the type
if (Request.Form["Type"] == typeof(Bread).Name)
{
var bread = new Bread { Unit = Request.Form["Unit"] }
Add bread to the action so it's bound
public ActionResult Post(RestaurantViewModel viewModelPost, Bread bread)
but then, obviously, it won't work for milk.
So could extend this using an ActionNameSelector to select the correct action
public ActionResult PostBread(RestaurantViewModel viewModelPost, Bread bread)
public ActionResult PostMilk(RestaurantViewModel viewModelPost, Milk milk)
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public sealed class FoodSelectorAttribute : ActionNameSelectorAttribute
{
public override bool IsValidName(ControllerContext controllerContext, string actionName, MethodInfo methodInfo)
{
... check if provided parameters contains bread/milk
(related link but not a solution to this specific case)
Another option might be to change the Restaurant type to a generic, but would require a few more changes (and ideally use of interfaces), and more details (provided here as an idea, rather than a solution)
The basics would be:
public class RestaurantViewModel<T>
where T: Food
{
}
public ActionResult Post(RestaurantViewModel<Bread> viewModelPost)
public ActionResult Post(RestaurantViewModel<Milk> viewModelPost)
but I've not confirmed if the default ModelBinder would work in this case.
The problem comes with the post. Once you post, all you have is a set of posted data and a parameter of type, RestaurantViewModel. The modelbinder sets all the appropriate fields on Food because that's all it knows. Everything else is discarded. There's nothing that can be done about this. If you need to post fields related to Bread then the type of your property must be Bread. That's the only way it will work.

ASP.NET MVC2 - Custom Model Binder Examples

I am trying to find some examples of building a custom model binder for a unique binding scenario I need to handle, but all of the articles I found were for older versions of MVC which are no longer relevant in MVC2. I've been referencing the DefaultModelBinder source code to try to get a general feel for what I need to do, but it's entirely more complicated than my scenario and I'm having trouble isolating the specific logic I need to implement.
My goal is to take a collection of Checkbox/Textbox pairs and for all of the Checked pairs I would like to create a key/value pair of the Checkbox's value and the associated Textbox's value. After aggregating this data I need to do some string serialization on the collection so I can store it in a string property of the desired Model type. I already the data being sent from the form in a manageable format which will allow me to relate a given Checkbox to a specific Textbox, it's just a matter of figuring out how to get all the pieces where I need them.
Does anyone know of some up-to-date tutorials that can get me started with building a custom model binder?
I don't know why you think a lot has changed since MVC 1 regarding custom model binders. But If I understand what you are trying to do, it should be fairly easy.
public class CustomModelBinder : DefaultModelBinder {
public override object BindModel(ControllerContext controllerContext,
ModelBindingContext bindingContext) {
NameValueCollection form = controllerContext.HttpContext.Request.Form;
//get what you need from the form collection
//creata your model
SomeModel myModel = new SomeMode();
myModel.Property = "value";
//or add some model errors if you need to
ModelStateDictionary mState = bindingContext.ModelState;
mState.Add("Property", new ModelState { });
mState.AddModelError("Property", "There's an error.");
return myModel; //return your model
}
}
And your action :
public ActionResult Contact([ModelBinder(typeof(CustomModelBinder))]SomeModel m){
//...
}
Was that the kind of information you are looking for?
Take a look at several examples of Custom MVC Model binders on my blog.

ASP.Net MVC : Sending JSON to Controller

I want to be able to send JSON as opposed to the standard QueryStrings when making a post to my controllers in ASP.Net MVC. I have the Front-End stuff working fine (building and then submitting my JSON objects).
The problem is on the controller side where the default ModelBinders that ship with the MVC framework do not support this.
I have seen a combination of ways around this, one of them is to apply a filter which takes the object as a parameter, uses a JSON library to de-serialise it, and adds that to the action parameters. This is not ideal.
The other, better, way is to use a custom Model Binder. All the ones I have seen though presume you will have only one model and that will be a class rather than a variable. If you have multiple ones it breaks down.
Has anyone else encountered this? One idea I had was if I could simply override how MVC deals with the FormCollection and intercept there, adding the values to the collection myself and hoping MVC can do the rest in it's normal fashion. Does anyone know if that is possible?
The key issue, I think, is that my problem is not with binding because my view models are no different to how they where before. The problem is getting the values from the JSON Post.
If I am correct MVC get's the values from the QueryString and puts it into the form collection which is then used for ModelBinding. So shouldn't the correct method be to change the way the FormCollection gets assigned?
Example of an action:
public ActionResult MyFirstAction(Int32 ID, PersonObject Person, ClassObject ClassDetails)
{
//etc
}
The normal binding works, JSON doesn't and all the example of Model Binders will not work either. My best solution so far is to convert the object to a dictionary and loop though each param and match it up. Doesn't seem ideal.
I use a custom model binder for json like this:
public class JsonModelBinder<T> : IModelBinder {
private string key;
public JsonModelBinder(string requestKey) {
this.key = requestKey;
}
public object BindModel(ControllerContext controllerContext, ...) {
var json = controllerContext.HttpContext.Request[key];
return new JsonSerializer().Deserialize<T>(json);
}
}
And then wire it up in Global.asax.cs like this:
ModelBinders.Binders.Add(
typeof(Product),
new JsonModelBinder<Product>("ProductJson"));
You can read more about this here: Inheritance is Evil: The Epic Fail of the DataAnnotationsModelBinder
EDIT
The JsonModelBinder should be used on the controller action parameter typed as Product only. The Int32 and ClassObject should fall back to the DefaultModelBinder. Are you experiencing a different result?

How can I keep my MVC Views, models, and model binders as clean as possible?

I'm rather new to MVC and as I'm getting into the whole framework more and more I'm finding the modelbinders are becoming tough to maintain.
Let me explain...
I am writing a basic CRUD-over-database app. My domain models are going to be very rich. In an attempt to keep my controllers as thin as possible I've set it up so that on Create/Edit commands the parameter for the action is a richly populated instance of my domain model. To do this I've implemented a custom model binder.
As a result, though, this custom model binder is very specific to the view and the model. I've decided to just override the DefaultModelBinder that ships with MVC 2. In the case where the field being bound to my model is just a textbox (or something as simple), I just delegate to the base method. However, when I'm working with a dropdown or something more complex (the UI dictates that date and time are separate data entry fields but for the model it is one Property), I have to perform some checks and some manual data munging.
The end result of this is that I have some pretty tight ties between the View and Binder. I'm architecturally fine with this but from a code maintenance standpoint, it's a nightmare.
For example, my model I'm binding here is of type Log (this is the object I will get as a parameter on my Action). The "ServiceStateTime" is a property on Log. The form values of "log.ServiceStartDate" and "log.ServiceStartTime" are totally arbitrary and come from two textboxes on the form (Html.TextBox("log.ServiceStartTime",...))
protected override object GetPropertyValue(ControllerContext controllerContext,
ModelBindingContext bindingContext,
PropertyDescriptor propertyDescriptor,
IModelBinder propertyBinder)
{
if (propertyDescriptor.Name == "ServiceStartTime")
{
string date = bindingContext.ValueProvider.GetValue("log.ServiceStartDate").ConvertTo(typeof (string)) as string;
string time =
bindingContext.ValueProvider.GetValue("log.ServiceStartTime").ConvertTo(typeof (string)) as string;
DateTime dateTime = DateTime.Parse(date + " " + time);
return dateTime;
}
if (propertyDescriptor.Name == "ServiceEndTime")
{
string date = bindingContext.ValueProvider.GetValue("log.ServiceEndDate").ConvertTo(typeof(string)) as string;
string time =
bindingContext.ValueProvider.GetValue("log.ServiceEndTime").ConvertTo(typeof(string)) as string;
DateTime dateTime = DateTime.Parse(date + " " + time);
return dateTime;
}
The Log.ServiceEndTime is a similar field.
This doesn't feel very DRY to me. First, if I refactor the ServiceStartTime or ServiceEndTime into different field names, the text strings may get missed (although my refactoring tool of choice, R#, is pretty good at this sort of thing, it wouldn't cause a build-time failure and would only get caught by manual testing). Second, if I decided to arbitrarily change the descriptors "log.ServiceStartDate" and "log.ServiceStartTime", I would run into the same problem. To me, runtime silent errors are the worst kind of error out there.
So, I see a couple of options to help here and would love to get some input from people who have come across some of these issues:
Refactor any text strings in common between the view and model binders out into const strings attached to the ViewModel object I pass from controller to the aspx/ascx view. This pollutes the ViewModel object, though.
Provide unit tests around all of the interactions. I'm a big proponent of unit tests and haven't started fleshing this option out but I've got a gut feeling that it won't save me from foot-shootings.
If it matters, the Log and other entities in the system are persisted to the database using Fluent NHibernate. I really want to keep my controllers as thin as possible.
So, any suggestions here are greatly welcomed!
Thanks
You can use ViewModel:
class ViewModelClass
{
[DateValidationAttribute]
public property DateTime ServiceStartTimeDate { get; set; }
[TimeValidationAttribute]
public property DateTime ServiceStartTimeTime { get; set; }
}
DefaultModelBinder binder doesn't need any modification to bind these fields. Then you can write function to combine these fields:
class DateUtil
{
public static DateTime CombineDateAndTime(DateTime Date, DateTime Time);
}
Then you get entity from database:
var entity = context.GetUpdatedEntity(id);
entity.ServiceStartTime = DateUtil.CombineDateAndTime(viewModel.ServiceStartTimeDate,viewModel.ServiceStartTimeTime);
If you really want to have it in model binder, you can do it like this:
public class DateAndTimeModelBinder : DefaultModelBinder
{
protected override object GetPropertyValue(ControllerContext controllerContext,
ModelBindingContext bindingContext,
PropertyDescriptor propertyDescriptor,
IModelBinder propertyBinder)
{
if (propertyDescriptor.PropertyType == typeof(DateTime))
{
string time = bindingContext.ValueProvider.GetValue(CreateSubPropertyName(bindingContext.ModelName, propertyDescriptor.Name + "Time")).ConvertTo(typeof(string)) as string;
var date = base.GetPropertyValue(controllerContext, bindingContext, propertyDescriptor, propertyBinder);
if ((date != null) && (time != null))
return CombineDateAndTime(date, time);
}
return base.GetPropertyValue(controllerContext, bindingContext, propertyDescriptor, propertyBinder);
}
}
When DateTime field is being bound, binder looks if there is additional form field with "Time" at the end (if we bind "ServiceStartTime", it look for "ServiceStartTimeTime", so you bind date portion to "ServiceStartTime" field and time portion to ""ServiceStartTimeTime"). If there is, it adds its value to date. You write it once and will work for every field. Code above will surely not work, it needs some adjustments, but shows idea.

Asp.Net MVC 1.0 custom Modelbinders - how to handle form posts and parameter names?

I have a custom model binding:
using System.Web.Mvc;
using MyProject.Model;
namespace MyProject.ModelBinders
{
public class VersionedIdModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
//Not completely happy with this. What if the parameter was named something besides id?
return VersionedId.Parse(bindingContext.ValueProvider["id"].RawValue.ToString());
}
}
}
which works as long as the id is passed in the url (either explicitly or via a route definition.) However, if the Id is passed in a form as a hidden input field:
<input type="hidden" id="id" name="id" value="12a" />
Then ValueProvider["id"].RawValue is a string array, so the code below doesn't behave as expected.
In the controller code, I expect to simply be able to do:
public ActionResult MyAction(VersionedId id)
{
...
}
Two questions:
I am surprised that passing the id via form post causes the RawValue to be a string array. Is this the expected behavior, and is the "standard" way to handle this to check the type of the RawValue? I need to be able to handle both form posts and url routes.
Is it normal to check for the Name of the parameter in the model binder, or is there another way to do this whereby the controller action can use whatever parameter name it likes?
Regarding your question 1, there is some interesting discussion posted Custom ModelBinder and Release Candidate.
The RawValue property is what is
consumed by the ConvertTo() method,
while the AttemptedValue property is
what is consumed by the validation
system to provide an error message.
To see an example of this, break into
one of your action methods and inspect
the Controller.ValueProvider entries.
Some of the RawValue entries will be
strings (like those from Routing),
some of them will be string arrays
(like those from QueryString or Form),
etc. The reason for the
differentiation between RawValue and
AttemptedValue is that RawValue
supports passing array types to the
ConvertTo() method. We can't convert
to a multi-element array from a single
AttemptedValue, but we can convert to
a multi-element array from a RawValue
that is itself an array. This
supports the infrastructure to allow
binding to collection types.
I agree about the unfortunate naming, though unfortunately this isn't likely to change. :(
You could directly access
controllerContext.HttpContext.Request["id"]
and get the value from there. Should be a simple string in that case.

Resources