Using Kestrel and middleware how to figure out the class type that was passed in from a Post or Put request?

64 Views Asked by At

I am using .NET 8 and Kestrel. I am trying to get the class type for any Post or Put requests.

My endpoint code looks something like this:

endpoints.MapPost($"{_basePath}/registerMyObject", [AllowAnonymous] async ([FromBody] MyOject myObject) =>
 {
   // my code
 }

My middleware looks something like:

public class ValidateModelMiddleware : IMiddleware
{
    private ITokenService _tokenService;
    private IServiceProvider _serviceProvider;


    public ValidateModelMiddleware([FromServices] ITokenService tokenService, [FromServices] IServiceProvider serviceProvider)
    {
        _tokenService = tokenService;
        _logger = logger;
    }

    public async Task InvokeAsync(HttpContext context, RequestDelegate next)
    {

        if (context.Request.Method == "POST" || context.Request.Method == "PUT")
        {
               // I need to figure out the type that was passed in.  For instance 
        }
     }

So when a registerMyObject request is received I want to get the Type MyOject so that I can deserilize that json text directly into that class. And if a different Post / Put request is used and it has a different class type then I want to get that corresponding type. Is there anyway to do this?

2

There are 2 best solutions below

1
Qiang Fu On

You could choose which type to de-serialize depending on if the jsonstring "keys" matches which class type fileds. For instance:
If there are ClassA and ClassB both implement MyObject.

    public class ClassA  :MyObject
    {
        public string Name { get; set; }
        public string Address { get; set; } 
    }
    public class ClassB : MyObject
    {
        public string Name { get; set; }
        public string Age { get; set; }
    }

You could use following code to get a "classResult" string which matches jsonstring.

        public async Task InvokeAsync(HttpContext context, RequestDelegate next)
        {
            var classResult = "";
            //Get JsonString
            var bodyStr = "";
            using (StreamReader reader
                      = new StreamReader(context.Request.Body, Encoding.UTF8, true, 1024, true))
            {
                bodyStr = await reader.ReadToEndAsync();
            }
            //Parse to JObject and get the keys
            var jobject = (JObject)JsonConvert.DeserializeObject(bodyStr);
            var keys = jobject.Properties().Select(p => p.Name);

            //Get all types implement "MyObject"
            var parenttype = typeof(MyObject);
            var types = AppDomain.CurrentDomain.GetAssemblies()
                .SelectMany(s => s.GetTypes())
                .Where(p => parenttype.IsAssignableFrom(p));

            //Compare which type matches keys
            foreach (var type in types)
            {
                if (Match(keys, type))
                {
                    classResult=type.Name;
                }
            }

            await next(context);
        }

        //Method to compare if keys matches all the fields of a Class type
        private bool Match(IEnumerable<string>? keys,Type type)  
        {
            var fieldNames =type .GetProperties().Select(field => field.Name);
            if (!keys.Except(fieldNames).Any() && !fieldNames.Except(keys).Any())
            {
                return true;
            }
            else
            {
                return false;
            }
        }
0
Guru Stron On

Without full setup it is hard to tell how exactly you need to handle the requirement.

If you are using Minimal APIs then you can leverage endpoint filters (for example like here):

app.MapPost("/reports", (ReportDTO activityReport) => ...)
    .AddEndpointFilter(async (context, next) =>
    {
        // collection will contain ReportDTO activityReport itself:
        IList<object?> mappedArguments = context.Arguments;
        return await next(context);
    });

If you want this to be a middleware then for endpoint routing you use IEndpointFeature (middleware should be registered in appropriate place, also note that for controllers handling can be different):

// lambda is analog for you InvokeAsync method
app.Use(async (context, next) =>
{
    // TODO: check if POST/PUT
    var endpointFeature = context.Features.Get<IEndpointFeature>();
    var endpointMetadataCollection = endpointFeature.Endpoint.Metadata;
    var acceptsMetadata = endpointMetadataCollection.GetMetadata<AcceptsMetadata>();
    var requestType = acceptsMetadata.RequestType; // typeof(ReportDTO)
    // ...
    await next();
});

Obviously first approach should be better in terms of perfromance since you will not need to buffer and bind data multiple times.