Easiest and most elegant way to inject custom IActionResultConverter instances

Topics: ASP.NET Web API
Oct 14, 2012 at 9:45 PM
Edited Oct 14, 2012 at 10:37 PM

What is the easiest and most elegant way to inject new IActionResultConverter instances?

As far as I can see, HttpActionDescriptor has knowledge on ActionResultConverters and the only way I can see to inject my own result converters is to override the virtual ResultConverter of the HttpActionDescriptor. However, it is not that simple because it will cost me to go all the way down to the action selector because it is the place where the ActionDescriptor is constructed.

Do I miss anything here? Is there a better way?

Developer
Oct 16, 2012 at 12:06 AM

It would have been easier had IActionResultConverter been replaceable as a Service....but currently its not. Following is a way I have tried and this seems to work and its similar to what you mentioned above. I use a wrapper ReflectedHttpActionDescriptor to which I supply the default implementation and I just provide my custom implementation for ResultConverter only and for the remaining delegate it to the original one.

 public class CustomActionSelector : ApiControllerActionSelector
    {
        public override HttpActionDescriptor SelectAction(HttpControllerContext controllerContext)
        {
            return new WrapperReflectedHttpActionDescriptor(base.SelectAction(controllerContext));
        }

        public override ILookup<string, HttpActionDescriptor> GetActionMapping(HttpControllerDescriptor controllerDescriptor)
        {
            //Wrap the original action descriptor into Wrapper action descriptor
            return base.GetActionMapping(controllerDescriptor)
                                .SelectMany(g => g, (g, v) => new KeyValuePair<string, HttpActionDescriptor>(g.Key, new WrapperReflectedHttpActionDescriptor(v)))
                                .ToLookup(kvp => kvp.Key, kvp => kvp.Value);
        }
    }

    public class WrapperReflectedHttpActionDescriptor : ReflectedHttpActionDescriptor
    {
        private HttpActionDescriptor _original;

        public WrapperReflectedHttpActionDescriptor(HttpActionDescriptor original)
        {
            this.Configuration = original.Configuration;
            this.ControllerDescriptor = original.ControllerDescriptor;
           
            this._original = original;
        }

        public override HttpActionBinding ActionBinding
        {
            get
            {
                return this._original.ActionBinding;
            }
            set
            {
                this._original.ActionBinding = value;
            }
        }

        public override string ActionName
        {
            get
            {
                return this._original.ActionName;
            }
        }

        public override System.Threading.Tasks.Task<object> ExecuteAsync(HttpControllerContext controllerContext, IDictionary<string, object> arguments, System.Threading.CancellationToken cancellationToken)
        {
            return this._original.ExecuteAsync(controllerContext, arguments, cancellationToken);
        }

        public override System.Collections.ObjectModel.Collection<T> GetCustomAttributes<T>()
        {
            return this._original.GetCustomAttributes<T>();
        }

        public override System.Collections.ObjectModel.Collection<System.Web.Http.Filters.FilterInfo> GetFilterPipeline()
        {
            return this._original.GetFilterPipeline();
        }

        public override System.Collections.ObjectModel.Collection<System.Web.Http.Filters.IFilter> GetFilters()
        {
            return this._original.GetFilters();
        }

        public override System.Collections.ObjectModel.Collection<HttpParameterDescriptor> GetParameters()
        {
            return this._original.GetParameters();
        }

        public override System.Collections.Concurrent.ConcurrentDictionary<object, object> Properties
        {
            get
            {
                return this._original.Properties;
            }
        }

        public override IActionResultConverter ResultConverter
        {
            get
            {
                //provide custom one
                return new CustomActionResultConverter();
            }
        }

        public override Type ReturnType
        {
            get
            {
                return this._original.ReturnType;
            }
        }

        public override System.Collections.ObjectModel.Collection<HttpMethod> SupportedHttpMethods
        {
            get
            {
                return this._original.SupportedHttpMethods;
            }
        }
    }

    public class CustomActionResultConverter : IActionResultConverter
    {
        public HttpResponseMessage Convert(HttpControllerContext controllerContext, object actionResult)
        {
           HttpResponseMessage response = new HttpResponseMessage();
            response.Content = new StringContent("Hello");
            return response;
        }
    }

Oct 16, 2012 at 10:31 AM

Thanks for the answer.

So, this not definitely the way to go I guess. It's doable but we are jumping lots of hoops. Would it be considered for vNext if I open up an issue for this one? It would open up lots of flexibilities if this can be injected through the DI as a service or based on rules (just like as it is with HttpParamaterBindings today).

Developer
Oct 16, 2012 at 3:44 PM

Sure...please go ahead and file an issue for this.

Oct 16, 2012 at 4:10 PM

thanks! here it is: http://aspnetwebstack.codeplex.com/workitem/551