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?

有帮助吗?

解决方案

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
}

其他提示

Should you wish to do this with a Web API controller the following model binder will help (implmenting System.Web.Http.ModelBinding.IModelBinder:

public class SagePayTransactionNotificationModelBinder : IModelBinder
{
    public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
    {
        var model = actionContext.Request.Content.ReadAsStringAsync()
                        .ContinueWith(t =>
                            {
                                var formCollection = HttpUtility.ParseQueryString(t.Result);

                                return new SagePayTransactionNotification
                                {
                                    VPSProtocol = formCollection["VPSProtocol"],
                                    TxType = formCollection["TxType"],
                                    VendorTxCode = formCollection["VendorTxCode"],
                                    VPSTxId = formCollection["VPSTxId"],
                                    Status = formCollection["Status"],
                                    StatusDetail = formCollection["StatusDetail"],
                                    TxAuthNo = formCollection["TxAuthNo"],
                                    AVSCV2 = formCollection["AVSCV2"],
                                    AddressResult = formCollection["AddressResult"],
                                    PostcodeResult = formCollection["PostcodeResult"],
                                    CV2Result = formCollection["CV2Result"],
                                    GiftAid = formCollection["GiftAid"],
                                    ThreeDSecureStatus = formCollection["3DSecureStatus"],
                                    CAVV = formCollection["CAVV"],
                                    CardType = formCollection["CardType"],
                                    Last4Digits = formCollection["Last4Digits"],
                                    VPSSignature = formCollection["VPSSignature"]
                                };
                            })
                        .Result;

        bindingContext.Model = model;

        return true;
    }
}

Which can be applied to your Controller method like so:

[ActionName("Notifications")]
public string Post([ModelBinder(typeof(SagePayTransactionNotificationModelBinder))] SagePayTransactionNotification notification)
{
    ...
}

I know this is not strictly what was asked for in the question but this is where I ended up when I had exactly the same problem as the OP and thought it might be useful for someone else later down the line.

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top