I want to implement property renderers as handlers. I am using Autofac as a DI container in the app. How can I get objects implementing IPropertyHandler in HtmlHelper extension without using globally accessible container (service location)? Is it a way to register own HtmlHelper in Autofac? Maybe MVC framework provide another way?
public static class HtmlHelperExtensions {
public static MvcHtmlString Editor(this HtmlHelper html, object model) {
return new Renderer(new List<IPropertyHandler>() /*Where to get these objects?*/ ).Render(html, model);
}
}
public class Renderer {
private readonly ICollection<IPropertyHandler> _propertyRenderers;
public Renderer(ICollection<IPropertyHandler> propertyRenderers) {
_propertyRenderers = propertyRenderers;
}
public MvcHtmlString Render(HtmlHelper html, object model) {
var result = "";
foreach(var prop in model.GetType().GetProperties()) {
var renderers = _propertyRenderers.OrderBy(b => b.Order);
//impl
}
return new MvcHtmlString(result);
}
}
AFAIK, MVC 5 doesn't provide a way to do this. But that doesn't mean you can't wire up your own solution.
As per the article DI Friendly Framework by Mark Seemann, you can make a factory interface for your HTML helper that can be used to instantiate it with its dependencies.
DefaultRendererFactory
First there is a default factory that provides the logical default behavior (whatever that is).
You may wish to make this default factory smarter or even use a fluent builder to supply its dependencies as per the other article DI Friendly Library so it is more flexible without using a DI container.
IRenderer
Then we use an abstraction for
Renderer
,IRenderer
so it can be swapped easily and/or provided via DI.Factory Registration
Next, we provide a hook to register the factory. Since the HTML helper is a static extension method, the only option is to make a static field with a static property or method to set it. Its always good practice to make a getter as well in case there is a need to use a decorator pattern on the factory.
You could provide some logical place to register all of your factories statically, if that makes more sense for the app. But there will basically need to be a factory per HTML helper to adhere to the SRP. If you try to generalize, you are basically back to a service locator.
AutofacRendererFactory
Now that all of the pieces are in place, this is how you would slip Autofac into the equation. You will need a custom
IRendererFactory
that you will make part of your composition root that is specific to Autofac.Next, you need to add the type mappings for
IRenderer
and its dependencies to Autofac.Last but not least, you will need to add a line to your application startup after creating the Autofac container to resolve the renderer when it is needed by the application.