2020-05-12 10:21:40 +10:00
using Microsoft.AspNetCore.Mvc ;
using Microsoft.AspNetCore.Mvc.ApplicationModels ;
using Microsoft.AspNetCore.Mvc.ModelBinding ;
using System.Collections.Generic ;
using System.Linq ;
using Umbraco.Core ;
using Umbraco.Web.Common.Attributes ;
namespace Umbraco.Web.Common.ApplicationModels
{
/// <summary>
/// A custom application model provider for Umbraco controllers
/// </summary>
/// <remarks>
/// <para>
/// Conventions will be applied to controllers attributed with <see cref="UmbracoApiControllerAttribute"/>
/// </para>
/// <para>
/// This is nearly a copy of aspnetcore's ApiBehaviorApplicationModelProvider which supplies a convention for the
/// [ApiController] attribute, however that convention is too strict for our purposes so we will have our own.
/// </para>
2020-05-12 12:29:03 +10:00
/// <para>
/// See https://shazwazza.com/post/custom-body-model-binding-per-controller-in-asp-net-core/
/// and https://github.com/dotnet/aspnetcore/issues/21724
/// </para>
2020-05-12 10:21:40 +10:00
/// </remarks>
public class UmbracoApiBehaviorApplicationModelProvider : IApplicationModelProvider
{
public UmbracoApiBehaviorApplicationModelProvider ( IModelMetadataProvider modelMetadataProvider )
{
// see see https://docs.microsoft.com/en-us/aspnet/core/web-api/?view=aspnetcore-3.1#apicontroller-attribute
// for what these things actually do
// NOTE: we don't have attribute routing requirements and we cannot use ApiVisibilityConvention without attribute routing
ActionModelConventions = new List < IActionModelConvention > ( )
{
2020-05-26 13:08:20 +02:00
new ClientErrorResultFilterConvention ( ) , // Ensures the responses without any body is converted into a simple json object with info instead of a string like "Status Code: 404; Not Found"
2020-05-12 10:21:40 +10:00
new ConsumesConstraintForFormFileParameterConvention ( ) , // If an controller accepts files, it must accept multipart/form-data.
new InferParameterBindingInfoConvention ( modelMetadataProvider ) , // no need for [FromBody] everywhere, A complex type parameter is assigned to FromBody
2020-05-12 12:29:03 +10:00
// This ensures that all parameters of type BindingSource.Body (based on the above InferParameterBindingInfoConvention) are bound
// using our own UmbracoJsonModelBinder
new UmbracoJsonModelBinderConvention ( )
2020-05-12 10:21:40 +10:00
} ;
// TODO: Need to determine exactly how this affects errors
var defaultErrorType = typeof ( ProblemDetails ) ;
var defaultErrorTypeAttribute = new ProducesErrorResponseTypeAttribute ( defaultErrorType ) ;
ActionModelConventions . Add ( new ApiConventionApplicationModelConvention ( defaultErrorTypeAttribute ) ) ;
}
/// <summary>
/// Will execute after <see cref="DefaultApplicationModelProvider"/>
/// </summary>
public int Order = > 0 ;
public List < IActionModelConvention > ActionModelConventions { get ; }
public void OnProvidersExecuted ( ApplicationModelProviderContext context )
{
}
public void OnProvidersExecuting ( ApplicationModelProviderContext context )
{
foreach ( var controller in context . Result . Controllers )
{
if ( ! IsUmbracoApiController ( controller ) )
continue ;
2020-05-28 15:20:02 +02:00
2020-05-12 10:21:40 +10:00
foreach ( var action in controller . Actions )
{
foreach ( var convention in ActionModelConventions )
{
convention . Apply ( action ) ;
2020-05-26 13:08:20 +02:00
}
2020-05-12 10:21:40 +10:00
}
2020-05-26 13:08:20 +02:00
2020-05-12 10:21:40 +10:00
}
}
private bool IsUmbracoApiController ( ControllerModel controller ) = > controller . Attributes . OfType < UmbracoApiControllerAttribute > ( ) . Any ( ) ;
}
}