2

I'm sending a JSON like this:

[
{col1: 'value', col2: 'value'}, 
{col1: 'value2', col2: 'value2'},
...
]

The action in my controller has a List parameter that is requiring a custom model binder, like this:

public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
        {
            var contentType = controllerContext.HttpContext.Request.ContentType;

            String bodyText;

            Stream stream = null;
            try
            {
                stream = controllerContext.HttpContext.Request.InputStream;
                stream.Seek(0, SeekOrigin.Begin);
                using (var reader = new StreamReader(stream))
                {
                    stream = null;
                    bodyText = reader.ReadToEnd();
                }
            }
            finally
            {
                if (stream != null)
                    stream.Dispose();
            }

            if (string.IsNullOrEmpty(bodyText))
            {
                return null;
            }

            var model = new JavaScriptSerializer().Deserialize<T>(bodyText);
            return model;

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

It's working, except that it is not considering the data annotations for validations (required, range, etc).

How can I get this working with validation?

UPDATE

Controller Action

[HttpPost]
        public ActionResult ActionName([ModelBinder(typeof(JsonArrayValidationModelBinder<List<EntityName>>))]List<EntityName> viewModel)

Entity

public class EntityName
{
        [Display(Name = "Data Entrada")]
        [DataType(DataType.Date)]
        [Required]
        public DateTime? DataEntrada { get; set; }
// ....
}
2
  • Please show Controller, Model and annotations. Commented Mar 27, 2014 at 14:23
  • Code added to question Commented Mar 27, 2014 at 15:30

2 Answers 2

1

I have revised my answer. There were a few problems that I ran into when trying to get this working. Detailed below is the problem and the solution I used.

The json: The json you provided did not match the Model you provided. So I assumed the json string should have included something like this:

`DataEntrada: "1/1/2014"`

The model: Your model describes only EntityName. The deserialized json is a list. These are two different things. So I modified the json to be an object that defines EntityNames (list of EntityName), like this:

 `data = { EntityNames: [{ DataEntrada: "1/1/2014" }] };`

and then I implemented this class..this will be the result of deserialization:

public class EntityInfo
{
    public EntityName[] EntityNames { get; set; }
}

and finally, modified the ActionMethod like so:

public JsonResult SaveActionName([ModelBinder(typeof(JsonArrayValidationModelBinder<EntityInfo>))]EntityInfo viewModel)

Validation: Validating EntityNames was not as easy to implement as I thought it would be. I could not get the validation attribute for EntityName to fire during model binding (being a member of a list). So, I implemented a custom validator derived from 'ValidationAttribute' like this:

public class EntityNamesValidation : ValidationAttribute
{
    public override bool IsValid(object value)
    {
        EntityName[] list = (EntityName[])value;
        foreach (EntityName e in list)
        {
            if (string.IsNullOrEmpty(e.DataEntrada.ToString()))
                return false;

            // more checks performed here

        }
        return true;
    }
}

and then I applied EntityNamesValidation attribute to EntityNames and EntityInfo, like so:

[EntityNamesValidation]
    public EntityName[] EntityNames { get; set; }

Incorrect model during bind: The JsonArrayValidationModelBinder was using a bindingContext that did not have an instance of anything. If you debug BindModel before base.BindModel you will see that bindingContext.Model is null. So what I did was set bindingContext.ModelMetadata.Model = model after deserialization and before the call to base.BindModel. I also moved base.BindModel in the code to fire just before model is returned...see below

public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)  
{
    [...]

    bindingContext.ModelMetadata.Model = model;
    base.BindModel(controllerContext, bindingContext);

    return model;
}

Verification: I did not unit test this, but I did place a breakpoint in the ActionMethod. I then used the following json:

data = { EntityNames: [{ DataEntrada: "1/1/2014" }, { DataEntrada: null }] };

when the code reached the breakpoint, ModelState.IsValid is false. I then changed json to this:

data = { EntityNames: [{ DataEntrada: "1/1/2014" }, { DataEntrada: "2/19/2014" }] };

when the code reached the breakpoint, ModelState.IsValid is true.

This approach works, but is not ideal. I think you want validation to occur without creating custom code and use MVC to handle this.

I hope this gets you a step further.

ALL THE CODE

javascript

        data = { EntityNames: [{ DataEntrada: "1/1/2014" }, { DataEntrada: null }] };
        var jsonOfLog = JSON.stringify(data);
        $.ajax({
            type: 'POST',
            dataType: 'text',
            url: "/EntityData/SaveActionName",
            data: jsonOfLog,
            success: function (data) {
                alert(data);
            },
            error: function (result) {
                alert(result);
            }
                ,
            async: false
        });

models

public class EntityInfo
{
    [EntityNamesValidation]
    public EntityName[] EntityNames { get; set; }
}

public class EntityName
{
    [Display(Name = "Data Entrada")]
    [DataType(DataType.Date)]
    [Required]
    public DateTime? DataEntrada { get; set; }
}

custom validator

public class EntityNamesValidation : ValidationAttribute
{
    public override bool IsValid(object value)
    {
        EntityName[] list = (EntityName[])value;
        foreach (EntityName e in list)
        {
            if (string.IsNullOrEmpty(e.DataEntrada.ToString()))
                return false;

            // more checks performed here

        }
        return true;
    }
}

BindModel

    public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        var contentType = controllerContext.HttpContext.Request.ContentType;
        String bodyText;
        Stream stream = null;
        try
        {
            stream = controllerContext.HttpContext.Request.InputStream;
            stream.Seek(0, SeekOrigin.Begin);
            using (var reader = new StreamReader(stream))
            {
                stream = null;
                bodyText = reader.ReadToEnd();
            }
        }
        finally
        {
            if (stream != null)
                stream.Dispose();
        }
        if (string.IsNullOrEmpty(bodyText))
        {
            return null;
        }
        var model = new JavaScriptSerializer().Deserialize<T>(bodyText);
        bindingContext.ModelMetadata.Model = model;
        base.BindModel(controllerContext, bindingContext);

        return model;
    }

ActionMethod

[HttpPost]
public JsonResult SaveActionName([ModelBinder(typeof(JsonArrayValidationModelBinder<EntityInfo>))]EntityInfo viewModel)
Sign up to request clarification or add additional context in comments.

Comments

0

Deriving from DefaultModelBinder will give you what you are looking for. In your override, call base method, like so

public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        // base BindModel should validate your model 
        base.BindModel(controllerContext, bindingContext);

        // (optional) Capture validation result
        bool ModelIsValid = bindingContext.ModelState.IsValid;

        var contentType = controllerContext.HttpContext.Request.ContentType;

        [...]
    }

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.