Bind formValue to property of different name, ASP.NET MVC - asp.net-mvc

I was wondering if there was a way to bind form values passed into a controller that have different Id's from the class properties.
The form posts to a controller with Person as a parameter that has a property Name but the actual form textbox has the id of PersonName instead of Name.
How can I bind this correctly?

Don't bother with this, just write a PersonViewModel class that reflects the exact same structure as your form. Then use AutoMapper to convert it to Person.
public class PersonViewModel
{
// Instead of using a static constructor
// a better place to configure mappings
// would be Application_Start in global.asax
static PersonViewModel()
{
Mapper.CreateMap<PersonViewModel, Person>()
.ForMember(
dest => dest.Name,
opt => opt.MapFrom(src => src.PersonName));
}
public string PersonName { get; set; }
}
public ActionResult Index(PersonViewModel personViewModel)
{
Person person = Mapper.Map<PersonViewModel, Person>(personViewModel);
// Do something ...
return View();
}

You could have your own custom model binder for that model.
public class PersonBinder : IModelBinder {
public object BindModel(ControllerContext controllerContext,
ModelBindingContext bindingContext) {
return new Person { Name =
controllerContext.HttpContext.Request.Form["PersonName"] };
}
}
And your action :
public ActionResult myAction([ModelBinder(typeof(PersonBinder))]Person m) {
return View();
}

Related

MVC 5 Session Variable ModelBinder null on Action Method

I am doing an MVC APP. I have a View that Inherit from Model call UserModel that have 2 properties. UserName and Password. I want to save those values in Session variables, so I am using ModelBinder.
My class definition is like this.
public class UserModel
{
public string UserName { get; set; }
public string Password { get; set; }
}
My model binder is like this.
public class UserDetailModelBinder : IModelBinder
{
#region Constants
private const string SessionKey = "User";
#endregion
#region Public Methods and Operators
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
UserModel user = (controllerContext.HttpContext.Session != null) ? (controllerContext.HttpContext.Session[SessionKey] as UserModel) : null;
if (user == null)
{
user = new UserDetail();
controllerContext.HttpContext.Session[SessionKey] = user;
}
return user;
}
#endregion
}
And I have defined Properly in My global.asax
The problem I found is that my Action Method that receives a UserModel instance from the View is null. It reads what already has my Session instead of Reading the View, and then Save it in the Session.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(UserModel model)
{
}
I suppose it is because it's the same Model I defined to save in the BinderModel
So, my question would be, How can I save in Session, a Model inherit from the View using BinderModel?
You're setting the null value to UserModel and returned. You should be read the values from request and return it.
var request = controllerContext.HttpContext.Request;
if (user == null)
{
user = new UserModel() {
UserName= request.Form.Get("UserName").ToString(),
Password = request.Form.Get("Password").ToString()
};
controllerContext.HttpContext.Session["User"] = user;
}
Instead of using the model binder you could directly storing the user model to session in your login method. I'm not sure why you're choosing model binder.
public async Task<ActionResult> Login(UserModel model)
{
//Session["User"] = model
}

ASP.NET MVC Web API Models that are not tied to query string parameters

