Search code examples
c#asp.net-mvcasp.net-mvc-4opayo

MVC Model Binding - Form Post Starts With a Number


I am attempting to create a model for SagePay notifications. I know that the MVC model binder will automatically bind all of my properties based on the POST names.

The SagePay system passes the following form names:

Status
VendorTxCode
VPSTxId
VPSSignature
StatusDetail
AVSCV2
AddressResult
PostCodeResult
CV2Result
GiftAid
3DSecureStatus
CAVV
AddressStatus
PayerStatus
CardType
Last4Digits
DeclineCode
ExpiryDate
FraudResponse
BankAuthCode

I have created the following class.

public class NotificationModel
{
    public string Status { get; set; }
    public string VendorTxCode { get; set; }
    public string VPSTxId { get; set; }
    public string VPSSignature { get; set; }
    public string StatusDetail { get; set; }
    public string AVSCV2 { get; set; }
    public string AddressResult { get; set; }
    public string PostCodeResult { get; set; }
    public string CV2Result { get; set; }
    public string GiftAid { get; set; }
    // When binding the model will MVC ignore the underscore?
    public string _3DSecureStatus { get; set; }
    public string CAVV { get; set; }
    public string AddressStatus { get; set; }
    public string PayerStatus { get; set; }
    public string CardType { get; set; }
    public string Last4Digits { get; set; }
    public string DeclineCode { get; set; }
    public string ExpiryDate { get; set; }
    public string FraudResponse { get; set; }
    public string BankAuthCode { get; set; }
}

Unfortunately a c# property name can't begin with a number. How can I get the model binder to automatically bind the 3DSecureStatus post name to a property?


Solution

  • You can do this with a custom model binder:

    public class NotificationModelBinder : DefaultModelBinder
    {
        public override object BindModel(ControllerContext controllerContext,
            ModelBindingContext bindingContext)
        {
            var model = this.CreateModel(controllerContext,
                bindingContext, bindingContext.ModelType);
            bindingContext.ModelMetadata.Model = model;
    
            var formValue = bindingContext.ValueProvider
                .GetValue(this.FormField).AttemptedValue;
    
            var target = model.GetType().GetProperty(this.FieldToBindTo);
            target.SetValue(model, formValue);
    
            // Let the default model binder take care of the rest
            return base.BindModel(controllerContext, bindingContext);
        }
    
        private readonly string FieldToBindTo = "_3DSecureStatus";
        private readonly string FormField = "3DSecureStatus";
    }
    

    I've not added an error handling, as this scenario dealing with just 1 string is trivial. Other than the actual value of the string not being something you'd expect, the only other error (well, exception in this case) you could get would be if the target property couldn't be found on your model, but obviously that's a simple fix, too.

    Edit: Actually, you could also get an exception for formValue if the field cannot be found on the form, but again, as this is a 3rd party payment system, I'd be inclined to say that wouldn't change either.

    You'd use this like so:

    [HttpPost]
    public ActionResult Index([ModelBinder(typeof(NotificationModelBinder))]NotificationModel model)
    {
        // do stuff
    }