Reputation: 2445
We are trying to sanitize posted string entries in an ASP.NET MVC web application in a "global" way. With my current attempt, I wrote a custom action filter and decorated a post action with my class FormPostSanitizer. The idea being that we'll decorate all post actions that should be sanitized.
The method successfully captures and sanitizes the inputs. But those sanitized values aren't being fixed to the model before it gets saved to the database.
Here is how it decorates the controller.
[HttpPost]
[ValidateAntiForgeryToken]
[FormPostSanitizer]
public ActionResult MyAction(MyViewModel model)
{
// If ModelState.IsValid, save the model ...
}
Here is my action filter.
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (filterContext.HttpContext.Request.HttpMethod != "POST") return;
FormCollection formCollection = new FormCollection(filterContext.Controller.ControllerContext.HttpContext.Request.Form);
foreach (string key in formCollection.AllKeys)
{
string sanitized = formCollection[key].SanitizeString(); // Extension method to alter the string.
formCollection.Set(key, sanitized);
}
filterContext.ActionParameters["form"] = formCollection;
}
My expectation was the last line would commit the changed values to the filterContext, and the model would have the sanitized values. The values get sanitized, but they aren't getting applied to the model.
If there is a better way in the compiled code to intercept and alter the posted values before they are bound to the model, then please point me to a post that shows the methodology. Thanks for your help.
Upvotes: 1
Views: 637
Reputation: 4456
You have to use a custom model binder like Alexandar explained above as the action filter attribute is executed after the model is bound already and you cannot change it at this point. If you do it in the Model Binder then it will be applied to the Model as you would expect
Upvotes: 1
Reputation: 9632
You can create custom model binder for this purpose
public class SanitizeModelBinder : DefaultModelBinder
{
protected override object GetPropertyValue(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor, IModelBinder propertyBinder)
{
bool sanitize = controllerContext.HttpContext.Request.HttpMethod == "POST";
//get value from default binder
object value = base.GetPropertyValue(controllerContext, bindingContext, propertyDescriptor, propertyBinder);
if (!sanitize)
{
return value;
}
//sanitize value if it is a string
string stringValue = value as string;
if (stringValue != null)
{
return stringValue.SanitizeString();
}
return value;
}
}
Set default binder in Global.asax.cs
to use it for every action
System.Web.Mvc.ModelBinders.Binders.DefaultBinder = new SanitizeModelBinder();
or if you want to use this binder for certain models
public ActionResult MyAction([ModelBinder(typeof(SanitizeModelBinder))]MyViewModel model)
Upvotes: 1