8

I have a client with a MVC application that accepts raw JSON requests. The ModelBinder maps incoming key/value pairs to the Controller Model properties, no problem there.

The problem is they want to throw an error when they send an invalid key/value pair, and for the life of me I cannot find the raw incoming data.

For example, if I have a model with a string property MName but in their JSON request they send "MiddleName":"M", the ModelBinder will toss this invalid key and leave the MName property blank. This does not throw an error and ModelState.IsValid returns true.

I know that I could throw a [Required] attribute on the property, but that's not right, either, since there might be null values for that property, and still doesn't get to the problem of detecting key/value pairs that don't belong.

This isn't a question of over-posting; I'm not trying to prevent an incoming value from binding to the model. I'm trying to detect when an incoming value just didn't map to anything in the model.

Since the values are coming in as application/json in the request body, I'm not having luck even accessing, let along counting or enumerating, the raw request data. I can pull name/value pairs from ModelState.Keys but that only includes the fields that were successfully mapped. None of these keys are in the Request[] collection.

And yes, this is in ASP.NET MVC 5, not WebApi. Does WebAPI handle this any differently?

Any ideas?

Example:

application/json: { "FName":"Joe", "MName":"M", "LName":"Blow" }

public class PersonModel
{
    public string FName { get; set; }
    public string LName { get; set; }
}

public class PersonController() : Controller
{
    public ActionResult Save(PersonModel person)
    {
        if(ModelState.IsValid) // returns true
        // do things
        return View(person)
    }
}
Neil Laslett
  • 2,019
  • 22
  • 22
  • How do you deserialize the JSON to model property. Can you kindly put that code. – Unbreakable Jul 28 '17 at 03:42
  • I will tell something obvious, but you, probably, want [to play with ModelBinders](https://learn.microsoft.com/en-us/aspnet/core/mvc/advanced/custom-model-binding). You can also use `Dictionary` as your action argument, but its really ugly :D – Yeldar Kurmangaliyev Jul 28 '17 at 03:43
  • I don't think ModelState.IsValid will catch such a mismatch. You need to handle it in ModelBinder. I am a newbie just trying to help. I might be completely wrong. – Unbreakable Jul 28 '17 at 03:45
  • @Unbreakable Deserialization is done automatically by the default model binder. I'm not actually sure how it's working here. I was hoping to keep it that way. :-) It's been awhile since I rolled my own ModelBinder but you might be right about needing to write my own. – Neil Laslett Jul 28 '17 at 03:50
  • Or as a work around. If you (kind of) know about the mismatched property that you can expect from the JSON. for example "MiddleName". You can set a Model Property with that name and then instead of ModelState.IsValid you can check whether that extraneous property is set. If it's set after the deserialization then you are sure that invalid JSON data came and if it's empty then you are good. – Unbreakable Jul 28 '17 at 04:01
  • It's a dirty non scalable way. But I am just throwing it out there – Unbreakable Jul 28 '17 at 04:01
  • May be this can help: https://stackoverflow.com/questions/43215165/custom-validation-mvc-5 – Oleg Tamarintsev Jul 28 '17 at 06:35

2 Answers2

1

You can read json data that was sent in the request from the Request.InputStream and deserialize to an object. You can then compare the property names that were posted with the property names of your model

public ActionResult Save(PersonModel person)
{
    // Get the models property names
    var modelProperties = person.GetType().GetProperties().Select(x => x.Name);
   // Read the InputStream
    StreamReader reader = new StreamReader(Request.InputStream);
    reader.BaseStream.Position = 0;
    string jsonText = reader.ReadToEnd();
    // Deserialize to object and read property names
    JavaScriptSerializer serializer = new JavaScriptSerializer();
    object jsonObject = serializer.DeserializeObject(jsonText);
    IDictionary<string, object> dictionary = jsonObject as IDictionary<string, object>;
    var jsonProperties = jsonObject.Keys;
    // Get the json property names which do not match the model property names
    List<string> invalidProperties = jsonProperties.Except(modelProperties, StringComparer.OrdinalIgnoreCase).ToList();

If invalidProperties contains values, then you could throw your error (and perhaps use String.Join() to include the list of the invalid property names and the list of actual model property names in the error message that is returned to the client).

0

well you can try out something like

  public ActionResult Save(object commingJson)
    {
         PersonModel person = new PersonModel();
          try{
           person.FName = commingJson.FName;
           person.LName = commingJson.LName ;
           }
          catch(Exception)
           {
             //Binding Failed invalid json
            }

         int countInObject = GetAttrCount(commingJson);
         int countInModel = GetAttrCount(person);
              if(countInObject != countInModel )
             {
               //Json have more or less value then model  
             }
        if(ModelState.IsValid) // returns true
        // do things
        return View(person)
    }



   public int GetAttrCount(obecjct countObject) 
{
    Type type = typeof(countObject);
    int attributeCount = 0;
    foreach(PropertyInfo property in type.GetProperties())
    {
     attributeCount += property.GetCustomAttributes(false).Length;
    }
return attributeCount ;
}
RAHUL S R
  • 1,569
  • 1
  • 11
  • 20
  • I am little confused. How do these lines `person.FName = commingJson.FName; person.LName = commingJson.LName ;` are going to `throw exception` ? – mmushtaq Jul 28 '17 at 06:02
  • @mmushtaq what if the coming json dosent have a property FName and LName? ie {Name:Rahul,LastName:SR} + its just a checking to ake sure we initialize the comming value to model – RAHUL S R Jul 28 '17 at 06:46