I know how to create a model class that mirrors query string variables so that when it comes into my Web API controller action, the model is populated.
However, is there a way to make it so that I'm not locked into the query string variable names as the properties on my model class?
Example:
public class MyModel {
public string o {get;set;}
}
public class MyController {
public string Get(MyModel model) {
}
}
Then, if my query string looks like:
GET http://domain.com/?o=12345
Is there a way to name that model property "Order" or something instead of "o" and then have it populated with the value from "o="?
You can create custom model binder that will bind data to model as you wish. To use it you should:
public string Get([ModelBinder(typeof(MyComplexTypeModelBinder))]MyModel model)
{
...
}
To create custom model binder you can inherit from IModelBinder or from DefaultModelBinder.
public class MyComplexTypeModelBinder : IModelBinder
{
public Object BindModel(ControllerContext controllerContext,
ModelBindingContext bindingContext)
{
if (bindingContext == null)
throw new ArgumentNullException("bindingContext");
// Create the model instance (using the ctor you like best)
var obj = new MyComplexType();
// Set properties reading values from registered value providers
obj.Order = FromPostedData<string>(bindingContext, "o");
...
return obj;
}
private T FromPostedData<T>(ModelBindingContext context, String key)
{
// Get the value from any of the input collections
ValueProviderResult result;
context.ValueProvider.TryGetValue(key, out result);
// Set the state of the model property resulting from
context.ModelState.SetModelValue(key, result);
// Return the value converted (if possible) to the target type
return (T) result.ConvertTo(typeof(T));
}
Solution for this scenario is custom IValueProvider. This ASP.NET MVC extension point is the correct place, where we can bridge the QueryString keys into Model.Property names. In comparison with ModelBinder, this will target exactly what we need (while not introducing later issues, when even other value providers (FORM) accidently contains that key...)
There is good tutorial how to introduce the custom IValueProvider:
http://donovanbrown.com/post/How-to-create-a-custom-Value-Provider-for-MVC.aspx
And there is an simple example which is able to provide values for Model "Order" property, coming as QueryString "o" key:
Factory
// Factory
public class MyValueProviderFactory : ValueProviderFactory
{
public override IValueProvider GetValueProvider(ControllerContext ctx)
{
return new MyValueProvider(ctx);
}
}
Provider
// Provider
class MyValueProvider : IValueProvider
{
protected HttpRequestBase Request { get; set; }
public MyValueProvider(ControllerContext ctx)
{
Request = ctx.HttpContext.Request;
}
// our custom logic to test QueryString keys, and expected prefixes
public bool ContainsPrefix(string prefix)
{
var containsSpecial =
"Order".Equals(prefix, StringComparison.OrdinalIgnoreCase)
&& Request.QueryString.AllKeys.Contains("o"
, StringComparer.InvariantCultureIgnoreCase);
return containsSpecial;
}
// Handling "Order" key
public ValueProviderResult GetValue(string key)
{
if (!ContainsPrefix(key))
{
return null;
}
var values = Request.QueryString.GetValues("o");
if (values.Any())
{
return new ValueProviderResult(values, values.First()
, CultureInfo.CurrentCulture);
}
return null;
}
}
And in the global.asax we have to inject it:
protected void Application_Start()
{
ValueProviderFactories.Factories.Add(new MyValueProviderFactory());
...

Escape Certain Characters On Model Property in ASP.NET MVC

Can I create an attribute that will let me modify the value of it in my ASP.NET MVC Model? It relates to this question below where '%' is being sent to the database, but I would like a generic way to escape certain characters with the data comes from the UI. I know you can validate properties, but can you modify them on the SET?
MySQL and LIKE comparison with %
[Clean]
public string FirstName { get; set; }
[Clean]
public string LastName{ get; set; }
Does this have a lot of value over just calling a clean method in the setter for each property? I worry that even if this were possible, it would introduce a lot of complexity depending on what the expected behavior was.
My suggestion is to just make a function and call it from the setter instead.
I think your Attribute should be at the class level to get access to this class properties
Lets say :
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)]
public class ClearAttribute : ValidationAttribute
{
private string[] wantedProperties;
public ClearAttribute(params string[] properties)
{
wantedProperties = properties;
}
public override object TypeId
{
get { return new object(); }
}
public override bool IsValid(object value)
{
PropertyInfo[] properties = value.GetType().GetProperties();
foreach (PropertyInfo property in properties)
{
if (wantedProperties.Contains(property.Name))
{
var oldValue = property.GetValue(value, null).ToString();
var newValue = oldValue + "Anything you want because i don't know a lot about your case";
property.SetValue(value, newValue, null);
}
}
return true;
}
}
And the usage should be:
[Clear("First")]
public class TestMe{
public string First {get; set;}
public string Second {get; set;}
}
Hope this helped :)
All you have to do is create a Custom Model Binder and override the SetProperty method to do the clean up.
public class CustomModelBinder: DefaultModelBinder
{
protected override void SetProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor, object value)
{
if (propertyDescriptor.Attributes.Contains(new Clean()) && propertyDescriptor.PropertyType == typeof(string))
{
value = value != null ? ((string)value).Replace("%", "") : value;
}
base.SetProperty(controllerContext, bindingContext, propertyDescriptor, value);
}
}
You can employ any of these options to use your custom model binder.
Registering the custom binder for a particular model in Global.asax.cs
ModelBinders.Binders.Add(typeof(MyModel), new CustomModelBinder());
Registering the custom binder in action parameter
public ActionResult Save([ModelBinder(typeof(CustomModelBinder))]MyModel myModel)
{
}
Registering the custom binder as the default model binder.
ModelBinders.Binders.DefaultBinder = new CustomModelBinder();

Custom Model Binder Not Validating Model

