views:

193

answers:

2

I want to write a modelbinder for ASP.NET MVC that will correct values that will be visible to the user. Maybe it will capitalize the initial letter of a value, trim strings, etc. etc.

I'd like to encapsulate this behavior within a modelbinder.

For instance here is a TrimModelBinder to trim strings. (taken from here)

public class TrimModelBinder : DefaultModelBinder
  {
    protected override void SetProperty(ControllerContext controllerContext, 
      ModelBindingContext bindingContext, 
      System.ComponentModel.PropertyDescriptor propertyDescriptor, object value)
    {
      if (propertyDescriptor.PropertyType == typeof(string))
      {
        var stringValue = (string)value;
        if (!string.IsNullOrEmpty(stringValue))
          stringValue = stringValue.Trim();

        value = stringValue;
      }

      base.SetProperty(controllerContext, bindingContext, 
                          propertyDescriptor, value);
    }
  }

This will set the values into the model, but when the page is redisplayed the original values will persist (because they're in ModelState).

I'd like to just re-display the trimmed values to the user.

Theres a whole lot of methods to override - like OnPropertyValidated, and OnPropertyValidating etc.

I could probably get it to work, but I don't want to have some unintended side effect if I override the wrong method.

I'd rather not try to do a Trim() or whatever the logic is when I'm generating the view. I want to encapsulate this logic completely within a modelbinder.

+1  A: 

From within the model binder you have access to the ModelBindingContext. On that you have access to ModelState and you should be able to modify the values in that dictionary to be the trimmed values.

Maybe something like:

string trimmedValue = GetTheTrimmedValueSomehow();
modelBindingContext.ModelState[modelBindingContext.ModelName] = trimmedValue;
Eilon
any thought on what about which event I should do this in - or how i need to handle the 'AttemptedValue' and 'RawValue' properties of ModelState.Value ?
Simon_Weaver
I suggest doing it from within the model binder itself. For string values the AttemptedValue and RawValue are the same. I think that AttemptedValue is often a string representation of RawValue, such as a string containing comma-separated list of array values, whereas RawValue would be an actual array of individual values.
Eilon
@eilon ya i wish they'd come up with better names - like StringRepresentation and ParsedValue or something like that
Simon_Weaver
+1  A: 

Replace this class.

  public class TrimModelBinder : DefaultModelBinder
  {
    protected override void SetProperty(ControllerContext controllerContext,
      ModelBindingContext bindingContext,
      System.ComponentModel.PropertyDescriptor propertyDescriptor, object value)
    {
      if (propertyDescriptor.PropertyType == typeof(string))
      {
        var stringValue = (string)value;
        if (!string.IsNullOrEmpty(stringValue))
          stringValue = stringValue.Trim();

        value = stringValue;
        bindingContext.ModelState[propertyDescriptor.Name].Value = 
          new ValueProviderResult(stringValue,
            stringValue,
            bindingContext.ModelState[propertyDescriptor.Name].Value.Culture);
      }

      base.SetProperty(controllerContext, bindingContext,
                propertyDescriptor, value);
    }
  }

Edit: modified by simon

(original had null reference exception and added change to support hierarchical model)

protected override void SetProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, System.ComponentModel.PropertyDescriptor propertyDescriptor, object value) { string modelStateName = string.IsNullOrEmpty(bindingContext.ModelName) ? propertyDescriptor.Name : bindingContext.ModelName + "." + propertyDescriptor.Name;

        // only process strings
        if (propertyDescriptor.PropertyType == typeof(string))
        {
            if (bindingContext.ModelState[modelStateName] != null)
            {
                // modelstate already exists so overwrite it with our trimmed value
                var stringValue = (string)value;
                if (!string.IsNullOrEmpty(stringValue))
                    stringValue = stringValue.Trim();

                value = stringValue;
                bindingContext.ModelState[modelStateName].Value =
                  new ValueProviderResult(stringValue,
                    stringValue,
                    bindingContext.ModelState[modelStateName].Value.Culture);
            }
            else
            {
                // trim and pass to default model binder
                base.SetProperty(controllerContext, bindingContext, propertyDescriptor, (value == null) ? null : (value as string).Trim());
            }
        }
        else
        {
            base.SetProperty(controllerContext, bindingContext, propertyDescriptor, value);
        }
    }
takepara
this has a couple issues - i've added to the end of it my changes and given you the points
Simon_Weaver
Thanks for change code, simon!
takepara