Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

A Custom ASP.NET MVC Model Binder for Repositories

DZone's Guide to

A Custom ASP.NET MVC Model Binder for Repositories

·
Free Resource

How do you take the values posted by an HTML form and turn them into a populated domain entity? One popular technique is to bind the POST values to a view-model and then map the view-model values to an entity. Since your action method’s argument is the view-model, it allows you to decide in the controller code if the view-model is a new entity or an existing one that should be retrieved from the database. If the view-model represents a new entity you can directly create the entity from the view-model values and then call your repository in order to save it.  In the update case, you can directly call your repository to get a specific entity and then update the entity from the values in the view-model.

However, this method is somewhat tedious for simple cases. Is a view-model always necessary? Wouldn’t it be simpler to have a model binder that simply created the entity for you directly? Here’s my attempt at such a binder:

public class EntityModelBinder : DefaultModelBinder, IAcceptsAttribute
{
readonly IRepositoryResolver repositoryResolver;
EntityBindAttribute declaringAttribute;

public EntityModelBinder(IRepositoryResolver repositoryResolver)
{
this.repositoryResolver = repositoryResolver;
}

protected override object CreateModel(
ControllerContext controllerContext,
ModelBindingContext bindingContext,
Type modelType)
{
if (modelType.IsEntity() && FetchFromRepository)
{
var id = GetIdFromValueProvider(bindingContext.ValueProvider, modelType);
if (id != 0)
{
var repository = repositoryResolver.GetRepository(modelType);
object entity;
try
{
entity = repository.GetById(id);
}
finally
{
repositoryResolver.Release(repository);
}
return entity;
}
}

// Fall back to default model creation if the target is not an existing entity
return base.CreateModel(controllerContext, bindingContext, modelType);
}

private static int GetIdFromValueProvider(IValueProvider valueProvider, Type modelType)
{
var result = valueProvider.GetValue(modelType.GetPrimaryKey().Name);
return (result == null) ? 0 : (int)result.ConvertTo(typeof(Int32));
}

public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var model = base.BindModel(controllerContext, bindingContext);
ValidateEntity(bindingContext, controllerContext, model);
return model;
}

protected virtual void ValidateEntity(
ModelBindingContext bindingContext,
ControllerContext controllerContext,
object entity)
{
// override to provide additional validation.
}

private bool FetchFromRepository
{
get
{
// by default we always fetch any model that implements IEntity
return declaringAttribute == null ? true : declaringAttribute.Fetch;
}
}

public virtual void Accept(Attribute attribute)
{
declaringAttribute = (EntityBindAttribute)attribute;
}

// For unit tests
public void SetModelBinderDictionary(ModelBinderDictionary modelBinderDictionary)
{
Binders = modelBinderDictionary;
}
}

I’ve simply inherited ASP.NET MVC’s DefaultModelBinder and overriden the CreateModel method. This allows me to check if the type being bound is one of my entities and then grabs its repository and gets it from the database if it is.

Now, I’m most definitely not doing correct Domain Driven Development here despite my use of terms like ‘entity’ and ‘repository’. It’s generally frowned on to have table-row like settable properties and generic repositories. If you want to do DDD, you are much better off only binding view-models to your views.

 

Topics:

Published at DZone with permission of Mike Hadlow, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

THE DZONE NEWSLETTER

Dev Resources & Solutions Straight to Your Inbox

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.

X

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}