user4614918
user4614918

Reputation:

Should model binder providers be considered as service locators?

I'd like to provide some custom model binding for my queryable web api, namely for the following interfaces:

Depending on the parameter specified on the controller action, I want to provide the correct model binder.

public void Test([ModelBinding] IPagingOptions<MyEntity, int> pagingOptions)
   // -> PagingOptionsModelBinder

My question is: Should there be a model binder provider for each model binder or should the model binder work as service locator and provide a model binder for each type to be bound?

e.g..

public QueryOptionsModelBinderProvider : ModelBinderProvider {

   public override IModelBinder GetBinder(HttpConf...
      if(modelType.IsOfGeneric((typeof(ISortingOptions<,>))
         return new SortingOptionsModelBinder();
      if(modelType.IsOfGeneric((typeof(IPagingOptions<,>))
         return new PagingOptionsModelBinder();
   ...

or...

public SortingOptionsModelBinderProvider

public PagingOptionsModelBinderProvider

// etc.

I'm asking this because on one hand, service locating is a (horrible, IMHO) anti pattern since for every new type to be bound, I need to alter the model binder provider, but on the other if it isn't being done here, the responsibility of finding the right provider is just delegated to ASP.NET WebApi, where I need to register a new provider everytime.

Upvotes: 1

Views: 525

Answers (1)

jlvaquero
jlvaquero

Reputation: 8785

A clever way I saw once (don't remember where, when I find it I will give its credit) is create a generic model binder:

public class EntityModelBinder<TEntity> 
    : IModelBinder
    where TEntity : Entity
{
    private readonly IRepository<TEntity> _repository;

    public EntityModelBinder(IRepository<TEntity> repository)
    {
        _repository = repository;
    }

    public object BindModel(ControllerContext controllerContext, 
        ModelBindingContext bindingContext)
    {
        ValueProviderResult value = bindingContext
            .ValueProvider.GetValue(bindingContext.ModelName);

        var id = Guid.Parse(value.AttemptedValue);

        var entity = _repository.GetById(id);

        return entity;
    }
}

Then use reflection in the provider:

public class EntityModelBinderProvider
    : IModelBinderProvider
{
    public IModelBinder GetBinder(Type modelType)
    {
        if (!typeof(Entity).IsAssignableFrom(modelType))
            return null;

        Type modelBinderType = typeof(EntityModelBinder<>)
            .MakeGenericType(modelType);

        var modelBinder = ObjectFactory.GetInstance(modelBinderType);

        return (IModelBinder) modelBinder;
    }
}

After that you just have to register one provider that never changes.

Upvotes: 2

Related Questions