Use custom ASP.NET MVC IValueProvider, without setting it globally?

Oved D picture Oved D · Sep 29, 2011 · Viewed 7.9k times · Source

I want to be able to grab keys/values from a cookie and use that to bind a model.

Rather than building a custom ModelBinder, I believe that the DefaultModelBinder works well out of the box, and the best way to choose where the values come from would be to set the IValueProvider that it uses.

To do this I don't want to create a custom ValueProviderFactory and bind it globally, because I only want this ValueProvider to be used in a specific action method.

I've built an attribute that does this:

/// <summary>
/// Replaces the current value provider with the specified value provider
/// </summary>
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = true, AllowMultiple = true)]
public class SetValueProviderAttribute : ActionFilterAttribute
{
    public SetValueProviderAttribute(Type valueProviderType)
    {
        if (valueProviderType.GetInterface(typeof(IValueProvider).Name) == null)
            throw new ArgumentException("Type " + valueProviderType + " must implement interface IValueProvider.", "valueProviderType");

        _ValueProviderType = valueProviderType;
    }

    private Type _ValueProviderType;

    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        IValueProvider valueProviderToAdd = GetValueProviderToAdd();

        filterContext.Controller.ValueProvider = valueProviderToAdd;
    }

    private IValueProvider GetValueProviderToAdd()
    {
        return (IValueProvider)Activator.CreateInstance(_ValueProviderType);
    }
}

Unfortunately, the ModelBinder and its IValueProvider are set BEFORE OnActionExecuting (why?????). Has anyone else figured out a way to inject a custom IValueProvider into the DefaultModelBinder without using the ValueProviderFactory?

Answer

nikmd23 picture nikmd23 · Oct 7, 2011

You should still use a ValueProviderFactory in this case.

The method that you have to implement on your ValueProviderFactory has this signature:

IValueProvider GetValueProvider(ControllerContext controllerContext)

Within your implementation of that method you can inspect the controller context, and if the incoming request is for the controller/action that you want to leverage cookies on, return some CustomCookieValueProvider.

If you don't want to leverage cookies for the request, just return null and the framework will filter that out of from the list of Value Providers.

As a bonus, you might not want to hard code the logic for when to use the CustomCookieValueProvider into the ValueProviderFactory. You could, perhaps, leverage DataTokens to match when to use cookies with given routes. So add a route like this:

routes.MapRoute("SomeRoute","{controller}/{action}").DataTokens.Add("UseCookies", true);

Notice the DataTokens.Add() call in there, now inside you GetValueProvider method you could do something like this:

if (controllerContext.RouteData.DataTokens.ContainsKey("UseCookies"))
{
    return new CustomCookieValueProvider(controllerContext.RequestContext.HttpContext.Request.Cookies);
}

return null;