13

Cookie からキー/値を取得し、それを使用してモデルをバインドできるようにしたいと考えています。

カスタム ModelBinder を構築するよりも、DefaultModelBinder がすぐに使用できると思います。値の取得元を選択する最善の方法は、使用する 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 に挿入する方法を見つけた人はいますか?

4

3 に答える 3

10

この場合でも、を使用する必要がありますValueProviderFactory

に実装する必要のあるメソッドには、次のValueProviderFactoryシグネチャがあります。

IValueProvider GetValueProvider(ControllerContext controllerContext)

そのメソッドの実装内で、コントローラーコンテキストを検査できます。着信要求が、Cookieを利用するコントローラー/アクションに対するものである場合は、いくつかを返しますCustomCookieValueProvider

リクエストにCookieを利用したくない場合は、戻るだけnullで、フレームワークがバリュープロバイダーのリストからCookieを除外します。

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;
于 2011-10-07T14:59:07.790 に答える
4

アクション パラメーターに対する属性として 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 からのコードですが、いくつか微調整が加えられています。封印されていないため、必要に応じて IValueProviders の作成方法を変更できます。

これは、おそらく非表示または暗号化されたフィールドである別のフィールドを調べ、データを取得して別のプロパティに入れる簡単な例です。

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);
    }
于 2013-03-03T18:06:54.580 に答える
3

これを行う方法を考え出しました。まず、コンストラクターで値プロバイダーの型を受け取るが、既定のモデル バインダーから継承するカスタム モデル バインダーを作成します。これにより、カスタム値プロバイダーで標準のモデル バインディングを使用できます。

/// <summary>
/// Uses default model binding, but sets the value provider it uses
/// </summary>
public class SetValueProviderDefaultModelBinder : DefaultModelBinder
{
    private Type _ValueProviderType;

    public SetValueProviderDefaultModelBinder(Type valueProviderType)
    {
        if (valueProviderType.GetInterface(typeof(IValueProvider).Name) == null)
            throw new ArgumentException("Type " + valueProviderType + " must implement interface IValueProvider.", "valueProviderType");

        _ValueProviderType = valueProviderType;
    }

    /// <summary>
    /// Before binding the model, set the IValueProvider it uses
    /// </summary>
    public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        bindingContext.ValueProvider = GetValueProvider();

        return base.BindModel(controllerContext, bindingContext);
    }

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

次に、上記で作成したカスタム モデル バインダーに値プロバイダーの型を挿入するモデル バインディング属性を作成し、それをモデル バインダーとして使用します。

/// <summary>
/// On the default model binder, replaces the current value provider with the specified value provider.  Cannot use custom model binder with this.
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Class | AttributeTargets.Enum | AttributeTargets.Interface | AttributeTargets.Parameter | AttributeTargets.Struct, AllowMultiple = false, Inherited = false)]
public class SetValueProviderAttribute : CustomModelBinderAttribute
{
    // Originally, this was an action filter, that OnActionExecuting, set the controller's IValueProvider, expecting it to be picked up by the default model binder
    // when binding the model.  Unfortunately, OnActionExecuting occurs AFTER the IValueProvider is set on the DefaultModelBinder.  The only way around this is
    // to create a custom model binder that inherits from DefaultModelBinder, and in its BindModel method set the ValueProvider and then do the standard model binding.

    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 IModelBinder GetBinder()
    {
        var modelBinder = new SetValueProviderDefaultModelBinder(_ValueProviderType);
        return modelBinder;
    }
}
于 2011-12-28T18:18:16.850 に答える