代码之家  ›  专栏  ›  技术社区  ›  Gqqnbig

为自定义模型绑定器提供参数

  •  2
  • Gqqnbig  · 技术社区  · 6 年前

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

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

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

    定制模型活页夹似乎是救命稻草。

    我的其他web api可能使用分号以外的其他分隔符。因此,而不是创建 SemicolonSeparatedBinder ,则, CommaSeparatedBinder ,则, PipeSeparatedBinder ,等等,我可以创建 SymbolSeparatedBinder 然后通过分离器?

    有可能这样做吗?

    [Route("{organizationIds}")]
    public Guid Get([ModelBinder(typeof(SymbolSeparatedBinder), separator=";")]
                    IList<string> organizationIds)
    {
        ...
    }
    
    2 回复  |  直到 6 年前
        1
  •  1
  •   CodeFuller    6 年前

    ModelBinder 属性需要活页夹的类型。这里最好的解决方案是用常量字符参数化泛型类,例如。 SymbolSeparatedBinder<','> ,则, SymbolSeparatedBinder<';'> .然而C# does not support 这与C++不同。

    对于这个问题,您仍然有简单而优雅的解决方案。定义基本 SymbolSeparatedBinder 使用摘要初始化 Separator 应使用特定分隔符重写的属性:

    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
  •  0
  •   Gqqnbig    6 年前

    我想出了另一种方法 ParameterBindingAttribute HttpParameterBinding

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