views:

500

answers:

2

I have a custom object called S2kBool that can be converted to and from a regular Boolean object. Basically, it allows my application to treat boolean values in my legacy database the same way it treats C# booleans. Then problem is, when I attempt to use a check box to set the value of an S2kBool property, it fails.

Code like this works:

public class MyClass {
    public S2kBool MyProperty { get; set; }
}

MyClassInstance.MyProperty = true;

But it's almost like UpdateModel is expecting an actual bool type, rather than an object that can be converted to a bool. I can't really tell, however, since the exception thrown is so vague:

The model was not successfully updated.

How can I get around this? Do I need a custom ModelBinder?

Thanks!

+2  A: 

You could have an additional bool property of type bool that when set changes the value of your S2kBool property.

public class MyClass {
    public S2kBool MyProperty { get; set; }
    public bool MyPropertyBool {
        get
        {
            return (bool)MyProperty;
        }
        set
        {
            MyProperty = value;
        }
    }
}

You then just have the MyPropertyBool in your html form and the modelbinder won't freak out about it's type.

I use this technique for properties like Password & HashedPassword where Password is the property from the html form that the ModelBinder binds to and in the Password's setter it sets HashedPassword to the hash of it which is then persisted to the database or what ever.

Charlino
+2  A: 

While Charlino's solution is clever and will work, I personally wouldn't like the idea of "dirtying" up my domain entities with an extra property just for this purpose. I think you had the answer up top already: a custom modelbinder. Something like:

public class S2kBoolAttribute : CustomModelBinderAttribute, IModelBinder
{
 public override IModelBinder GetBinder()
 {
  return this;
 }

 public object BindModel( ControllerContext controllerContext, ModelBindingContext bindingContext )
 {
  ValueProviderResult result;
  return bindingContext.ValueProvider.TryGetValue( bindingContext.ModelName, out result )
   ? (S2kBool)result.ConvertTo( typeof( bool ) )
   : null;
 }
}

And then you can modify your controller action to look like:

public ActionResult Foo( [S2kBool]S2kBool myProperty ){
    myClassInstance.MyProperty = myProperty;
    SaveToLegacyDb(myClassInstance);
    return RedirectToAction("Bar");
}

If you put a bit more work into the modelbinder you could get it to work with the binder being globally registered - but the implementation I gave you above should work for cherry-picking values out when needed.

Troy
Presuming you never want to bind a property of type S2kBool with any other model binder, I would add the custom binder to the collection in the global.asax application start event for that type, rather than using the binding attributes in the action itself.
Craig Stuntz
By adding the custom model binder in global.asax, will it be applied to all S2kBool properties automatically? For example, in the code I gave above, if I was attempting to bind data to MyClass, would my model binder apply to MyClass.MyProperty, or will it only work on method arguments?
David Brown