简体   繁体   中英

Accept x-www-form-urlencoded in Minimal API NET 6

I'm trying to send a "Content-Type" header: "application/x-www-form-urlencoded" to a minimal api and I get the following error:

Microsoft.AspNetCore.Http.BadHttpRequestException: Expected a supported JSON media type but got "application/x-www-form-urlencoded".

Here's the endpoint:

app.MapPost("/api/formulario/imovel-web/cadastrar",
            async (LeadRequest request, ILeadService service, ILogService logService, ICorretorMrvRepository corretorMrvRepository) =>
{
    request.Origem = "imovelweb";

    try
    {
        if (string.IsNullOrEmpty(request.Token))
        {
            logService.Error("leads-api", "imovel-web: Acesso não autorizado (Token não encontrado).", Environment.StackTrace, request: request.Serialize());

            return Result.ResponseFailed("Acesso não autorizado (Token não encontrado).");
        }
        else
        {
            var idParceiro = await corretorMrvRepository.ObterIdParceiro("imovelweb");
            var origem = string.Empty;

            if (idParceiro != Guid.Empty)
            {

                var jwt = request.Token;
                var handler = new JwtSecurityTokenHandler();
                var data = handler.ReadJwtToken(request.Token);

                var autorizado = data.Claims.Any(x => x.Type.Equals("origin") && x.Value.Equals("imovelweb")) &&
                                 data.Claims.Any(x => x.Type.Equals("key") && x.Value.Equals(idParceiro.ToString()));

                if (!autorizado)
                {
                    logService.Error("leads-api", "imovel-web: Acesso não autorizado.", Environment.StackTrace, request: request.Serialize());

                    return Result.ResponseFailed("Acesso não autorizado (Dados do token não encontrados).");
                }

                request.Origem = await corretorMrvRepository.ValidaParceiroLead(idParceiro);
            }
            else
            {
                logService.Error("leads-api", "imovel-web: Acesso não autorizado (Id do parceiro não encontrado).", Environment.StackTrace, request: request.Serialize());

                return Result.ResponseFailed("Acesso não autorizado (Id do parceiro não encontrado).");
            }

            if (!string.IsNullOrEmpty(request.txtDdd) && !string.IsNullOrEmpty(request.txtTelefone))
                request.txtTelefone = $"{request.txtDdd}{request.txtTelefone}".FormatPhone(false);

            var responseResult = await service.Cadastrar(request);

            return Result.Response<ResponseResult>(responseResult, responseResult.Success);
        }
    }
    catch (Exception e)
    {
        logService.Error("leads-api", new ExceptionForLog(e.Message, e));
        return Result.ResponseFailed(e.Message);
    }
})
.Accepts<LeadRequest>("application/x-www-form-urlencoded")
.Produces<ResponseResult>()
.ProducesValidationProblem()
.WithTags("Imóvel Web")
.WithName("CadastrarImovelWeb");

在此处输入图像描述

This is the same problem as .NET 6 Minimal API and multipart/form-data . The answer explains it's not currently possible to bind form values in minimal APIs. However, there are a couple of work-arounds which are detailed in the answer:

  1. Custom Model Binding via BindAsync
  2. Map the values straight from HttpContext

Utilizing MVC Core model binding

However, there is another method where we can leverage some of the model-binding mechanisms that already exist in MVC, in your minimal API. The below is inspired by Abuhakmeh's page Using ASP.NET Core MVC Value Providers With Minimal APIs .

First, add a BindAsync method to your model ( LeadRequest ) which generates an instance from the request context:

public class LeadRequest
{
    // class properties
    ...

    public static async ValueTask<LeadRequest?> BindAsync(HttpContext httpContext, ParameterInfo parameter)
    {
        return await httpContext.BindFromForm<LeadRequest>();
    }
}

where I've assumed LeadRequest is a class but this will work with records too.

Now, you need to add an extension method for BindFromForm<T>() . This utilizes model-binding available in Asp.Net MVC Core.

using System.Globalization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.ModelBinding;

namespace YourProjectNamespace;

public static class BindingExtensions
{
    public static async Task<T?> BindFromForm<T>(this HttpContext httpContext)
    {
        var serviceProvider = httpContext.RequestServices;
        var factory = serviceProvider.GetRequiredService<IModelBinderFactory>();
        var metadataProvider = serviceProvider.GetRequiredService<IModelMetadataProvider>();

        var metadata = metadataProvider.GetMetadataForType(typeof(T));
        var modelBinder = factory.CreateBinder(new() {
            Metadata = metadata
        });

        var context = new DefaultModelBindingContext
        {
            ModelMetadata = metadata,
            ModelName = string.Empty,
            ValueProvider = new FormValueProvider(
                BindingSource.Form,
                httpContext.Request.Form,
                CultureInfo.InvariantCulture
            ),
            ActionContext = new ActionContext(
                httpContext, 
                new RouteData(), 
                new ActionDescriptor()),
            ModelState = new ModelStateDictionary()
        };
        await modelBinder.BindModelAsync(context);
        return (T?) context.Result.Model;
    }
}

Finally, you need to register MVC Core with your application services:

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddMvcCore();

...

app.MapPost("/api/formulario/imovel-web/cadastrar",
    async (LeadRequest request, ILeadService service, ILogService logService, ICorretorMrvRepository corretorMrvRepository) =>
{
    ...
}

This will bind the request data to your model. Note: there's no need to use Accepts<LeadRequest>("application/x-www-form-urlencoded") when using this method.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM