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; using Umbraco.Web.Common.ModelBinding; namespace Umbraco.Web.Common.ApplicationModels { public class NewtonsoftJsonModelBinderConvention : IActionModelConvention { public void Apply(ActionModel action) { foreach(var p in action.Parameters) { if (p.BindingInfo?.BindingSource == BindingSource.Body) { p.BindingInfo.BinderType = typeof(UmbracoJsonModelBinder); } } } } /// /// A custom application model provider for Umbraco controllers /// /// /// /// Conventions will be applied to controllers attributed with /// /// /// 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. /// /// 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() { new ClientErrorResultFilterConvention(), // TODO: Need to determine exactly how this affects errors new InvalidModelStateFilterConvention(), // automatically 400 responses if ModelState is invalid before hitting the controller 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 new NewtonsoftJsonModelBinderConvention() }; // TODO: Need to determine exactly how this affects errors var defaultErrorType = typeof(ProblemDetails); var defaultErrorTypeAttribute = new ProducesErrorResponseTypeAttribute(defaultErrorType); ActionModelConventions.Add(new ApiConventionApplicationModelConvention(defaultErrorTypeAttribute)); } /// /// Will execute after /// public int Order => 0; public List ActionModelConventions { get; } public void OnProvidersExecuted(ApplicationModelProviderContext context) { } public void OnProvidersExecuting(ApplicationModelProviderContext context) { foreach (var controller in context.Result.Controllers) { if (!IsUmbracoApiController(controller)) continue; foreach (var action in controller.Actions) { foreach (var convention in ActionModelConventions) { convention.Apply(action); } } } } private bool IsUmbracoApiController(ControllerModel controller) => controller.Attributes.OfType().Any(); } }