Search code examples
asp.net-web-apicustom-model-binderasp.net-mvc-apiexplorer

WebApi: ApiExplorer and Custom ModelBinders


Most of my api routes are segmented like so:

/api/{segment}/MyEntity (i.e. "/api/SegmentA/MyEntity")

Where I've defined a ModelBinder that converts from the string to a Segment object like so:

class SegmentModelBinder : IModelBinder
{
    public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
    {
        var value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
        if (value == null || String.IsNullOrEmpty(value.AttemptedValue))
            return false;

        bindingContext.Model = **logic to find segment object from value.AttemptedValue**;
        return true;
    }
}

Configured as:

GlobalConfiguration.Configuration.BindParameter(typeof(Segment), new SegmentModelBinder());

So my routes end up looking like this:

public class MyEntityController : BaseController
{
    [HttpGet, Route("api/{segment}/MyEntity")]
    public IEnumerable<MyEntity> Get(Segment segment)
    {
        ...
    }
}

The problem is, I'm now attempting to generate documentation for these Api calls, and ApiExplorer is completely confused by these routes and ignores them.

How do I tell it that for these routes, when it sees a parameter of type Segment, it's really just a string from the route?


Solution

  • Switching from using a ModelBinder to TypeConverter resolved the problem:

    [TypeConverter(typeof(MyEntityConverter))]
    public class MyEntity
    {....
    

    -

    public class MyEntityConverter : TypeConverter
    {
        public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
        {
            if (sourceType == typeof(string))
                return true;
            return base.CanConvertFrom(context, sourceType);
        }
    
        public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
        {
            var key = value as string;
            if (!String.IsNullOrEmpty(key))
                return **Find Entity**;
    
            return base.ConvertFrom(context, culture, value);
        }
    }
    

    Edit:

    If you ever return this entity in call, you need this in there as well, otherwise the newtonsoft json serializer will serialize the class to the type name:

        public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
        {
            return false;
        }