Augmenting the [AutoLoad] — Logical Parameter Names

A few days ago I blogged about my custom model binder attribute for model binding to entities managed by a persistence framework. I also mentioned, there were a number of ways it could be improved. And since then, I’ve had even more ideas.

The most fundamental issue was the parameter names — I can’t bind to a parameter called “id” just because that is the key of a key-value pair supplied by the request. This coupling has to be destroyed.Separating Model Names and Request Names

At the moment, when model binding, the key for a value sent in the request, must match the name of the parameter that the model binder works on. But this isn’t good. Can you see why?

public ViewResult Details([AutoLoad]HealthFood id)

{

return View(id);

}

Yes, I know you can — the name id doesn’t make sense and it makes me feel insecure about my employment status. Fortunately, we can keep the attribute concise, and have flexibility with parameter names. It’s a two-step process:

  1. Tell the model binder what key to look for and ignore the name of the parameter

  2. Add an option to manually specify the key

So the current key is the ModelName — the name of the parameter:

var valueKey = bindingContext.ModelName;

What I’m going to do is step in here and say, the default is now “id” unless I tell you otherwise. The updated code looks like so:

private const string DefaultValueKey = “id”;

private string ValueKey { get; set; }

public AutoLoadModelBinder(string valueKey = DefaultValueKey)

{

ValueKey = valueKey;

}

public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)

{

var typeBeingBoundTo = bindingContext.ModelType;

var rawValue = bindingContext.ValueProvider.GetValue(ValueKey);

Have a quick glance at the constant “DefaultValueKey” with the value id. If no value is specified as the constructor’s optional parameter, then this is the key that the value provider will be queried for. And obviously, to override, you just pass your desired key into the constructor.Keeping the AutoLoad Attrbute in Sync

We’ve allowed the binder to specify a key, but the key will need to be propagated down from the attribute. Again, that’s really trivial and it looks like so:

public class AutoLoadAttribute : CustomModelBinderAttribute

{

private readonly string _valueKey;

public AutoLoadAttribute(string valueKey)

{

_valueKey = valueKey;

}

public AutoLoadAttribute()

{

}

public override IModelBinder GetBinder()

{

return !string.IsNullOrEmpty(_valueKey)

? new AutoLoadModelBinder(_valueKey)

: new AutoLoadModelBinder();

}

}

Attributes and default parameters don’t like to play nicely, so we have to have an overload for the constructor. But the rest is pretty self explanatory.

If I now go back to my details action, and just change the name of the parameter:

public ViewResult Details([AutoLoad]HealthFood healthFood)

{

return View(healthFood);

}

Not only does the code make sense, but it actually still works. Fantastico.

The assumption here is that the form posts the id for this HealthFood using the name “id” — the default value in the model binder. But if it wasn’t and it was “healthFoodId”, well I could just do this:

public ViewResult Details([AutoLoad(“healthFoodId”)]HealthFood healthFood)Why is This Useful?

Good question. Let’s say you are creating an object, like HealthFood, that has a many-to-many association with “HealthBenefit”. You might want to create a new instance of HealthFood, and model bind to a HealthBenefit, using the name “healthBenefitId”, which you can then associate in the controller directly, rather than via a repository query — bleugh!.Conclusion

After a successful proof-of-concept in the last post for elegant, opt-int model-binding to persistent entities, work needs to be done to tidy up and add significant value to the [AutoLoad] custom model binder attribute. In this blog post I removed the coupling between the parameter name and the key of the value in the request being bound to.

I’ve still managed to dodge the bullet of making this tool ORM-agnostic and I am to still to make it work on Http Post requests. Let’s not forget, we’d like to model bind to collections — on their own, or as child entities of other domain entities.

Plenty more to come then.

public class AutoLoadModelBinder : DefaultModelBinder

{

private const string DefaultValueKey = “id”;

private string ValueKey { get; set; }

public AutoLoadModelBinder(string valueKey = DefaultValueKey)

{

ValueKey = valueKey;

}

public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)

{

var typeBeingBoundTo = bindingContext.ModelType;

var rawValue = bindingContext.ValueProvider.GetValue(ValueKey);

var entityId = Convert.ToInt32(rawValue.RawValue);

//TODO — tied to implementation details — I know

var dataContext = new AutoLoadModelBinderExampleContext();

var allItemsOfType = dataContext.Set(typeBeingBoundTo);

var entityBeingBoundTo = allItemsOfType.Find(new object[] {entityId});

return entityBeingBoundTo;

}

}

public class AutoLoadAttribute : CustomModelBinderAttribute

{

private readonly string _valueKey;

public AutoLoadAttribute(string valueKey)

{

_valueKey = valueKey;

}

public AutoLoadAttribute()

{

}

public override IModelBinder GetBinder()

{

return !string.IsNullOrEmpty(_valueKey)

? new AutoLoadModelBinder(_valueKey)

: new AutoLoadModelBinder();

}

}