web api model binding to an interface

14,436

So in case this can help anyone else, I'll go ahead and post the solution I came up with.

As I mentioned above, the interface parameters of the action method can be resolved using DI. But the interface members of that object need to be handled differently.

I created 2 types of Json converters, a single entity type and a collection type, to decorate the interface properties.

Here's a class that needs to be resolved as an action interface parameter.

public class CreateEnvelopeModel : ICreateEnvelopeCommand
{
    [JsonConverter(typeof(EntityModelConverter<CreateEmailModel, ICreateEmailCommand>))]
    public ICreateEmailCommand Email { get; set; }
    [JsonConverter(typeof(CollectionEntityConverter<CreateFormModel, ICreateFormCommand>))]
    public IList<ICreateFormCommand> Forms { get; set; }
}

Here's the controller action method

public HttpResponseMessage PostEnvelope(ICreateEnvelopeCommand model)
{
    // do stuff
}

Here's the 2 json converters

public class EntityModelConverter<T, Tt> : JsonConverter where T : Tt
{
    public override bool CanConvert(Type objectType)
    {
        return (objectType == typeof(Tt));
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        return serializer.Deserialize<T>(reader);
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        serializer.Serialize(writer, value, typeof(T));
    }
}

public class CollectionEntityConverter<T, Tt> : JsonConverter where T : Tt
{
    public override bool CanConvert(Type objectType)
    {
        return (objectType == typeof(IList<Tt>));
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        IList<Tt> items = serializer.Deserialize<List<T>>(reader).Cast<Tt>().ToList();
        return items;
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        serializer.Serialize(writer, value, typeof(IList<T>));
    }
}
Share:
14,436

Related videos on Youtube

chrisl91
Author by

chrisl91

Updated on September 14, 2022

Comments

  • chrisl91
    chrisl91 over 1 year

    I'm trying to bind a controller action to an interface but still maintain the default binding behavior.

    public class CoolClass : ISomeInterface
    {
        public DoSomething {get;set;} // ISomeInterface
    }
    
    public class DosomethingController : ApiController
    {
        public HttpResponseMessage Post(ISomeInterface model)
        {
            // do something with model which should be an instance of CoolClass
        }
    } 
    

    The consumer of my service knows nothing of CoolClass so having them add "$type" to the Json they are passing would be a hack in my opinion. I'd like to be able to handle it in the service. If I specify CoolClass as the action parameter it works fine.

    EDIT: So I found a partial solution to my question here Dependency injection for ASP.NET Web API action method parameters but there is a follow up issue. That solution does not resolve interface properties. See my example below.

    IConcreteClass will be resolved, but ISubtype will not.

    public class SubConcreteClass : ISubtype
    {
        // properties
    }
    
    public class ConcreteClass : IConcreteClass
    {
        public ISubtype Subtype {get;set;}
    }
    

    Once the media formatter sees that is can resolve the type in IConcreteClass, it then reads the entire stream. So I'm guessing there is no chance to resolve interface members.

  • chrisl91
    chrisl91 over 10 years
    Thanks Alex. I saw that page but I was really hoping this could be resolved with my DI container. Is it not possible to resolve API action interface parameters with DI?
  • Rama
    Rama almost 10 years
    This is a great solution.
  • BrianS
    BrianS almost 9 years
    This is beautifully simple and effective. Are there any drawbacks in using this versus custom model binding? Any thoughts on how to use something like this on a collection where the interface has multiple implementation classes (e.g. IAuto, Car:IAuto, Truck:IAuto)?
  • Louis Duran
    Louis Duran about 6 years
    This advice was not helpful as the linked content doesn't cover generic lists.