【问题标题】:Give arguments to custom model binder为自定义模型绑定器提供参数
【发布时间】:2018-04-19 19:15:06
【问题描述】:

我正在编写 asp.net mvc 5 web api。我有以下代码,我想像http://localhost/API/Compatibility/59dd2c60-c340-4735-8ecb-85efc60c7b14;d126d9b3-4516-46ca-bd6c-1a8c23740b90一样调用web api

[RoutePrefix("API/Compatibility")]
public class CompatibilityController : ApiController
{
    [Route("{organizationIds}")]
    public Guid Get(IList<Guid> organizationIds)
    {
        ...
    }
}

当前参数organizationIds 无法从 URL 接收值。我理解这是因为默认模型绑定器不知道如何分隔 GUID。

自定义模型绑定器似乎是救命稻草。

我的其他 web api 可能使用分号以外的不同分隔符。因此,与其创建SemicolonSeparatedBinderCommaSeparatedBinderPipeSeparatedBinder 等,我可以创建SymbolSeparatedBinder 并传入分隔符吗?

这样可以吗?

[Route("{organizationIds}")]
public Guid Get([ModelBinder(typeof(SymbolSeparatedBinder), separator=";")]
                IList<string> organizationIds)
{
    ...
}

【问题讨论】:

    标签: asp.net-mvc asp.net-mvc-5


    【解决方案1】:

    ModelBinder 属性需要绑定器的类型。这里最好的解决方案是使用常量字符参数化的泛型类,例如SymbolSeparatedBinder&lt;','&gt;SymbolSeparatedBinder&lt;';'&gt;。但是 C# does not support 这与 C++ 不同。

    对于这个问题,您仍然有简单而优雅的解决方案。使用抽象的Separator 属性定义基本的SymbolSeparatedBinder 类,该属性应该被特定的分隔符覆盖:

    public abstract class SymbolSeparatedBinder : IModelBinder
    {
        protected abstract char Separator { get; }
    
        public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
        {
            //  Put all logic here. Use Separator property for splitting.
            //  ...
    
            return true;
        }
    }
    
    public class SemicolonSeparatedBinder : SymbolSeparatedBinder
    {
        protected override char Separator => ';';
    }
    
    public class CommaSeparatedBinder : SymbolSeparatedBinder
    {
        protected override char Separator => ',';
    }
    
    [Route("{organizationIds}")]
    public Guid Get([ModelBinder(typeof(CommaSeparatedBinder))]
                    IList<string> organizationIds)
    {
        ...
    }
    

    通过这样的解决方案,您可以避免重复绑定代码。唯一的缺点是必须为每个支持的分隔符声明一个类。

    【讨论】:

    • 谢谢。我自己想出了另一个解决方案。
    【解决方案2】:

    我通过使用ParameterBindingAttributeHttpParameterBinding 找到了另一种方法。

    /// <summary>
    /// Applies to an Array type, specifies the string that is used to split elements.
    /// </summary>
    [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false)]
    public class ArraySeparatorAttribute : ParameterBindingAttribute
    {
        string _separator;
    
        public ArraySeparatorAttribute(string separator)
        {
            _separator = separator;
        }
    
    
        public override HttpParameterBinding GetBinding(HttpParameterDescriptor parameter)
        {
            if (typeof(System.Collections.IEnumerable).IsAssignableFrom(parameter.ParameterType))
                return new ArraySeparatorParameterBinding(parameter, _separator);
            throw new NotSupportedException($"{nameof(ArraySeparatorAttribute)} can only be applied to array.");
        }
    }
    
    internal class ArraySeparatorParameterBinding : HttpParameterBinding
    {
        string _separator;
    
        public ArraySeparatorParameterBinding(HttpParameterDescriptor parameter, string separator) : base(parameter)
        {
            _separator = separator;
        }
    
        public override Task ExecuteBindingAsync(ModelMetadataProvider metadataProvider, HttpActionContext actionContext, CancellationToken cancellationToken)
        {
            string separatorListString = (string)actionContext.RequestContext.RouteData.Values[Descriptor.ParameterName];
            var elements = separatorListString.Split(new string[] { _separator }, StringSplitOptions.RemoveEmptyEntries);
    
    
            ValueProviderResult r = new ValueProviderResult(elements, null, System.Globalization.CultureInfo.InvariantCulture);
    
            actionContext.ActionArguments[Descriptor.ParameterName] = r.ConvertTo(Descriptor.ParameterType);
    
    
            var tsc = new TaskCompletionSource<object>();
            tsc.SetResult(null);
            return tsc.Task;
        }
    }
    

    用法是这样的

    [RoutePrefix("API/Compatibility")]
    public class CompatibilityController : ApiController
    {
        [Route("{organizationIds}")]
        public Guid Get([ArraySeparator(";")] Guid[] organizationIds)
        {
            return Guid.NewGuid();
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-11-10
      • 2020-06-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-07-16
      • 1970-01-01
      • 2012-02-08
      相关资源
      最近更新 更多