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

时间:2018-04-19 19:15:06

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

我正在编写asp.net mvc 5 web api。我有以下代码,我想将网络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。

定制模型活页夹似乎很有帮助。

我的其他网络API可能使用分号以外的其他分隔符。因此,我是否可以创建SemicolonSeparatedBinder并传入分隔符,而不是创建CommaSeparatedBinderPipeSeparatedBinderSymbolSeparatedBinder等等?

可以这样做吗?

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

2 个答案:

答案 0 :(得分:1)

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)
{
    ...
}

使用此类解决方案可避免重复绑定代码。唯一的缺点是必须为每个支持的分隔符声明一个类。

答案 1 :(得分:0)

我使用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();
    }
}