I would like to create model binding functionality so a user can enter ',' '.' etc for currency values which bind to a double value of my ViewModel.
I was able to do this in MVC 1.0 by creating a custom model binder, however since upgrading to MVC 2.0 this functionality no longer works.
Does anyone have any ideas or better solutions for performing this functionality? A better solution would be to use some data annotation or custom attribute.
public class MyViewModel
{
public double MyCurrencyValue { get; set; }
}
A preferred solution would be something like this...
public class MyViewModel
{
[CurrencyAttribute]
public double MyCurrencyValue { get; set; }
}
Below is my solution for model binding in MVC 1.0.
public class MyCustomModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
object result = null;
ValueProviderResult valueResult;
bindingContext.ValueProvider.TryGetValue(bindingContext.ModelName, out valueResult);
bindingContext.ModelState.SetModelValue(bindingContext.ModelName, valueResult);
if (bindingContext.ModelType == typeof(double))
{
string modelName = bindingContext.ModelName;
string attemptedValue = bindingContext.ValueProvider[modelName].AttemptedValue;
string wantedSeperator = NumberFormatInfo.CurrentInfo.NumberDecimalSeparator;
string alternateSeperator = (wantedSeperator == "," ? "." : ",");
try
{
result = double.Parse(attemptedValue, NumberStyles.Any);
}
catch (FormatException e)
{
bindingContext.ModelState.AddModelError(modelName, e);
}
}
else
{
result = base.BindModel(controllerContext, bindingContext);
}
return result;
}
}
You might try something among the lines:
// Just a marker attribute
public class CurrencyAttribute : Attribute
{
}
public class MyViewModel
{
[Currency]
public double MyCurrencyValue { get; set; }
}
public class CurrencyBinder : DefaultModelBinder
{
protected override object GetPropertyValue(
ControllerContext controllerContext,
ModelBindingContext bindingContext,
PropertyDescriptor propertyDescriptor,
IModelBinder propertyBinder)
{
var currencyAttribute = propertyDescriptor.Attributes[typeof(CurrencyAttribute)];
// Check if the property has the marker attribute
if (currencyAttribute != null)
{
// TODO: improve this to handle prefixes:
var attemptedValue = bindingContext.ValueProvider
.GetValue(propertyDescriptor.Name).AttemptedValue;
return SomeMagicMethodThatParsesTheAttemptedValue(attemtedValue);
}
return base.GetPropertyValue(
controllerContext,
bindingContext, propertyDescriptor,
propertyBinder
);
}
}
public class HomeController: Controller
{
[HttpPost]
public ActionResult Index([ModelBinder(typeof(CurrencyBinder))] MyViewModel model)
{
return View();
}
}
UPDATE:
Here's an improvement of the binder (see TODO section in previous code):
if (!string.IsNullOrEmpty(bindingContext.ModelName))
{
var attemptedValue = bindingContext.ValueProvider
.GetValue(bindingContext.ModelName).AttemptedValue;
return SomeMagicMethodThatParsesTheAttemptedValue(attemtedValue);
}
In order to handle collections you will need to register the binder in Application_Start as you will no longer be able to decorate the list with the ModelBinderAttribute:
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RegisterRoutes(RouteTable.Routes);
ModelBinders.Binders.Add(typeof(MyViewModel), new CurrencyBinder());
}
And then your action could look like this:
[HttpPost]
public ActionResult Index(IList<MyViewModel> model)
{
return View();
}
Summarizing the important part:
bindingContext.ValueProvider.GetValue(bindingContext.ModelName)
A further improvement step of this binder would be to handle validation (AddModelError/SetModelValue)
Related
How to alter the TViewModel from within a action filter or a model binder?
[HasPriviliege]
public IHttpActionResult Get(long id)
{
var entity = AutoMapper.Mapper.Map<TViewModel, TEntity>(model);
repo.Update(id, entity);
repo.Save();
return Ok(model);
}
[HasPriviliege]
public IHttpActionResult Edit(long id, TViewModel model)
{
var entity = AutoMapper.Mapper.Map<TViewModel, TEntity>(model);
repo.Update(id, entity);
repo.Save();
return Ok(model);
}
the filter should be
public class HasPriviliege:ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
if(getPrivileges()=="doctor"){
//the TViewModel(view model type to bind to) should be
// DoctorPatientViewModel should be;
}else{
//the TViewModel(view model type to bind to) should be
//ExaminationPatientViewModel
}
//base.OnActionExecuting(actionContext);
}
}
or alternativaly, the model binder
public class IPrivilegeableModelBinder: IModelBinder
{
public object BindModel(ControllerContext controllerContext,
ModelBindingContext bindingContext)
{
//return (hasPriviliege()?DoctorPatientViewModel:ExaminationPatientViewModel) ;
}
}
Rather than write an over-bloated comment, I'll post my suggestion on how we accomplished something similar to this using a generic controller.
Controller factory:
public class ControllerFactory : IControllerFactory
{
public IController CreateController(RequestContext requestContext, string controllerName)
{
Type controllerType = typeof(GenericController<>);
Type genericType = controllerType.MakeGenericType(GetPrivilegeType());
ConstructorInfo ctor = genericType.GetConstructor(new Type[]{});
return (IController)ctor.Invoke(new object[] { });
}
public SessionStateBehavior GetControllerSessionBehavior(RequestContext requestContext, string controllerName)
{
...
return SessionStateBehavior.ReadOnly;
}
public void ReleaseController(IController controller)
{
if (controller is IDisposable)
{
((IDisposable)controller).Dispose();
}
}
private string GetPrivilegeType()
{
if (getPrivileges() == "doctor") {
return typeof(DoctorPatientViewModel);
} else {
return typeof(ExaminationPatientViewModel);
}
}
}
Register it like this:
ControllerBuilder.Current.SetControllerFactory(new ControllerFactory());
...and finally what your controller might look like
public class GenericController<TViewModel> // TViewModel will be the privilege type from the factory
where TViewModel : IPrivilege
{
[HasPriviliege]
public IHttpActionResult Edit(long id, TViewModel model)
{
var entity = AutoMapper.Mapper.Map<TViewModel, TEntity>(model);
repo.Update(id, entity);
repo.Save();
return Ok(model);
}
}
That's the most basic example to get a generic controller working for mvc which might go some way to what you're trying to accomplish.
I have a custom modelbinder, its check the authentication cookie and return the value.
public class UserDataModelBinder<T> : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
if (controllerContext.RequestContext.HttpContext.Request.IsAuthenticated)
{
var cookie =
controllerContext.RequestContext.HttpContext.Request.Cookies[FormsAuthentication.FormsCookieName];
if (cookie == null)
return null;
var decrypted = FormsAuthentication.Decrypt(cookie.Value);
if (!string.IsNullOrWhiteSpace(decrypted.UserData))
return JsonSerializer.DeserializeFromString<T>(decrypted.UserData);
}
return null;
}
}
if I need to use it, I just need to pass it to the action. everything works.
public ActionResult Index(UserData userData)
{
AccountLoginWidgetVM model = new AccountLoginWidgetVM();
if (null != userData)
model.UserData = userData;
return View(userData);
}
However, I want to use it in my master page, because once user login, i want to display their info on the top on every page. I tried a few things, coudln't get it work
#Html.RenderPartial("LoginPartial", ???model here??)
We did it as follows:
Defined separate viewmodel for masterpages.
public class MasterPageViewModel
{
public Guid CurrentUserId { get; set; }
public string CurrentUserFullName { get; set; }
}
Added injection filter and filter provider.
public class MasterPageViewModelInjectorFilterProvider: IFilterProvider
{
public IEnumerable<Filter> GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
{
return new [] {new Filter(new MasterPageViewModelInjectorFilter(), FilterScope.Action, null), };
}
private class MasterPageViewModelInjectorFilter: IResultFilter
{
public void OnResultExecuting(ResultExecutingContext filterContext)
{
var viewResult = filterContext.Result as ViewResult;
if (viewResult == null)
return;
if (viewResult.ViewBag.MasterPageViewModel != null)
return;
//setup model whichever way you want
var viewModel = new MasterPageViewModel();
//inject model into ViewBag
viewResult.ViewBag.MasterPageViewModel = viewModel;
}
public void OnResultExecuted(ResultExecutedContext filterContext)
{
}
}
}
Configure filter provider:
//in Application_Start
FilterProviders.Providers.Add(new MasterPageViewModelInjectorFilterProvider());
Use in master:
ViewBag.MasterPageViewModel
This way you have fine uncoupled architecture. Of course you can combine it with Dependency Injection (we do, but I left it out for clarity) and configure your action filter for every action whichever way you want.
In this case you can use ViewBag.
public ActionResult Index(UserData userData)
{
AccountLoginWidgetVM model = new AccountLoginWidgetVM();
if (null != userData)
model.UserData = userData;
ViewBag.UserData = userData;
return View(userData);
}
#Html.RenderPartial("LoginPartial", ViewBag.UserData)
You have to make sure that userData is not null. If it'll be null the passed model will be default model of the view.
In my MVC 3 solution I want to have all Ids in querystring to be crypted. To decrypt URLs I inherited from DefaultModelBinder and overrided BindProperty method:
public class CryptedIdBinder : DefaultModelBinder
{
protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, System.ComponentModel.PropertyDescriptor propertyDescriptor)
{
if (propertyDescriptor.Name.ToLower() == "id")
{
propertyDescriptor.SetValue(bindingContext.Model, CryptoHelper.Decrypt(controllerContext.HttpContext.Request.Form["id"]));
return;
}
base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
return;
}
After that I set new DefaultBinder in global.asax on Application_Start:
System.Web.Mvc.ModelBinders.Binders.DefaultBinder = new CryptedIdBinder();
I didn't inherit from IModelBinder because I want to change binding logic only for id fields in solution.
The issue is that BindProperty method is never called. What am I doning wrong?
PS. In order to be sure that I call at least BindModel method I added a peace of this code inside my custom binder, and it was hit by the debugger:
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
return base.BindModel(controllerContext, bindingContext);
}
If your models don't have Id properties of course the BindProperty won't be called. Because it called on the model properties. If I understood your question what you need is to transform each Id named query string parameter. In this case you need a custom value provider instead of a modelbinder. This is good article about the value providers. And it's quite easy to write one:
public class MyValueProviderFacotry : ValueProviderFactory
{
public override IValueProvider GetValueProvider(ControllerContext controllerContext)
{
return new MyValueProvider(controllerContext);
}
}
public class MyValueProvider : IValueProvider
{
private ControllerContext controllerContext;
public MyValueProvider(ControllerContext controllerContext)
{
this.controllerContext = controllerContext;
}
public bool ContainsPrefix(string prefix)
{
return true;
}
public ValueProviderResult GetValue(string key)
{
if (key.ToLower() == "id")
{
var originalValue = controllerContext.HttpContext.Request.QueryString[key];
var transformedValue = CryptoHelper.Decrypt(orignalValue );
var result = new ValueProviderResult(transformedValue,originalValue,CultureInfo.CurrentCulture);
return result;
}
return null;
}
}
In global.asax:
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
ValueProviderFactories.Factories.Insert(4, new MyValueProviderFacotry()); //Its need to be inserted before the QueryStringValueProviderFactory
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
}
I want to be able to grab keys/values from a cookie and use that to bind a model.
Rather than building a custom ModelBinder, I believe that the DefaultModelBinder works well out of the box, and the best way to choose where the values come from would be to set the IValueProvider that it uses.
To do this I don't want to create a custom ValueProviderFactory and bind it globally, because I only want this ValueProvider to be used in a specific action method.
I've built an attribute that does this:
/// <summary>
/// Replaces the current value provider with the specified value provider
/// </summary>
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = true, AllowMultiple = true)]
public class SetValueProviderAttribute : ActionFilterAttribute
{
public SetValueProviderAttribute(Type valueProviderType)
{
if (valueProviderType.GetInterface(typeof(IValueProvider).Name) == null)
throw new ArgumentException("Type " + valueProviderType + " must implement interface IValueProvider.", "valueProviderType");
_ValueProviderType = valueProviderType;
}
private Type _ValueProviderType;
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
IValueProvider valueProviderToAdd = GetValueProviderToAdd();
filterContext.Controller.ValueProvider = valueProviderToAdd;
}
private IValueProvider GetValueProviderToAdd()
{
return (IValueProvider)Activator.CreateInstance(_ValueProviderType);
}
}
Unfortunately, the ModelBinder and its IValueProvider are set BEFORE OnActionExecuting (why?????). Has anyone else figured out a way to inject a custom IValueProvider into the DefaultModelBinder without using the ValueProviderFactory?
You should still use a ValueProviderFactory in this case.
The method that you have to implement on your ValueProviderFactory has this signature:
IValueProvider GetValueProvider(ControllerContext controllerContext)
Within your implementation of that method you can inspect the controller context, and if the incoming request is for the controller/action that you want to leverage cookies on, return some CustomCookieValueProvider.
If you don't want to leverage cookies for the request, just return null and the framework will filter that out of from the list of Value Providers.
As a bonus, you might not want to hard code the logic for when to use the CustomCookieValueProvider into the ValueProviderFactory. You could, perhaps, leverage DataTokens to match when to use cookies with given routes. So add a route like this:
routes.MapRoute("SomeRoute","{controller}/{action}").DataTokens.Add("UseCookies", true);
Notice the DataTokens.Add() call in there, now inside you GetValueProvider method you could do something like this:
if (controllerContext.RouteData.DataTokens.ContainsKey("UseCookies"))
{
return new CustomCookieValueProvider(controllerContext.RequestContext.HttpContext.Request.Cookies);
}
return null;
Here is an alternative that lets you specify IValueProviders as attributes against an actions parameters.
This makes the IValueProviders transient and not Global.
public interface IControllerContextAware
{
ControllerContext ControllerContext { get; set; }
}
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum | AttributeTargets.Interface | AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)]
public class ValueProviderAttribute : CustomModelBinderAttribute
{
public Type[] ValueProviders { get; private set; }
public ValueProviderAttribute(params Type[] valueProviders)
{
if (valueProviders == null)
{
throw new ArgumentNullException("valueProviders");
}
foreach (var valueProvider in valueProviders.Where(valueProvider => !typeof(IValueProvider).IsAssignableFrom(valueProvider)))
{
throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, "The valueProvider {0} must be of type {1}", valueProvider.FullName, typeof(IValueProvider)), "valueProviders");
}
ValueProviders = valueProviders;
}
public override IModelBinder GetBinder()
{
return new ValueProviderModelBinder
{
ValueProviderTypes = ValueProviders.ToList(),
CreateValueProvider = OnCreateValueProvider
};
}
protected virtual IValueProvider OnCreateValueProvider(Type valueProviderType, ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var valueProvider = (IValueProvider)Activator.CreateInstance(valueProviderType);
if (valueProvider is IControllerContextAware)
{
(valueProvider as IControllerContextAware).ControllerContext = controllerContext;
}
return valueProvider;
}
private class ValueProviderModelBinder : DefaultModelBinder
{
public IList<Type> ValueProviderTypes { get; set; }
public Func<Type, ControllerContext, ModelBindingContext, IValueProvider> CreateValueProvider { get; set; }
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var valueProviders = from type in ValueProviderTypes
select CreateValueProvider(type, controllerContext, bindingContext);
bindingContext.ValueProvider = new ValueProviderCollection(valueProviders.Concat((Collection<IValueProvider>)bindingContext.ValueProvider).ToList());
return base.BindModel(controllerContext, bindingContext);
}
}
}
This is basically the code form the ModelBinderAttribute, but with a few tweaks.
It isn't sealed and so you can alter the way in which the IValueProviders are created if need be.
Here is a simple example which looks in another field, possibly a hidden or encrypted field, and takes the data and puts it into another property.
Here is the model, which has no knowledge of the IValueProvider, but does know about the hidden field.
public class SomeModel
{
[Required]
public string MyString { get; set; }
[Required]
public string MyOtherString { get; set; }
[Required]
public string Data { get; set; }
}
THen we have the IValueProvider, in this case, my provider knows explicitly about my model, but this doesn't have to be the case.
public class MyValueProvider : IValueProvider, IControllerContextAware
{
public ControllerContext ControllerContext { get; set; }
public bool ContainsPrefix(string prefix)
{
var containsPrefix = prefix == "MyString" && ControllerContext.HttpContext.Request.Params.AllKeys.Any(key => key == "Data");
return containsPrefix;
}
public ValueProviderResult GetValue(string key)
{
if (key == "MyString")
{
var data = ControllerContext.RequestContext.HttpContext.Request.Params["Data"];
var myString = data.Split(':')[1];
return new ValueProviderResult(myString, myString, CultureInfo.CurrentCulture);
}
return null;
}
}
and then the action that ties all this together:
[HttpGet]
public ActionResult Test()
{
return View(new SomeModel());
}
[HttpPost]
public ActionResult Test([ValueProvider(typeof(MyValueProvider))]SomeModel model)
{
return View(model);
}
Figured out how to do this. First, create a custom model binder that takes a value provider type in the constructor - but inherits from default modelbinder. This allows you to use standard model binding with a custom value provider:
/// <summary>
/// Uses default model binding, but sets the value provider it uses
/// </summary>
public class SetValueProviderDefaultModelBinder : DefaultModelBinder
{
private Type _ValueProviderType;
public SetValueProviderDefaultModelBinder(Type valueProviderType)
{
if (valueProviderType.GetInterface(typeof(IValueProvider).Name) == null)
throw new ArgumentException("Type " + valueProviderType + " must implement interface IValueProvider.", "valueProviderType");
_ValueProviderType = valueProviderType;
}
/// <summary>
/// Before binding the model, set the IValueProvider it uses
/// </summary>
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
bindingContext.ValueProvider = GetValueProvider();
return base.BindModel(controllerContext, bindingContext);
}
private IValueProvider GetValueProvider()
{
return (IValueProvider)Activator.CreateInstance(_ValueProviderType);
}
}
Then we create a model binding attribute that will inject the value provider type in the custom model binder created above, and use that as the model binder:
/// <summary>
/// On the default model binder, replaces the current value provider with the specified value provider. Cannot use custom model binder with this.
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Class | AttributeTargets.Enum | AttributeTargets.Interface | AttributeTargets.Parameter | AttributeTargets.Struct, AllowMultiple = false, Inherited = false)]
public class SetValueProviderAttribute : CustomModelBinderAttribute
{
// Originally, this was an action filter, that OnActionExecuting, set the controller's IValueProvider, expecting it to be picked up by the default model binder
// when binding the model. Unfortunately, OnActionExecuting occurs AFTER the IValueProvider is set on the DefaultModelBinder. The only way around this is
// to create a custom model binder that inherits from DefaultModelBinder, and in its BindModel method set the ValueProvider and then do the standard model binding.
public SetValueProviderAttribute(Type valueProviderType)
{
if (valueProviderType.GetInterface(typeof(IValueProvider).Name) == null)
throw new ArgumentException("Type " + valueProviderType + " must implement interface IValueProvider.", "valueProviderType");
_ValueProviderType = valueProviderType;
}
private Type _ValueProviderType;
public override IModelBinder GetBinder()
{
var modelBinder = new SetValueProviderDefaultModelBinder(_ValueProviderType);
return modelBinder;
}
}
What's the best way to bind ExtJs 4 Grid filter info to asp.net mvc action parameters?
I wrote these helper classes:
public class ExtFilterInfo
{
public string Field { get; set; }
public ExtFilterData Data { get; set; }
}
public class ExtFilterData
{
public string Type { get; set; }
public string Value { get; set; }
}
Here is the Action:
public ActionResult Grid(int start, int limit, string sort, ExtFilterInfo[] filter)
The QueryString looks something like this:
_dc:1306799668564
filter%5B0%5D%5Bfield%5D:Nome
filter%5B0%5D%5Bdata%5D%5Btype%5D:string
filter%5B0%5D%5Bdata%5D%5Bvalue%5D:nu
page:1
start:0
limit:20
A custom model binder looks like it could fit the bill:
public class ExtFilterInfoModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var filter = (ExtFilterInfo)base.BindModel(controllerContext, bindingContext);
var field = bindingContext.ValueProvider.GetValue(bindingContext.ModelName + "[field]");
if (field != null)
{
filter.Field = field.AttemptedValue;
}
var type = bindingContext.ValueProvider.GetValue(bindingContext.ModelName + "[data][type]");
if (type != null)
{
if (filter.Data == null)
{
filter.Data = new ExtFilterData();
}
filter.Data.Type = type.AttemptedValue;
}
var value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName + "[data][value]");
if (value != null)
{
if (filter.Data == null)
{
filter.Data = new ExtFilterData();
}
filter.Data.Value = value.AttemptedValue;
}
return filter;
}
}
which could be registered in Application_Start:
ModelBinders.Binders.Add(typeof(ExtFilterInfo), new ExtFilterInfoModelBinder());
and now the filter collection which your controller action takes as argument should be bound correctly.