9

How do I convert ModelState to JSON in the same format that ASP.NET Core does?

I know I can do BadRequest(ModelState) and it will return JSON to the client. How does it convert ModelState to JSON? And more importantly, how can I use what it is using?

My goal is to log the ModelState as JSON in our log "files".

i.e. var blah = ModelState.ToJson()

Deilan
  • 4,740
  • 3
  • 39
  • 52
spottedmahn
  • 14,823
  • 13
  • 108
  • 178
  • 4
    JSON.NET is built-in. It's what ASP.NET Core uses to serialize objects for responses, so you can simply do the same thing: `JsonCovert.SerializeObject(ModelState)` – Chris Pratt Aug 24 '18 at 19:27
  • Hi @ChrisPratt - that serializes the whole object. ASP.NET returns a cleaner version: `{ "Name": [ "The Name field is required." ] }` – spottedmahn Aug 24 '18 at 19:59
  • 1
    Use `ModelState.Errors` then. – Chris Pratt Aug 24 '18 at 20:09
  • FYI, there is no `ModelState.Errors`. Apparently, each dictionary entry has an `Errors` property though. Either way that outputs too much info. `SerializableError` provides what I'm looking for though! @ChrisPratt – spottedmahn Aug 27 '18 at 16:59

2 Answers2

15

How does it convert ModelState to JSON?

The SerializableError Class provides this functionality.

And more importantly, how can I use what it is using?

using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;

//get key(s) and error message(s) from the ModelState
var serializableModelState = new SerializableError(ModelState);

//convert to a string
var modelStateJson = JsonConvert.SerializeObject(serializableModelState);

//log it
logger.LogInformation("Bad Model State", modelStateJson);

Sample output

{
    "Name": ["The Name field is required."]
}

I was able to figure this out by digging through the ASP.NET Core MVC source code. The interesting part of source code from the SerializableError class:

foreach (var keyModelStatePair in modelState)
{
    var key = keyModelStatePair.Key;
    var errors = keyModelStatePair.Value.Errors;
    if (errors != null && errors.Count > 0)
    {
        var errorMessages = errors.Select(error =>
        {
            return string.IsNullOrEmpty(error.ErrorMessage) ?
                Resources.SerializableError_DefaultError : error.ErrorMessage;
        }).ToArray();

        Add(key, errorMessages);
    }
}
spottedmahn
  • 14,823
  • 13
  • 108
  • 178
1

Just to get a list of the error messages for each Model Property that failed validation in the manner you want as indicated above using an extension method. i.e ModelState.ToJson(), you need create a static class with a static function ToJson(...). The code example will look something like this.

public static class ModelStateExtensions
{
    /// <summary>
    /// Reads all the error messages in a <see cref="ModelStateDictionary"/> as 
    /// a collection and returns a JSON <see cref="string"/> of the list.
    /// </summary>
    /// <param name="modelstate">Current modelstate assuming that you've checked
    /// and confirmed that is Invalid using <see 
    /// cref="ModelStateDictionary.IsValid"/>
    /// </param>
    /// <returns>
    /// Collection of validation errors for the model as a JSON string.
    /// </returns>
    public static string ToJson(this ModelStateDictionary modelstate)
    {
        List<string> errors = modelstate.Values
                                        .SelectMany(x => x.Errors)
                                        .Select(x => x.ErrorMessage)
                                        .ToList();
        return JsonConvert.SerializeObject(errors);
    }
}

The ModelState property on every controller is normally a ModelStateDictionary, so if we want an additional method for it, that is the class we need to extend. You can learn more about extension methods in C# by following this Link.

Let now see how to use our extension method in a sample controller action:

public IActionResult Create(UserViewModel model)
{
    if(!ModelState.IsValid)
    {
        string json = ModelState.ToJson();

        // insert code to log json to file here

        return BadRequest(ModelState);
    }
}
Timothy Macharia
  • 2,641
  • 1
  • 20
  • 27