Implementation of the FromBody binding converter for the ASP.NET Core Integration

This commit is contained in:
Fabio Cavalcante 2023-07-26 11:44:00 -07:00 коммит произвёл Fabio Cavalcante
Родитель 11e96ab0b6
Коммит 3829bbd22d
6 изменённых файлов: 128 добавлений и 3 удалений

Просмотреть файл

@ -0,0 +1,100 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
using System;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.Azure.Functions.Worker.Extensions.Http.Converters;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore
{
internal sealed class FromBodyConverstionFeature : IFromBodyConversionFeature
{
public static FromBodyConverstionFeature Instance { get; } = new();
public async ValueTask<object?> ConvertAsync(FunctionContext context, Type targetType)
{
var httpContext = context.GetHttpContext()
?? throw new InvalidOperationException($"The '{nameof(FromBodyConverstionFeature)} expects an '{nameof(HttpContext)}' instance in the current context.");
var metadata = httpContext.RequestServices
.GetService<IModelMetadataProvider>()?
.GetMetadataForType(targetType);
if (metadata is null)
{
context.GetLogger<FromBodyConverstionFeature>()
.LogWarning("Unable to resolve a model metadata provider for the target type ({TargetType}).", targetType);
return null;
}
var binderModelName = metadata.BinderModelName ?? string.Empty;
var modelBinder = (httpContext.RequestServices
.GetService<IModelBinderFactory>()?
.CreateBinder(new ModelBinderFactoryContext
{
Metadata = metadata,
BindingInfo = new BindingInfo()
{
BinderModelName = binderModelName,
BindingSource = BindingSource.Body,
BinderType = metadata.BinderType,
PropertyFilterProvider = metadata.PropertyFilterProvider,
},
CacheToken = null
}))
?? throw new InvalidOperationException($"Unable to resolve a request body model binder for the target type ({metadata.BinderType}.");
var modelBindingContext = new DefaultModelBindingContext
{
ModelMetadata = metadata,
ModelName = binderModelName,
BindingSource = BindingSource.Body,
ModelState = new ModelStateDictionary(),
ActionContext = new ActionContext
{
HttpContext = httpContext
}
};
await modelBinder.BindModelAsync(modelBindingContext);
if (modelBindingContext.Result.IsModelSet)
{
return modelBindingContext.Result.Model;
}
else if (!modelBindingContext.ModelState.IsValid)
{
var messageBuilder = new StringBuilder();
foreach (var key in modelBindingContext.ModelState.Keys)
{
var dictionary = modelBindingContext.ModelState[key];
if (dictionary == null)
{
continue;
}
foreach (var error in dictionary.Errors)
{
messageBuilder.AppendLine(error.ErrorMessage);
}
}
throw new InvalidOperationException(messageBuilder.ToString());
}
context.GetLogger<FromBodyConverstionFeature>()
.LogWarning("Unable to bind the request body to the target type ({TargetType}).", targetType);
return null;
}
}
}

Просмотреть файл

@ -8,6 +8,7 @@ using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Routing;
using Microsoft.Azure.Functions.Worker.Extensions.Http.Converters;
using Microsoft.Azure.Functions.Worker.Http;
using Microsoft.Azure.Functions.Worker.Middleware;
@ -41,6 +42,9 @@ namespace Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore
AddHttpContextToFunctionContext(context, httpContext);
// Register additional context features
context.Features.Set<IFromBodyConversionFeature>(FromBodyConverstionFeature.Instance);
await next(context);
if (context.GetInvocationResult()?.Value is IActionResult actionResult)

Просмотреть файл

@ -18,6 +18,7 @@
<ItemGroup>
<ProjectReference Include="..\..\..\src\DotNetWorker\DotNetWorker.csproj" />
<ProjectReference Include="..\..\Worker.Extensions.Abstractions\src\Worker.Extensions.Abstractions.csproj" />
<ProjectReference Include="..\..\Worker.Extensions.Http\src\Worker.Extensions.Http.csproj" />
</ItemGroup>
</Project>

Просмотреть файл

@ -0,0 +1,22 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.Functions.Worker;
using FromBodyAttribute = Microsoft.Azure.Functions.Worker.Http.FromBodyAttribute;
namespace AspNetIntegration
{
public class BodyBindingHttpTrigger
{
[Function(nameof(BodyBindingHttpTrigger))]
public IActionResult Run([HttpTrigger(AuthorizationLevel.Anonymous, "post")] HttpRequest req,
[FromBody] Person person)
{
return new OkObjectResult(person);
}
}
public record Person(string Name, int Age);
}

Просмотреть файл

@ -18,9 +18,7 @@ namespace AspNetIntegration
// operations can be performed using HttpContext
// example of getting route information from HttpContext:
RouteEndpoint? endpoint = httpContext.GetEndpoint() as RouteEndpoint;
if (endpoint != null)
if (httpContext.GetEndpoint() is RouteEndpoint endpoint)
{
string? displayName = endpoint.DisplayName;
string? routePattern = endpoint.RoutePattern.RawText;