diff --git a/src/Umbraco.Web.Common/Extensions/TypeExtensions.cs b/src/Umbraco.Web.Common/Extensions/TypeExtensions.cs new file mode 100644 index 0000000000..60edea5b15 --- /dev/null +++ b/src/Umbraco.Web.Common/Extensions/TypeExtensions.cs @@ -0,0 +1,10 @@ +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.PublishedContent; + +namespace Umbraco.Extensions; + +internal static class TypeExtensions +{ + public static bool IsRenderingModel(this Type type) + => typeof(ContentModel).IsAssignableFrom(type) || typeof(IPublishedContent).IsAssignableFrom(type); +} diff --git a/src/Umbraco.Web.Common/Mvc/UmbracoMvcConfigureOptions.cs b/src/Umbraco.Web.Common/Mvc/UmbracoMvcConfigureOptions.cs index 246e313b9a..eb4991fec3 100644 --- a/src/Umbraco.Web.Common/Mvc/UmbracoMvcConfigureOptions.cs +++ b/src/Umbraco.Web.Common/Mvc/UmbracoMvcConfigureOptions.cs @@ -2,6 +2,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; using Umbraco.Cms.Web.Common.Filters; using Umbraco.Cms.Web.Common.ModelBinders; +using Umbraco.Cms.Web.Common.Validators; namespace Umbraco.Cms.Web.Common.Mvc; @@ -18,6 +19,8 @@ public class UmbracoMvcConfigureOptions : IConfigureOptions public void Configure(MvcOptions options) { options.ModelBinderProviders.Insert(0, new ContentModelBinderProvider()); + options.ModelValidatorProviders.Insert(0, new BypassRenderingModelValidatorProvider()); + options.ModelMetadataDetailsProviders.Add(new BypassRenderingModelValidationMetadataProvider()); options.Filters.Insert(0, new EnsurePartialViewMacroViewContextFilterAttribute()); } } diff --git a/src/Umbraco.Web.Common/Validators/BypassRenderingModelValidationMetadataProvider.cs b/src/Umbraco.Web.Common/Validators/BypassRenderingModelValidationMetadataProvider.cs new file mode 100644 index 0000000000..94f0dea502 --- /dev/null +++ b/src/Umbraco.Web.Common/Validators/BypassRenderingModelValidationMetadataProvider.cs @@ -0,0 +1,18 @@ +using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Web.Common.Validators; + +/// +/// Ensures we bypass object graph validation for rendering models. +/// +internal class BypassRenderingModelValidationMetadataProvider : IValidationMetadataProvider +{ + public void CreateValidationMetadata(ValidationMetadataProviderContext context) + { + if (context.Key.ModelType.IsRenderingModel()) + { + context.ValidationMetadata.ValidateChildren = false; + } + } +} diff --git a/src/Umbraco.Web.Common/Validators/BypassRenderingModelValidatorProvider.cs b/src/Umbraco.Web.Common/Validators/BypassRenderingModelValidatorProvider.cs new file mode 100644 index 0000000000..ab52483180 --- /dev/null +++ b/src/Umbraco.Web.Common/Validators/BypassRenderingModelValidatorProvider.cs @@ -0,0 +1,25 @@ +using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Web.Common.Validators; + +/// +/// Ensures we bypass property validation for rendering models. +/// +internal class BypassRenderingModelValidatorProvider : IModelValidatorProvider +{ + public void CreateValidators(ModelValidatorProviderContext context) + { + if (context.ModelMetadata.ModelType.IsRenderingModel()) + { + context.Results.Clear(); + context.Results.Add(new ValidatorItem + { + Validator = null, + IsReusable = true + }); + } + } +} + +