Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AllowHtml attribute without referencing MVC

We have separated our business logic layer and business objects into a completely separate project/assembly. Some properties of the models can contain HTML content. In front of the business logic, we have an ASP.NET MVC web application, where users can manage the business objects.

  • To allow HTML content on specific properties, we had to add the AllowHtml attribute. But we can't, because we do not want to reference System.Web.Mvc in our core project.
  • Partial classes cannot be used across multiple assemblies.
  • Using the MetadataType attribute is not an option, because it would cause an indirect dependency to MVC or a circular dependency between the core layer and the web application.
  • Another partial solution would be to turn off request validation for the whole request, by using the ValidateInput attribute, but we want to turn off request validation only to specific properties.
  • Properties are not virtual, so we can't just simply create a derived type to override the specific properties.
  • We do not want to duplicate our business objects to view models with exactly the same properties and metadata.
  • Overriding the model binding logic is not an option.

So, how can we indicate to the MVC model binder that we want to allow HTML content on (and only on) some specific properties, without referencing ASP.NET MVC in our business logic layer? Or, how can metadata be injected from another assembly without strong references?

Thank you.

like image 674
BlueCode Avatar asked Nov 07 '13 20:11

BlueCode


2 Answers

I had to change BindModel to the following (this builds on Russ Cam's answer) in order to check the attribute on the actual property. I also looked at this answer for help:

public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {            
        var holderType = bindingContext.ModelMetadata.ContainerType;
        if (holderType != null)
        {
            var propertyType = holderType.GetProperty(bindingContext.ModelMetadata.PropertyName);
            var attributes = propertyType.GetCustomAttributes(true);
            var hasAttribute = attributes
              .Cast<Attribute>()
              .Any(a => a.GetType().IsEquivalentTo(typeof(MyAllowHtmlAttribute)));
            if (hasAttribute)
            {
                bindingContext.ModelMetadata.RequestValidationEnabled = false;
            }
        }

        return base.BindModel(controllerContext, bindingContext);
    }
like image 150
cmour Avatar answered Sep 30 '22 18:09

cmour


Implement your own IModelBinder and AllowHtmlAttribute - put the attribute in your core project and the IModelBinder in your MVC application.

public class MyAllowHtmlAttribute : Attribute
{
}

To implement the IModelBinder, simply inherit from DefaultModelBinder and add logic to turn off request validation based on the presence of your own AllowHtmlAttribute

public class MyBetterDefaultModelBinder : DefaultModelBinder
{
    public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        var allowHtmlAttribute = bindingContext.ModelType.GetCustomAttribute<MyAllowHtmlAttribute>();

        if (allowHtmlAttribute != null)
        {
            bindingContext.ModelMetadata.RequestValidationEnabled = false;
        }

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

Then hook up your own ModelBinder in Application_Start (or other startup code)

ModelBinders.Binders.DefaultBinder = new MyBetterDefaultModelBinder();

This logic in the custom model binder is what the AllowHtmlAttribute in MVC does but you wouldn't be able to use that one easily as it is intrinsically tied to ModelMetadata in MVC.

like image 26
Russ Cam Avatar answered Sep 30 '22 16:09

Russ Cam