Asp.net-Mvc

使用自定義 ASP.NET MVC IValueProvider,而不進行全域設置?

  • March 3, 2013

我希望能夠從 cookie 中獲取鍵/值並使用它來綁定模型。

我相信 DefaultModelBinder 開箱即用,而不是建構自定義 ModelBinder,並且選擇值來自何處的最佳方法是設置它使用的 IValueProvider。

為此,我不想創建自定義 ValueProviderFactory 並將其綁定到全域,因為我只想將此 ValueProvider 用於特定的操作方法。

我已經建立了一個執行此操作的屬性:

/// <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);
   }
}

不幸的是,ModelBinder 及其 IValueProvider 是在 OnActionExecuting 之前設置的(為什麼??????)。有沒有其他人想出一種在不使用 ValueProviderFactory 的情況下將自定義 IValueProvider 注入 DefaultModelBinder 的方法?

這是一種替代方法,可讓您將 IValueProviders 指定為針對操作參數的屬性。這使得 IValueProviders 是瞬態的,而不是全域的。

public interface IControllerContextAware
{
   ControllerContext ControllerContext { get; set; }
}

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum | AttributeTargets.Interface | AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)]
public class ValueProviderAttribute : CustomModelBinderAttribute
{
   public Type[] ValueProviders { get; private set; }

   public ValueProviderAttribute(params Type[] valueProviders)
   {
       if (valueProviders == null)
       {
           throw new ArgumentNullException("valueProviders");
       }
       foreach (var valueProvider in valueProviders.Where(valueProvider => !typeof(IValueProvider).IsAssignableFrom(valueProvider)))
       {
           throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, "The valueProvider {0} must be of type {1}", valueProvider.FullName, typeof(IValueProvider)), "valueProviders");
       }

       ValueProviders = valueProviders;
   }

   public override IModelBinder GetBinder()
   {
       return new ValueProviderModelBinder
           {
               ValueProviderTypes = ValueProviders.ToList(),
               CreateValueProvider = OnCreateValueProvider
           };
   }

   protected virtual IValueProvider OnCreateValueProvider(Type valueProviderType, ControllerContext controllerContext, ModelBindingContext bindingContext)
   {
       var valueProvider = (IValueProvider)Activator.CreateInstance(valueProviderType);
       if (valueProvider is IControllerContextAware)
       {
           (valueProvider as IControllerContextAware).ControllerContext = controllerContext;
       }
       return valueProvider;
   }

   private class ValueProviderModelBinder : DefaultModelBinder
   {
       public IList<Type> ValueProviderTypes { get; set; }
       public Func<Type, ControllerContext, ModelBindingContext, IValueProvider> CreateValueProvider { get; set; }

       public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
       {
           var valueProviders = from type in ValueProviderTypes
                                select CreateValueProvider(type, controllerContext, bindingContext);

           bindingContext.ValueProvider = new ValueProviderCollection(valueProviders.Concat((Collection<IValueProvider>)bindingContext.ValueProvider).ToList());

           return base.BindModel(controllerContext, bindingContext);
       }
   }
}

這基本上是 ModelBinderAttribute 的程式碼,但有一些調整。它不是密封的,因此您可以根據需要更改創建 IValueProvider 的方式。

這是一個簡單的範例,它查看另一個欄位,可能是隱藏或加密的欄位,然後獲取數據並將其放入另一個屬性中。

這是模型,它不知道 IValueProvider,但知道隱藏欄位。

public class SomeModel
{
   [Required]
   public string MyString { get; set; }

   [Required]
   public string MyOtherString { get; set; }

   [Required]
   public string Data { get; set; }
}

然後我們有 IValueProvider,在這種情況下,我的提供者明確知道我的模型,但不一定是這種情況。

public class MyValueProvider : IValueProvider, IControllerContextAware
{
   public ControllerContext ControllerContext { get; set; }

   public bool ContainsPrefix(string prefix)
   {
       var containsPrefix = prefix == "MyString" && ControllerContext.HttpContext.Request.Params.AllKeys.Any(key => key == "Data");
       return containsPrefix;
   }

   public ValueProviderResult GetValue(string key)
   {
       if (key == "MyString")
       {
           var data = ControllerContext.RequestContext.HttpContext.Request.Params["Data"];

           var myString = data.Split(':')[1];

           return new ValueProviderResult(myString, myString, CultureInfo.CurrentCulture);
       }
       return null;
   }
}

然後是把這一切聯繫在一起的動作:

   [HttpGet]
   public ActionResult Test()
   {
       return View(new SomeModel());
   }

   [HttpPost]
   public ActionResult Test([ValueProvider(typeof(MyValueProvider))]SomeModel model)
   {
       return View(model);
   }

ValueProviderFactory在這種情況下,您仍應使用 a 。

您必須在您的方法上實現的方法ValueProviderFactory具有以下簽名:

IValueProvider GetValueProvider(ControllerContext controllerContext)

在該方法的實現中,您可以檢查控制器上下文,如果傳入請求是針對您要利用 cookie 的控制器/操作,則返回 some CustomCookieValueProvider.

如果您不想為請求使用 cookie,只需返回null,框架就會將其從值提供者列表中過濾掉。

作為獎勵,您可能不想將何時使用的邏輯硬編碼CustomCookieValueProviderValueProviderFactory. 也許,您可以利用DataTokens給定路由匹配何時使用 cookie。所以添加這樣的路線:

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

注意DataTokens.Add()那裡的呼叫,現在在你的GetValueProvider方法中你可以做這樣的事情:

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

return null;

引用自:https://stackoverflow.com/questions/7600212