ASP.NET Core 7 when receiving an object data from Http POST binding invalid properties

62 views Asked by At

When receiving an object from a client on a Http API endpoint - and that object comes in as a C# object (FromBody) - is there any way to detect if the client has added a property that is not part of the expected object?

Say for instance we were to receive an object of a class that was defined as:

public class SomeData 
{
    public int id { get; set; }
    public string name { get; set; }
}

and the data that was sent from a client is

{
    "id": 56,
    "name": "JimBob",
    "Something": "Some other piece of data not expected"
}

Is there any way to get told by the .NET data binders that we received an invalid property?

2

There are 2 answers

0
Emre Bener On

In terms of "being notified" of unexpected properties, there is no built-in way. Also, you can't simply tell model binder to "add any extra properties to a model". However, you can implement a middleware to intercept the received body data and alter it however you want. With such middleware, you can theoretically achieve what you want.

Here is what I did in a project. It involved taking data from body and parsing it into a meaningful model.

public class RequestBodyParser
{
    private readonly RequestDelegate _next;

    public RequestBodyParser(RequestDelegate next)
    {
        _next = next;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        if (context.Request.Path.Value.EndsWith("GetPaged")) // which exact requests do you want to process? you probably don't want to process every request, so specify a path here.
        {
            try
            {
                // Read the request body
                var reqBody = await new StreamReader(context.Request.Body).ReadToEndAsync();
                if (reqBody == null || !reqBody.StartsWith("draw")) await _next(context); // just an extra check

                // URL decode the request body
                var decodedBody = Uri.UnescapeDataString(reqBody);
                if (decodedBody == null) await _next(context);

                // Split the decoded body into key-value pairs
                var keyValuePairs = decodedBody.Split('&')
                    .Select(part => part.Split('='))
                    .ToDictionary(split => split[0], split => split.Length > 1 ? split[1] : "");

                DataTablesRequestModel dataTableReqModel = new DataTablesRequestModel(); // in my case, I have a certain model and I instantiate an object in its type to inject it into HttpRequest

                // for example, here I extract a certain key's value and use it
                keyValuePairs.TryGetValue("draw", out string draw);
                dataTableReqModel.Draw = Convert.ToInt32(draw);

                // do whatever else you need to do..

                context.Items["ReqBody"] = dataTableReqModel;// Store the parsed data in HttpContext.Items
            }
            catch (Exception ex)
            {
                // TODO log
                await _next(context);
            }
        }

        await _next(context);
    }
}

And register the middleware like this. You can put it just before routing middleware:

app.UseMiddleware<RequestBodyParser>(); // our custom middleware    
app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

And here is how you would use the custom request data in your controller actions:

[HttpPost]
public async Task<IActionResult> GetPaged()
{
    try
    {
        var reqBody = (DataTablesRequestModel?)HttpContext.Items["ReqBody"];
        // do stuff...

In theory, you can parse body into your expected model (which contain all expected properties), and add a Dictionary<string, string> property and add any extra/unexpected properties into it as a key-value pair. The keyValuePairs variable in middleware will contain everything in the request body. What the middleware does is inject an object into HttpContext.Items, which you can access within the same request scope in preceding handlers, similar to passing a ViewData or TempData to views.

All that being said, you should keep security in mind. Accepting any extra properties seems like a risky approach. Having a predefined list of all expected properties would be an infinitely healthier approach.

0
Ruikai Feng On

Since you've mentioned you are working with .NET 7,it would deserialize the jsonstring in request body to target object with System.Text.Json

The most simple solution I know:

create a DTO:

public class SomeDataDTO:SomeData 
{
    [JsonExtensionData]
    public Dictionary<string, JsonElement>? ExtensionData { get; set; }
}

check if ExtensionData is null in controller:

[HttpPost]
public IActionResult Post([FromBody]SomeDataDTO someData)
{
    if (someData.ExtensionData != null)
    {
        ModelState.AddModelError("", "JsonOverFlow");
        return BadRequest();
    }
    return Ok();
}

The document related