使用自定義 ASP.NET MVC IValueProvider,而不進行全域設置?
我希望能夠從 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,框架就會將其從值提供者列表中過濾掉。作為獎勵,您可能不想將何時使用的邏輯硬編碼
CustomCookieValueProvider到ValueProviderFactory. 也許,您可以利用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;