A custom model binder for XML content

Most of the time the default model binder in MVC suffices and maps everything into your model cleanly. One thing that you can't do automatically is read XML into an XDocument. You could solve this with something along the lines of:

[ValidateInput(false)]
public ActionResult TransferXMLData(string xmlText, int? otherValue)
{
var document = XDocument.Parse(xmlText);

    return Json(new { Success = true, OtherValue = otherValue });
}

But this means that every action you want to use an XML parameter on will need this, along with any appropriate code for checking that the parameter isn't null, contains valid XML, etc,..

The solution I've used before is to create a custom model binder, which requires both the custom model binder and an attribute to use to decorate the arguments that should be transformed into an XDocument:

public class XDocumentModelBinderAttribute : CustomModelBinderAttribute
{
    /// <summary>
    /// Get a model binder that transforms an XML string into an XDocument
    /// </summary>
    public override IModelBinder GetBinder()
    {
        return XDocumentModelBinder.Instance;
    }
}
/// <summary>
/// A model binder that transforms the input into an XmlDocument
/// </summary>
public class XDocumentModelBinder : IModelBinder
{
    internal static XDocumentModelBinder Instance { get; } = new XDocumentModelBinder();

    object IModelBinder.BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        // First check if request validation is required
        var shouldPerformRequestValidation = controllerContext.Controller.ValidateRequest && bindingContext.ModelMetadata.RequestValidationEnabled;
        var incomingData = bindingContext.GetValueFromValueProvider(shouldPerformRequestValidation).AttemptedValue;
        var document = XDocument.Parse(incomingData);
        return document;
    }
}
/// <summary>
/// Helper methods for XDocumentModelBinder
/// </summary>
public static class ModelBindingContextExtensions
{
    /// <remarks>
    /// Code originally from http://blogs.taiga.nl/martijn/2011/09/29/custom-model-binders-and-request-validation/
    /// </remarks>
    public static ValueProviderResult GetValueFromValueProvider(this ModelBindingContext bindingContext, bool performRequestValidation)
    {
        var unvalidatedValueProvider = bindingContext.ValueProvider as IUnvalidatedValueProvider;
        return (unvalidatedValueProvider != null)
                    ? unvalidatedValueProvider.GetValue(bindingContext.ModelName, !performRequestValidation)
                    : bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
    }
}

A parameter on an action can then be decorated with the XDocumentModelBinderAttribute so that it gets transformed into an XDocument (along with ValidateInput to allow the XML through):

[ValidateInput(false)]
public ActionResult TransferXMLData([XDocumentModelBinder] XDocument xmlText, int? otherValue)
{
   var response = new
   {
        Success = true
   };
   return Json(response);
}

A working sample that uses this model binder can be found on GitHub at https://github.com/robertwray/BlogStuff.


No Comments

Add a Comment