A RetroSearch Logo

Home - News ( United States | United Kingdom | Italy | Germany ) - Football scores

Search Query:

Showing content from https://stackoverflow.com/questions/79718638/asp-net-core-web-api-null-vs-undefined below:

c# - ASP.NET Core Web API [NULL vs UNDEFINED]

.NET Core MVC Web API [NULL vs UNDEFINED]

For - <TargetFramework>net7.0</TargetFramework> or newer.

Step 1 of 3
// https://stackoverflow.com/a/79718639
using System.Linq.Expressions;
using System.Reflection;
using System.Text.Json;
using System.Text.Json.Nodes;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Formatters;

namespace XXX.XXX;

public class JsonKeys<T> where T : class
{
    private string[] _jsonKeys = Array.Empty<string>();

    public bool HasKey<TReturn>(Expression<Func<T, TReturn>> expression) =>
        expression.Body is MemberExpression { Member.MemberType: MemberTypes.Property } memberExpression &&
        _jsonKeys.Contains(memberExpression.Member.Name, StringComparer.InvariantCultureIgnoreCase);

    public void SetJsonKeys(string[] jsonKeys)
    {
        if (_jsonKeys.Length > 0) throw new InvalidOperationException($"{nameof(jsonKeys)} is already set");
        _jsonKeys = jsonKeys;
    }
}

public class JsonKeysInputFormatter : SystemTextJsonInputFormatter
{
    public JsonKeysInputFormatter(
        JsonOptions options,
        ILogger<SystemTextJsonInputFormatter> logger
    ) : base(options, logger)
    {
    }

    public override async Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context)
    {
        var httpContext = context.HttpContext;
        var memoryStream = new MemoryStream();
        await httpContext.Request.Body.CopyToAsync(memoryStream);
        memoryStream.Seek(0, SeekOrigin.Begin);
        httpContext.Request.Body = memoryStream;
        var inputFormatterResult = await base.ReadRequestBodyAsync(context);
        memoryStream.Seek(0, SeekOrigin.Begin);
        var jsonNode = await JsonSerializer.DeserializeAsync<JsonNode>(memoryStream, SerializerOptions);
        AddKeyExistence(inputFormatterResult.Model, jsonNode);
        await memoryStream.DisposeAsync();
        return inputFormatterResult;
    }

    private static void AddKeyExistence(object? model, JsonNode? jsonNode)
    {
        if (model == null || jsonNode == null || model.GetType().IsArray || !model.GetType().IsClass) return;

        // https://learn.microsoft.com/dotnet/fundamentals/reflection/how-to-examine-and-instantiate-generic-types-with-reflection
        var modelsGenericType = model.GetType().BaseType?.GetGenericArguments().SingleOrDefault();
        if (modelsGenericType == null) return;
        if (!model.GetType().IsAssignableTo(typeof(JsonKeys<>).MakeGenericType(modelsGenericType))) return;

        var setJsonKeysMethod = typeof(JsonKeys<>).GetMethod(nameof(JsonKeys<object>.SetJsonKeys));
        if (setJsonKeysMethod == null) return;
        var modelSetJsonKeysMethod = model.GetType().GetMethod(
            setJsonKeysMethod.Name,
            BindingFlags.Public | BindingFlags.Instance,
            setJsonKeysMethod.GetParameters().Select(a => a.ParameterType).ToArray()
        );
        if (modelSetJsonKeysMethod == null) return;

        JsonObject jsonObject;

        try
        {
            jsonObject = jsonNode.AsObject();
        }
        catch (Exception e)
        {
            Console.WriteLine(e);
            return;
        }

        var modelType = model.GetType();
        foreach (var propertyInfo in modelType.GetProperties())
        {
            if (propertyInfo.GetMethod == null) continue;
            var getValue = propertyInfo.GetMethod.Invoke(model, null);
            if (!jsonObject.TryGetPropertyValue(propertyInfo.Name, out var value)) continue;
            AddKeyExistence(getValue, value);
        }

        modelSetJsonKeysMethod.Invoke(model, new object[] { jsonObject.Select(a => a.Key).ToArray() });
    }
}
Step 2 of 3
// https://stackoverflow.com/a/79718639
using dotenv.net;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.Extensions.Options;

WebApplication? app = null;
var builder = WebApplication.CreateBuilder(args);

// ...

builder.Services.AddControllers(mvcOptions =>
{
    ArgumentNullException.ThrowIfNull(app);
    var loggerFactory = app.Services.GetRequiredService<ILoggerFactory>();
    var jsonOptions = app.Services.GetRequiredService<IOptions<JsonOptions>>();
    // https://learn.microsoft.com/aspnet/core/web-api/advanced/custom-formatters?view=aspnetcore-7.0
    mvcOptions.InputFormatters.Insert(0,
        new JsonKeysInputFormatter(jsonOptions.Value, loggerFactory.CreateLogger<SystemTextJsonInputFormatter>()));
});

// ...

app = builder.Build();
Step 3 of 3
// https://stackoverflow.com/a/79718639
using XXX.XXX;
using Microsoft.AspNetCore.Mvc;

namespace YYY.YYY.YYY;

public class Nested : JsonKeys<Nested>
{
    public string? NestedOne { get; set; }
}

public class MyRequest : JsonKeys<MyRequest>
{
    public string? One { get; set; }

    public string? Two { get; set; }

    public string? Three { get; set; }

    public Nested? Nested { get; set; }

    public int[]? Four { get; set; }
}

[ApiController]
[Route("api/dev")]
public class DevTestJsonKeysController : ControllerBase
{
    [HttpPost(nameof(TestJsonKeys))]
    public async Task<IActionResult> TestJsonKeys([FromBody] MyRequest request)
    {
        request.HasKey(x => x.Nested);
        request.Nested?.HasKey(x => x.NestedOne);
        return Ok();
    }
}

RetroSearch is an open source project built by @garambo | Open a GitHub Issue

Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo

HTML: 3.2 | Encoding: UTF-8 | Version: 0.7.4