ASP.NET Web API Operation with interfaces instead concrete class
Asked Answered
R

1

3

My team needs to develop a framework for our company and products using this framework. One of the requisites is that a product could be customized for a specific client, yet it should be easily updated with another version of the same product (not automatically).

We're using ASP.NET MVC 4 + Web API (for now, a desktop product will be created next year, based on our framework), NHibernate, heavily IoC using Autofac as DI container and N-Layers.

So, in some points of the WebApp, we're using ViewModels as Interfaces with one default implementation and using Autofac to link them, and that's is easy to change in futures customizations.

In ASP.NET MVC, we achieve this implementing IModelBinderProvider and creating a custom class inherating DefaultModelBinder:

Something like this:

public class MyCustomMVCModelBinderProvider : IModelBinderProvider
{
    public IModelBinder GetBinder(Type modelType)
    {
        if (modelType.IsInterface)
            return new MyCustomMVCModelBinder();

        return new DefaultModelBinder();
    }
}
public class MyCustomMVCModelBinder : DefaultModelBinder
{
    public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        var context = new ModelBindingContext(bindingContext);
        var item = DependencyResolver.Current.GetService(bindingContext.ModelType);

        Func<object> modelAccessor = () => item;
        context.ModelMetadata = new ModelMetadata(new DataAnnotationsModelMetadataProvider(),
            bindingContext.ModelMetadata.ContainerType, modelAccessor, item.GetType(), bindingContext.ModelName);

        return base.BindModel(controllerContext, context);
    }
}

And in my controller I just use the interface as parameter. So, it works fine because the values are populated correctly.

But, how do I do the same in Web API with the values binded correctly? I tried implemeting IModelBinder and inheriting ModelBinderProvider. I can get the instance of interface's implementantion, but the values aren't populated.

Here's an attempt implementation in WebAPI:

public class MyCustomWebAPIModelBinderProvider : ModelBinderProvider
{
    public override IModelBinder GetBinder(System.Web.Http.HttpConfiguration configuration, Type modelType)
    {
        return new MyCustomWebAPIModelBinder();
    }
}

public class MyCustomWebAPIModelBinder : IModelBinder
{
    public bool BindModel(System.Web.Http.Controllers.HttpActionContext actionContext, ModelBindingContext bindingContext)
    {
        if (bindingContext.ModelType.IsInterface)
        {
            var item = GlobalConfiguration.Configuration.DependencyResolver.GetService(bindingContext.ModelType);

            if (item != null)
            {
                Func<object> modelAccessor = () => item;
                var a = bindingContext.ModelMetadata.ContainerType;
                var b = modelAccessor;
                var c = item.GetType();
                var d = bindingContext.ModelName;

                bindingContext.ModelMetadata = new ModelMetadata(new DataAnnotationsModelMetadataProvider(), a, b, c, d);

                bindingContext.Model = item;

                return true;
            }
        }

        return false;
    }
}

What am I missing? Is it possible to do what we want?

Romish answered 15/6, 2013 at 0:28 Comment(3)
Is IConfigurationModel present in the request body or does it come from the uri? Could you share what the HttpRequestMessage looks like, may be a fiddler trace?Reconnoiter
@RaghuRamNadiminti I changed the images with de body request of the POST and the PUTRomish
Could you please integrate the images in your question? The links are broken.Donalddonaldson
N
1

Instead of using ModelBinder, use an implementation of MediaTypeFormatter.

You can overrides the method "ReadFromStreamAsync" and change the type to whatever you need.

In the example below, the type is changed by resolving the concrete type using the MVC's DependencyResolver, witch works fine for WebAPI.



    public class CustomFormUrlEncodedMediaTypeFormatter : FormUrlEncodedMediaTypeFormatter
    {
        public CustomFormUrlEncodedMediaTypeFormatter() : base() { }

        public override Task ReadFromStreamAsync(Type type, Stream readStream, HttpContent content, IFormatterLogger formatterLogger)
        {
            if (type.IsInterface)
                type = GetConcreteType(type);

            return base.ReadFromStreamAsync(type, readStream, content, formatterLogger);
        }

        public override Task WriteToStreamAsync(Type type, object value, Stream writeStream, HttpContent content, TransportContext transportContext)
        {
            if (type.IsInterface)
                type = GetConcreteType(type);

            return base.WriteToStreamAsync(type, value, writeStream, content, transportContext);
        }

        private Type GetConcreteType(Type type)
        {
            object concrete = System.Web.Mvc.DependencyResolver.Current.GetService(type);
            return concrete.GetType();
        }
    }

If you want to do that with the JsonFormatter, it's a better aproach to create a "CustomCreationConverter" for Json.NET, than they can resolve the depenencies for all child objects that you have in your interface.



    public class DomainConverter : CustomCreationConverter
    {
            public DomainConverter() { }

            public override bool CanConvert(Type objectType)
            {
                return objectType.IsInterface;
            }

            public override object Create(Type objectType)
            {
                return System.Web.Mvc.DependencyResolver.Current.GetService(objectType);
            }
    }

Nim answered 19/6, 2013 at 11:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.