Jalal
Jalal

Reputation: 6836

How to change form values after post in ASP.NET MVC?

I want to change form value before pass it to action of controller. but it throws Collection is read-only.

public class PersonController : Controller
{
    public ActionResult Add()
    {
        return View();
    }

    [HttpPost]
    [PersianDateConvertor("birthday")]
    public ActionResult Add(FormCollection collection)
    {
        string firstName = collection["firstName"];
        string lastName = collection["lastName"];
        string birthday = collection["birthday"];

        return View();
    }
}
public class PersianDateConvertorAttribute : ActionFilterAttribute
{
    string[] fields;
    public PersianDateConvertorAttribute(params string[] persianDateFieldNames)
    {
        if (persianDateFieldNames == null)
            fields = new string[] { };
        else
            fields = persianDateFieldNames;
    }

    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        foreach (var field in fields)
        {
            string value = filterContext.HttpContext.Request.Form[field];
            filterContext.HttpContext.Request.Form.Remove(field); //throws Collection is read-only
            filterContext.HttpContext.Request.Form.Add(field, ConvertToGregorian(value));
            // or filterContext.HttpContext.Request.Form[field] = ConvertToGregorian(value);
        }

        base.OnActionExecuting(filterContext);
    }
}

Upvotes: 2

Views: 3706

Answers (1)

Iridio
Iridio

Reputation: 9271

If I understand correctly, you want to modify the behaviour of the DateTime during the binding process. Instead of using an attribute, I would use a ModelBinder to change the format of the date string.

I did something similar for a problem while converting decimal values from multiple cultures: (the code is taken from a blog post, it's not mine, but I don't remember the source. Sorry)

using System;
using System.Globalization;
using System.Web.Mvc;

public class DecimalModelBinder : IModelBinder
{
  public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
  {
    ValueProviderResult valueResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
    ModelState modelState = new ModelState { Value = valueResult };
    object actualValue = null;
    try
    {
      actualValue = Convert.ToDecimal(valueResult.AttemptedValue, CultureInfo.CurrentCulture);
    }
    catch (FormatException e)
    {
      modelState.Errors.Add(e);
    }
    bindingContext.ModelState.Add(bindingContext.ModelName, modelState);
    return actualValue;
  }
}

in global.asax you register the binder

protected void Application_Start()
{
  AreaRegistration.RegisterAllAreas();
  RegisterGlobalFilters(GlobalFilters.Filters);
  RegisterRoutes(RouteTable.Routes);

  ModelBinders.Binders.Add(typeof(decimal), new DecimalModelBinder());

}

Imo this is a better approach and you don't have to put an attribute to every action

Upvotes: 4

Related Questions