ApiExplorer does not recognize route attributes with custom type
Asked Answered
K

2

2

I have a project where I want to use route attributes with a custom type. The following code where I have the custom type as a query parameter works fine and the help page displays the custom type.

// GET api/values?5,6
[Route("api/values")]
public string Get(IntegerListParameter ids)
{
    return "value";
}

WebApi.HelpPage gives the following documentation Help:Page

If I change the code to use route attributes, the result is that I get an empty help page.

// GET api/values/5,6
[Route("api/values/{ids}")]
public string Get(IntegerListParameter ids)
{
    return "value";
}

When I inspect the code I observe in HelpController.cs that ApiExplorer.ApiDescriptions returns an empty collection of ApiDescriptions

public ActionResult Index()
{
    ViewBag.DocumentationProvider = Configuration.Services.GetDocumentationProvider();
    Collection<ApiDescription> apiDescriptions = Configuration.Services.GetApiExplorer().ApiDescriptions;

    return View(apiDescriptions);
}

Is there any way to get ApiExplorer to recognize my custom class IntegerListParameter as attribute routing?

Krasnoff answered 16/12, 2016 at 7:20 Comment(0)
T
2

You need to:

  1. add HttpParameterBinding for your IntegerListParameter type
  2. mark the binding as IValueProviderParameterBinding and implement ValueProviderFactories
  3. add a converter for IntegerListParameter and override CanConvertFrom method for typeof(string) parameter

After these actions, route with custom type IntegerListParameter must be recognized in ApiExplorer.

See my example for type ObjectId:

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        //...
        config.ParameterBindingRules.Insert(0, GetCustomParameterBinding);
        TypeDescriptor.AddAttributes(typeof(ObjectId), new TypeConverterAttribute(typeof(ObjectIdConverter)));
        //...
    }

    public static HttpParameterBinding GetCustomParameterBinding(HttpParameterDescriptor descriptor)
    {
        if (descriptor.ParameterType == typeof(ObjectId))
        {
            return new ObjectIdParameterBinding(descriptor);
        }
        // any other types, let the default parameter binding handle
        return null;
    }
}

public class ObjectIdParameterBinding : HttpParameterBinding, IValueProviderParameterBinding
{
    public ObjectIdParameterBinding(HttpParameterDescriptor desc)
        : base(desc)
    {
    }

    public override Task ExecuteBindingAsync(ModelMetadataProvider metadataProvider, HttpActionContext actionContext, CancellationToken cancellationToken)
    {
        try
        {
            SetValue(actionContext, new ObjectId(actionContext.ControllerContext.RouteData.Values[Descriptor.ParameterName] as string));
            return Task.CompletedTask;
        }
        catch (FormatException)
        {
            throw new BadRequestException("Invalid id format");
        }
    }

    public IEnumerable<ValueProviderFactory> ValueProviderFactories { get; } = new[] { new QueryStringValueProviderFactory() };
}

public class ObjectIdConverter : TypeConverter
{
    public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
    {
        if (sourceType == typeof(string))
            return true;
        return base.CanConvertFrom(context, sourceType);
    }
}
Tachograph answered 5/11, 2017 at 16:35 Comment(2)
This was helpful when trying to figure out why a custom model binder (comma delimited array of ints) wasn't showing up in Api Explorer or Swagger, thanks for putting this together!Attribute
I'm struggling to see how to implement this answer in the context of the original question. I am in a similar situation to @Attribute where I have a comma delimited array of ints to bind. I have a working ModelBinder but Swagger doesn't recognise it. How does this tie in with the ModelBinder?Endres
K
0

Not exactly sure what data structure IntegerListParameter list is but if you need to send a comma delimited list of integers in the query(e.g. ~api/products?ids=1,2,3,4) you can use filter attributes. An example implementation of this can be found here: Convert custom action filter for Web API use?

Kinzer answered 16/12, 2016 at 7:46 Comment(2)
I have implemented comma separated list of integers. It works fine bothe as query parameters and as attribute routing.Krasnoff
My problem is that the helppage is not showing actions where the list is an attribute routing. The problem is that ApiExplorer.ApiDescriptions is not recognizing the action when the list is implemented as an attribute routing.Krasnoff

© 2022 - 2024 — McMap. All rights reserved.