2021-02-18 11:06:02 +01:00
using System.Collections.Generic ;
2020-05-12 10:21:40 +10:00
using System.Linq ;
2021-02-15 18:50:16 +11:00
using Microsoft.AspNetCore.Mvc ;
using Microsoft.AspNetCore.Mvc.ApplicationModels ;
using Microsoft.AspNetCore.Mvc.ModelBinding ;
2021-02-18 11:06:02 +01:00
using Umbraco.Cms.Web.Common.Attributes ;
2020-05-12 10:21:40 +10:00
2021-02-18 11:06:02 +01:00
namespace Umbraco.Cms.Web.Common.ApplicationModels
2020-05-12 10:21:40 +10:00
{
/// <summary>
2020-08-06 22:08:27 +10:00
/// An application model provider for Umbraco API controllers to behave like WebApi controllers
2020-05-12 10:21:40 +10:00
/// </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
{
2021-02-15 18:50:16 +11:00
private readonly List < IActionModelConvention > _actionModelConventions ;
/// <summary>
/// Initializes a new instance of the <see cref="UmbracoApiBehaviorApplicationModelProvider"/> class.
/// </summary>
2020-05-12 10:21:40 +10:00
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
2021-02-15 18:50:16 +11:00
_actionModelConventions = new List < IActionModelConvention > ( )
2020-05-12 10:21:40 +10:00
{
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 ) ;
2021-02-15 18:50:16 +11:00
_actionModelConventions . Add ( new ApiConventionApplicationModelConvention ( defaultErrorTypeAttribute ) ) ;
2020-05-12 10:21:40 +10:00
}
2021-02-15 18:50:16 +11:00
/// <inheritdoc/>
2020-05-12 10:21:40 +10:00
/// <summary>
/// Will execute after <see cref="DefaultApplicationModelProvider"/>
/// </summary>
public int Order = > 0 ;
2021-02-15 18:50:16 +11:00
/// <inheritdoc/>
2020-05-12 10:21:40 +10:00
public void OnProvidersExecuted ( ApplicationModelProviderContext context )
{
}
2021-02-15 18:50:16 +11:00
/// <inheritdoc/>
2020-05-12 10:21:40 +10:00
public void OnProvidersExecuting ( ApplicationModelProviderContext context )
{
foreach ( var controller in context . Result . Controllers )
{
if ( ! IsUmbracoApiController ( controller ) )
2021-02-15 18:50:16 +11:00
{
2020-05-12 10:21:40 +10:00
continue ;
2021-02-15 18:50:16 +11:00
}
2020-05-28 15:20:02 +02:00
2020-05-12 10:21:40 +10:00
foreach ( var action in controller . Actions )
{
2021-02-15 18:50:16 +11:00
foreach ( var convention in _actionModelConventions )
2020-05-12 10:21:40 +10:00
{
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
}
}
2020-11-27 13:35:22 +01:00
private bool IsUmbracoApiController ( ControllerModel controller )
= > controller . Attributes . OfType < UmbracoApiControllerAttribute > ( ) . Any ( ) ;
2020-05-12 10:21:40 +10:00
}
}