Search code examples
c#jsonasp.net-coreasp.net-core-webapimedia-type

How to pass a serialized JSON payload to an ASP.NET Core Web API controller


This API endpoint is meant to receive a serialized JSON object and pass it over to the underlying reporting engine.

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

namespace Server.Controllers
{
    [ApiController]
    [Route("[controller]")]
    public class ReportingController : ControllerBase
    {
        [HttpPost("generate")]
        [ProducesResponseType(StatusCodes.Status201Created)]
        [ProducesResponseType(StatusCodes.Status400BadRequest)]
        [Consumes("application/json")]
        public async Task<ActionResult> Generate([FromBody] string reportModel)
        {
            try
            {
                dynamic data = JObject.Parse(reportModel);
                string templateName = data.TemplateName;
                return Ok(templateName);
            }
            catch (Exception ex)
            {
                return BadRequest(ex.Message);
            }
        }
    }
}

I'm struggling with it's usage, how to pass over a stringified JSON object?

Like for example:

{ "TemplateName": "myTemplate" }

Covered it in a unit test. Learned already that it needs to start and end with double quote (").

In the code below the method JsonStringToStringContent needs to be adjusted so that the double quotes within the JSON get proper escapes as well.

Currently the controller is not even hit, ASP.NET Core returns a 400 status code beforehand already.

I am also not sure about the media type application/json.

Many thanks for your help!

using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.Extensions.Configuration;
using System.Text;

namespace TestProject
{
    public class UnitTest1(TestServerFixture fixture) : IClassFixture<TestServerFixture>
    {
        [Fact]
        public async Task Test1()
        {
            var json = "{\"TemplateName\":\"myTemplate\"}";
            var jsonStringContent = JsonStringToStringContent(json);
            using var content = new StringContent(jsonStringContent, Encoding.UTF8, "application/json");
            var server = fixture.Server;
            var request = server
                .CreateRequest("/Reporting/generate")
                .And(req => req.Content = content);
            var response = await request.PostAsync();
            response.EnsureSuccessStatusCode();
        }

        private string JsonStringToStringContent(string json)
        {
            // current result: "{"TemplateName":"myTemplate"}"
            // expected result: ?
            return $"\"{json}\"";
        }
    }

    public sealed class TestServerFixture : WebApplicationFactory<Server.Program>
    {
        private HttpClient client;
        public HttpClient Client
        {
            get
            {
                client ??= Server.CreateClient();
                return client;
            }
        }

        protected override void ConfigureWebHost(IWebHostBuilder builder)
        {
            FileInfo fileInfo = new(Path.Combine(Directory.GetCurrentDirectory(), "appsettings.json"));
            var configurationBuilder = new ConfigurationBuilder();
            var configuration = configurationBuilder.Build();

            builder
                .UseConfiguration(configuration)
                .ConfigureAppConfiguration(builder =>
                {
                    builder.AddJsonFile(fileInfo.FullName);
                });
        }
    }
}

Solution

  • No need to use a string quote ("), I'd suggest getting the body as an object and then converting it:

    public async Task<ActionResult> Generate([FromBody] object reportModel)
    {
        try
        {
            dynamic data = JObject.Parse(reportModel.ToString());
            string templateName = data.TemplateName;
            return Ok(templateName);
        }
        catch (Exception ex)
        {
            return BadRequest(ex.Message);
        }
    }
    

    Also if you know the body is always in JSON format then you can directly map the body to JObject:

    public async Task<ActionResult> Generate([FromBody] JObject reportModel)
    {
        try
        {
            dynamic data = reportModel;
            string templateName = data.TemplateName;
            return Ok(templateName);
        }
        catch (Exception ex)
        {
            return BadRequest(ex.Message);
        }
    }