I started to play around with knockout.js and in doing so I used the FromJsonAttribute (created by Steve Sanderson). I ran into an issue with the custom attribute not performing model validation. I put together a simple example-- I know it looks like a lot of code-- but the basic issue is how to force the validation of the model within a custom model binder.
using System.ComponentModel.DataAnnotations;
namespace BindingExamples.Models
{
public class Widget
{
[Required]
public string Name { get; set; }
}
}
and here is my controller:
using System;
using System.Web.Mvc;
using BindingExamples.Models;
namespace BindingExamples.Controllers
{
public class WidgetController : Controller
{
public ActionResult Index()
{
return View();
}
[HttpPost]
public ActionResult Index(Widget w)
{
if(this.ModelState.IsValid)
{
TempData["message"] = String.Format("Thanks for inserting {0}", w.Name);
return RedirectToAction("Confirmation");
}
return View(w);
}
[HttpPost]
public ActionResult PostJson([koListEditor.FromJson] Widget w)
{
//the ModelState.IsValid even though the widget has an empty Name
if (this.ModelState.IsValid)
{
TempData["message"] = String.Format("Thanks for inserting {0}", w.Name);
return RedirectToAction("Confirmation");
}
return View(w);
}
public ActionResult Confirmation()
{
return View();
}
}
}
My issue is that the model is always valid in my PostJson method. For completeness here is the Sanderson code for the FromJson attribute:
using System.Web.Mvc;
using System.Web.Script.Serialization;
namespace koListEditor
{
public class FromJsonAttribute : CustomModelBinderAttribute
{
private readonly static JavaScriptSerializer serializer = new JavaScriptSerializer();
public override IModelBinder GetBinder()
{
return new JsonModelBinder();
}
private class JsonModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var stringified = controllerContext.HttpContext.Request[bindingContext.ModelName];
if (string.IsNullOrEmpty(stringified))
return null;
var model = serializer.Deserialize(stringified, bindingContext.ModelType);
return model;
}
}
}
}
Description
The FromJsonAttribute only binds to the model and does, like you said, no validation.
You can add validation to the FromJsonAttribute in order to validate the model's against his DataAnnotations attributes.
This can be done using the TypeDescriptor class.
TypeDescriptor Provides information about the characteristics for a component, such as its attributes, properties, and events.
Check out my solution. I have tested it.
Solution
private class JsonModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var stringified = controllerContext.HttpContext.Request[bindingContext.ModelName];
if (string.IsNullOrEmpty(stringified))
return null;
var model = serializer.Deserialize(stringified, bindingContext.ModelType);
// DataAnnotation Validation
var validationResult = from prop in TypeDescriptor.GetProperties(model).Cast<PropertyDescriptor>()
from attribute in prop.Attributes.OfType<ValidationAttribute>()
where !attribute.IsValid(prop.GetValue(model))
select new { Propertie = prop.Name, ErrorMessage = attribute.FormatErrorMessage(string.Empty) };
// Add the ValidationResult's to the ModelState
foreach (var validationResultItem in validationResult)
bindingContext.ModelState.AddModelError(validationResultItem.Propertie, validationResultItem.ErrorMessage);
return model;
}
}
More Information
TypeDescriptor Class
System.ComponentModel.DataAnnotations Namespace
Thank you, thank you, dknaack!! Your answer was exactly what I was looking for, except I want to validate after each property is bound b/c I have properties that are dependent on other properties, and I don't want to continue binding if a dependent property is invalid.
Here's my new BindProperty overload:
protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor){
// if this is a simple property, bind it and return
if(_simplePropertyKeys.ContainsKey(propertyDescriptor.Name)){
this.BindSimpleProperty(bindingContext, propertyDescriptor);
// if this is complex property, only bind it if we don't have an error already
} else if (bindingContext.ModelState.IsValid){
this.BindComplexProperty(bindingContext, propertyDescriptor);
}
// add errors from the data annotations
propertyDescriptor.Attributes.OfType<ValidationAttribute>()
.Where(a => a.IsValid(propertyDescriptor.GetValue(bindingContext.Model)) == false)
.ForEach(r => bindingContext.ModelState.AddModelError(propertyDescriptor.Name, r.ErrorMessage));
}
First of all, I'm only starting to learn ASP.NET so don't take my solution seriously. I found this article and as you, tried to do a custom model binder. There was no validation. Then i just replaced IModelBinder interface with DefaultModelBinder and voula, it works. Hope I could help someone

Forms Collection when submitting

What is the best practice for submitting forms in ASP.NET MVC? I have been doing code like this below, but I have a feeling there is a better way.
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult AddNewLink(FormCollection collection_)
{
string url = collection_["url"].ToString();
string description = collection_["description"].ToString();
string tagsString = collection_["tags"].ToString();
string[] tags = tagsString.Replace(" ","").Split(',');
linkRepository.AddLink(url, description, tags);
You can use the parameters directly; the parameters will automatically get parsed and casted to its correct type. The parameter names in the method must match the parameter names that are posted from your form.
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult AddNewLink(string url, string description, string tagsString)
{
string[] tags = tagsString.Replace(" ","").Split(',');
linkRepository.AddLink(url, description, tags);
}
This generally works on more complex objects as well, as long as its properties can be set, and as long as your form keys are in the format objectName.PropertyName. If you need anything more advanced, you should look into model binders.
public class MyObject
{
public int Id {get; set;}
public string Text {get; set;}
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult AddNewLink(MyObject obj)
{
string[] tags = obj.Text.Replace(" ","").Split(',');
linkRepository.AddLink(url, description, tags);
}
In my opinion, the Model Binder is cleaner. You can learn more at OdeToCode.com
Basically, You wrap your input from a FormCollection to a desirable model as well as validation.
public class LinkModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var link = new Link();
link.Url = GetValue<string>(bindingContext, "url");
// ... and so on for all properties
if (String.IsNullOrEmpty(url.Name))
{
bindingContext.ModelState.AddModelError("Url", "...");
}
return link;
}
private T GetValue<T>(ModelBindingContext bindingContext, string key)
{
ValueProviderResult valueResult;
bindingContext.ValueProvider.TryGetValue(key, out valueResult);
return (T)valueResult.ConvertTo(typeof(T));
}
}
In the controller
public ActionResult AddNewLink(Link link)

Resources