diff --git a/src/Umbraco.Web.Common/ApplicationModels/UmbracoApiBehaviorApplicationModelProvider.cs b/src/Umbraco.Web.Common/ApplicationModels/UmbracoApiBehaviorApplicationModelProvider.cs
index b80104a7bf..df0584386b 100644
--- a/src/Umbraco.Web.Common/ApplicationModels/UmbracoApiBehaviorApplicationModelProvider.cs
+++ b/src/Umbraco.Web.Common/ApplicationModels/UmbracoApiBehaviorApplicationModelProvider.cs
@@ -18,6 +18,7 @@ namespace Umbraco.Cms.Web.Common.ApplicationModels
///
/// 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.
+ /// Uses UmbracoJsonModelBinder for complex parameters and those with BindingSource of Body, but leaves the rest alone see GH #11554
///
///
/// See https://shazwazza.com/post/custom-body-model-binding-per-controller-in-asp-net-core/
@@ -41,14 +42,12 @@ namespace Umbraco.Cms.Web.Common.ApplicationModels
{
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"
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
- // This ensures that all parameters of type BindingSource.Body (based on the above InferParameterBindingInfoConvention) are bound
+ // This ensures that all parameters of type BindingSource.Body and those of complex type are bound
// using our own UmbracoJsonModelBinder
- new UmbracoJsonModelBinderConvention()
+ new UmbracoJsonModelBinderConvention(modelMetadataProvider)
};
- // TODO: Need to determine exactly how this affects errors
var defaultErrorType = typeof(ProblemDetails);
var defaultErrorTypeAttribute = new ProducesErrorResponseTypeAttribute(defaultErrorType);
_actionModelConventions.Add(new ApiConventionApplicationModelConvention(defaultErrorTypeAttribute));
@@ -68,25 +67,24 @@ namespace Umbraco.Cms.Web.Common.ApplicationModels
///
public void OnProvidersExecuting(ApplicationModelProviderContext context)
{
- foreach (var controller in context.Result.Controllers)
+ foreach (ControllerModel controller in context.Result.Controllers)
{
if (!IsUmbracoApiController(controller))
{
continue;
}
- foreach (var action in controller.Actions)
+ foreach (ActionModel action in controller.Actions)
{
- foreach (var convention in _actionModelConventions)
+ foreach (IActionModelConvention convention in _actionModelConventions)
{
convention.Apply(action);
}
}
-
}
}
- private bool IsUmbracoApiController(ControllerModel controller)
+ private static bool IsUmbracoApiController(ICommonModel controller)
=> controller.Attributes.OfType().Any();
}
}
diff --git a/src/Umbraco.Web.Common/ApplicationModels/UmbracoJsonModelBinderConvention.cs b/src/Umbraco.Web.Common/ApplicationModels/UmbracoJsonModelBinderConvention.cs
index e96bda8771..2eff08d54d 100644
--- a/src/Umbraco.Web.Common/ApplicationModels/UmbracoJsonModelBinderConvention.cs
+++ b/src/Umbraco.Web.Common/ApplicationModels/UmbracoJsonModelBinderConvention.cs
@@ -1,25 +1,61 @@
-using System.Linq;
using Microsoft.AspNetCore.Mvc.ApplicationModels;
using Microsoft.AspNetCore.Mvc.ModelBinding;
+using Microsoft.Extensions.DependencyInjection;
+using Umbraco.Cms.Web.Common.DependencyInjection;
using Umbraco.Cms.Web.Common.ModelBinders;
namespace Umbraco.Cms.Web.Common.ApplicationModels
{
///
- /// Applies the body model binder to any parameter binding source of type
+ /// Applies the body model binder to any complex parameter and those with a
+ /// binding source of type
///
- ///
- /// For this to work Microsoft's own convention must be executed before this one
- ///
public class UmbracoJsonModelBinderConvention : IActionModelConvention
{
+ private readonly IModelMetadataProvider _modelMetadataProvider;
+
+ public UmbracoJsonModelBinderConvention()
+ : this(StaticServiceProvider.Instance.GetRequiredService())
+ {
+ }
+
+ public UmbracoJsonModelBinderConvention(IModelMetadataProvider modelMetadataProvider)
+ {
+ _modelMetadataProvider = modelMetadataProvider;
+ }
+
///
public void Apply(ActionModel action)
{
- foreach (ParameterModel p in action.Parameters.Where(p => p.BindingInfo?.BindingSource == BindingSource.Body))
+ foreach (ParameterModel p in action.Parameters)
{
- p.BindingInfo.BinderType = typeof(UmbracoJsonModelBinder);
+ if (p.BindingInfo == null)
+ {
+ if (IsComplexTypeParameter(p))
+ {
+ p.BindingInfo = new BindingInfo
+ {
+ BindingSource = BindingSource.Body,
+ BinderType = typeof(UmbracoJsonModelBinder)
+ };
+ }
+
+ continue;
+ }
+
+ if (p.BindingInfo.BindingSource == BindingSource.Body)
+ {
+ p.BindingInfo.BinderType = typeof(UmbracoJsonModelBinder);
+ }
}
}
+
+ private bool IsComplexTypeParameter(ParameterModel parameter)
+ {
+ // No need for information from attributes on the parameter. Just use its type.
+ ModelMetadata metadata = _modelMetadataProvider.GetMetadataForType(parameter.ParameterInfo.ParameterType);
+
+ return metadata.IsComplexType;
+ }
}
}