I have the following Custom Model Binder:
public class AllowAndSanitizeHtmlBinder : IModelBinder
{
// initialize HtmlSanitizer (I want this to be injected)
private HtmlSanitizer _htmlSanitizer = new HtmlSanitizer();
sanitizer.PostProcessNode += (s, e) =>
(e.Node as IHtmlAnchorElement)?.SetAttribute("rel", "nofollow");
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var request = controllerContext.HttpContext.Request;
var name = bindingContext.ModelName;
// get the unvalidated user input
var unsanitizedMessageHtmlString = request.Unvalidated[name];
// removed script or any XSS threat from user input
return _htmlSanitizer.Sanitize(unsanitizedMessageHtmlString);
}
}
The problem I have with this code is that I am doing all the initialization of HtmlSanitizer
in the model binder class (this is violating SRP). Is it possible to inject the HtmlSanitizer
into the above binder? I am using Ninject.
I have seen this question: the accepted answer suggests that a model binder should not depend on any service, I am not sure this is the case here... I think DI would simplify my code.
You need a custom IModelBinderProvider
to achieve this.
public class AllowAndSanitizeHtmlBinderProvider : IModelBinderProvider
{
public HtmlSanitizer Sanitizer{get;}
public AllowAndSanitizeHtmlBinderProvider(HtmlSanitizer sanitizer)
{
Sanitizer = sanitizer;
}
public IModelBinder GetBinder(Type modelType)
{
if(modelType==typeof(string)) // I assume it's string
return new AllowAndSanitizeHtmlBinder (Sanitizer);
return null;
}
}
public class AllowAndSanitizeHtmlBinder : IModelBinder
{
private HtmlSanitizer _htmlSanitizer;
public AllowAndSanitizeHtmlBinder(HtmlSanitizer sanitizer)
{
_htmlSanitizer = sanitizer;
}
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var request = controllerContext.HttpContext.Request;
var name = bindingContext.ModelName;
// get the unvalidated user input
var unsanitizedMessageHtmlString = request.Unvalidated[name];
// removed script or any XSS threat from user input
return _htmlSanitizer.Sanitize(unsanitizedMessageHtmlString);
}
}
then in you Ninject configuration
kernel.Bind<IModelBinderProvider>().To<AllowAndSanitizeHtmlBinderProvider >();
kernel.Bind<HtmlSanitizer>().ToMethod(ctx => {
var sanitizer = new HtmlSanitizer();
sanitizer.PostProcessNode += (s, e) =>
(e.Node as IHtmlAnchorElement)?.SetAttribute("rel", "nofollow");
return sanitizer;
});
An even better approach would be to define a factory for AllowAndSanitizeHtmlBinder
which would hold the dependency on HtmlSanitizer
. The Provider would then just receive the factory as dependency. This would mask the dependency on HtmlSanitizer
to the provider.
Also, it would allow to hide HtmlSanitizer behind an interface, and have all your non Ninject code address this interface. This would allow to hide this technical dependency from the other parts of your code.