v10: Fix build warnings in Web.Common (#12349)
* Run code cleanup * Run dotnet format * Start manual cleanup in Web.Common * Finish up manual cleanup * Fix tests * Fix up InMemoryModelFactory.cs * Inject proper macroRenderer * Update src/Umbraco.Web.Common/Filters/JsonDateTimeFormatAttribute.cs Co-authored-by: Mole <nikolajlauridsen@protonmail.ch> * Update src/Umbraco.Web.Common/Filters/ValidateUmbracoFormRouteStringAttribute.cs Co-authored-by: Mole <nikolajlauridsen@protonmail.ch> * Fix based on review Co-authored-by: Nikolaj Geisle <niko737@edu.ucl.dk> Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>
This commit is contained in:
@@ -1,6 +1,4 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Events;
|
||||
@@ -10,162 +8,160 @@ using Umbraco.Cms.Core.Notifications;
|
||||
using Umbraco.Cms.Web.Common.Routing;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Web.Common.ModelBinders
|
||||
namespace Umbraco.Cms.Web.Common.ModelBinders;
|
||||
|
||||
/// <summary>
|
||||
/// Maps view models, supporting mapping to and from any <see cref="IPublishedContent" /> or
|
||||
/// <see cref="IContentModel" />.
|
||||
/// </summary>
|
||||
public class ContentModelBinder : IModelBinder
|
||||
{
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
|
||||
/// <summary>
|
||||
/// Maps view models, supporting mapping to and from any <see cref="IPublishedContent"/> or <see cref="IContentModel"/>.
|
||||
/// Initializes a new instance of the <see cref="ContentModelBinder" /> class.
|
||||
/// </summary>
|
||||
public class ContentModelBinder : IModelBinder
|
||||
public ContentModelBinder(IEventAggregator eventAggregator) => _eventAggregator = eventAggregator;
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task BindModelAsync(ModelBindingContext bindingContext)
|
||||
{
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ContentModelBinder"/> class.
|
||||
/// </summary>
|
||||
public ContentModelBinder(IEventAggregator eventAggregator) => _eventAggregator = eventAggregator;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task BindModelAsync(ModelBindingContext bindingContext)
|
||||
// Although this model binder is built to work both ways between IPublishedContent and IContentModel in reality
|
||||
// only IPublishedContent will ever exist in the request so when this model binder is used as an IModelBinder
|
||||
// in the aspnet pipeline it will really only support converting from IPublishedContent which is contained
|
||||
// in the UmbracoRouteValues --> IContentModel
|
||||
UmbracoRouteValues? umbracoRouteValues = bindingContext.HttpContext.Features.Get<UmbracoRouteValues>();
|
||||
if (umbracoRouteValues is null)
|
||||
{
|
||||
// Although this model binder is built to work both ways between IPublishedContent and IContentModel in reality
|
||||
// only IPublishedContent will ever exist in the request so when this model binder is used as an IModelBinder
|
||||
// in the aspnet pipeline it will really only support converting from IPublishedContent which is contained
|
||||
// in the UmbracoRouteValues --> IContentModel
|
||||
UmbracoRouteValues? umbracoRouteValues = bindingContext.HttpContext.Features.Get<UmbracoRouteValues>();
|
||||
if (umbracoRouteValues is null)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
BindModel(bindingContext, umbracoRouteValues.PublishedRequest.PublishedContent, bindingContext.ModelType);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
// source is the model that we have
|
||||
// modelType is the type of the model that we need to bind to
|
||||
//
|
||||
// create a model object of the modelType by mapping:
|
||||
// { ContentModel, ContentModel<TContent>, IPublishedContent }
|
||||
// to
|
||||
// { ContentModel, ContentModel<TContent>, IPublishedContent }
|
||||
BindModel(bindingContext, umbracoRouteValues.PublishedRequest.PublishedContent, bindingContext.ModelType);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to bind the model
|
||||
/// </summary>
|
||||
public void BindModel(ModelBindingContext bindingContext, object? source, Type modelType)
|
||||
// source is the model that we have
|
||||
// modelType is the type of the model that we need to bind to
|
||||
//
|
||||
// create a model object of the modelType by mapping:
|
||||
// { ContentModel, ContentModel<TContent>, IPublishedContent }
|
||||
// to
|
||||
// { ContentModel, ContentModel<TContent>, IPublishedContent }
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to bind the model
|
||||
/// </summary>
|
||||
public void BindModel(ModelBindingContext bindingContext, object? source, Type modelType)
|
||||
{
|
||||
// Null model, return
|
||||
if (source == null)
|
||||
{
|
||||
// Null model, return
|
||||
if (source == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// If types already match, return
|
||||
Type sourceType = source.GetType();
|
||||
if (sourceType.Inherits(modelType))
|
||||
{
|
||||
bindingContext.Result = ModelBindingResult.Success(source);
|
||||
return;
|
||||
}
|
||||
|
||||
// Try to grab the content
|
||||
var sourceContent = source as IPublishedContent; // check if what we have is an IPublishedContent
|
||||
if (sourceContent == null && sourceType.Implements<IContentModel>())
|
||||
{
|
||||
// else check if it's an IContentModel, and get the content
|
||||
sourceContent = ((IContentModel)source).Content;
|
||||
}
|
||||
|
||||
if (sourceContent == null)
|
||||
{
|
||||
// else check if we can convert it to a content
|
||||
Attempt<IPublishedContent> attempt1 = source.TryConvertTo<IPublishedContent>();
|
||||
if (attempt1.Success)
|
||||
{
|
||||
sourceContent = attempt1.Result;
|
||||
}
|
||||
}
|
||||
|
||||
// If we have a content
|
||||
if (sourceContent != null)
|
||||
{
|
||||
// If model is IPublishedContent, check content type and return
|
||||
if (modelType.Implements<IPublishedContent>())
|
||||
{
|
||||
if (sourceContent.GetType().Inherits(modelType) == false)
|
||||
{
|
||||
ThrowModelBindingException(true, false, sourceContent.GetType(), modelType);
|
||||
}
|
||||
|
||||
bindingContext.Result = ModelBindingResult.Success(sourceContent);
|
||||
return;
|
||||
}
|
||||
|
||||
// If model is ContentModel, create and return
|
||||
if (modelType == typeof(ContentModel))
|
||||
{
|
||||
bindingContext.Result = ModelBindingResult.Success(new ContentModel(sourceContent));
|
||||
return;
|
||||
}
|
||||
|
||||
// If model is ContentModel<TContent>, check content type, then create and return
|
||||
if (modelType.IsGenericType && modelType.GetGenericTypeDefinition() == typeof(ContentModel<>))
|
||||
{
|
||||
Type targetContentType = modelType.GetGenericArguments()[0];
|
||||
if (sourceContent.GetType().Inherits(targetContentType) == false)
|
||||
{
|
||||
ThrowModelBindingException(true, true, sourceContent.GetType(), targetContentType);
|
||||
}
|
||||
|
||||
bindingContext.Result = ModelBindingResult.Success(Activator.CreateInstance(modelType, sourceContent));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Last chance : try to convert
|
||||
Attempt<object?> attempt2 = source.TryConvertTo(modelType);
|
||||
if (attempt2.Success)
|
||||
{
|
||||
bindingContext.Result = ModelBindingResult.Success(attempt2.Result);
|
||||
return;
|
||||
}
|
||||
|
||||
// Fail
|
||||
ThrowModelBindingException(false, false, sourceType, modelType);
|
||||
return;
|
||||
}
|
||||
|
||||
private void ThrowModelBindingException(bool sourceContent, bool modelContent, Type sourceType, Type modelType)
|
||||
// If types already match, return
|
||||
Type sourceType = source.GetType();
|
||||
if (sourceType.Inherits(modelType))
|
||||
{
|
||||
var msg = new StringBuilder();
|
||||
|
||||
// prepare message
|
||||
msg.Append("Cannot bind source");
|
||||
if (sourceContent)
|
||||
{
|
||||
msg.Append(" content");
|
||||
}
|
||||
|
||||
msg.Append(" type ");
|
||||
msg.Append(sourceType.FullName);
|
||||
msg.Append(" to model");
|
||||
if (modelContent)
|
||||
{
|
||||
msg.Append(" content");
|
||||
}
|
||||
|
||||
msg.Append(" type ");
|
||||
msg.Append(modelType.FullName);
|
||||
msg.Append(".");
|
||||
|
||||
// raise event, to give model factories a chance at reporting
|
||||
// the error with more details, and optionally request that
|
||||
// the application restarts.
|
||||
var args = new ModelBindingErrorNotification(sourceType, modelType, msg);
|
||||
_eventAggregator.Publish(args);
|
||||
|
||||
throw new ModelBindingException(msg.ToString());
|
||||
bindingContext.Result = ModelBindingResult.Success(source);
|
||||
return;
|
||||
}
|
||||
|
||||
// Try to grab the content
|
||||
var sourceContent = source as IPublishedContent; // check if what we have is an IPublishedContent
|
||||
if (sourceContent == null && sourceType.Implements<IContentModel>())
|
||||
{
|
||||
// else check if it's an IContentModel, and get the content
|
||||
sourceContent = ((IContentModel)source).Content;
|
||||
}
|
||||
|
||||
if (sourceContent == null)
|
||||
{
|
||||
// else check if we can convert it to a content
|
||||
Attempt<IPublishedContent> attempt1 = source.TryConvertTo<IPublishedContent>();
|
||||
if (attempt1.Success)
|
||||
{
|
||||
sourceContent = attempt1.Result;
|
||||
}
|
||||
}
|
||||
|
||||
// If we have a content
|
||||
if (sourceContent != null)
|
||||
{
|
||||
// If model is IPublishedContent, check content type and return
|
||||
if (modelType.Implements<IPublishedContent>())
|
||||
{
|
||||
if (sourceContent.GetType().Inherits(modelType) == false)
|
||||
{
|
||||
ThrowModelBindingException(true, false, sourceContent.GetType(), modelType);
|
||||
}
|
||||
|
||||
bindingContext.Result = ModelBindingResult.Success(sourceContent);
|
||||
return;
|
||||
}
|
||||
|
||||
// If model is ContentModel, create and return
|
||||
if (modelType == typeof(ContentModel))
|
||||
{
|
||||
bindingContext.Result = ModelBindingResult.Success(new ContentModel(sourceContent));
|
||||
return;
|
||||
}
|
||||
|
||||
// If model is ContentModel<TContent>, check content type, then create and return
|
||||
if (modelType.IsGenericType && modelType.GetGenericTypeDefinition() == typeof(ContentModel<>))
|
||||
{
|
||||
Type targetContentType = modelType.GetGenericArguments()[0];
|
||||
if (sourceContent.GetType().Inherits(targetContentType) == false)
|
||||
{
|
||||
ThrowModelBindingException(true, true, sourceContent.GetType(), targetContentType);
|
||||
}
|
||||
|
||||
bindingContext.Result = ModelBindingResult.Success(Activator.CreateInstance(modelType, sourceContent));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Last chance : try to convert
|
||||
Attempt<object?> attempt2 = source.TryConvertTo(modelType);
|
||||
if (attempt2.Success)
|
||||
{
|
||||
bindingContext.Result = ModelBindingResult.Success(attempt2.Result);
|
||||
return;
|
||||
}
|
||||
|
||||
// Fail
|
||||
ThrowModelBindingException(false, false, sourceType, modelType);
|
||||
}
|
||||
|
||||
private void ThrowModelBindingException(bool sourceContent, bool modelContent, Type sourceType, Type modelType)
|
||||
{
|
||||
var msg = new StringBuilder();
|
||||
|
||||
// prepare message
|
||||
msg.Append("Cannot bind source");
|
||||
if (sourceContent)
|
||||
{
|
||||
msg.Append(" content");
|
||||
}
|
||||
|
||||
msg.Append(" type ");
|
||||
msg.Append(sourceType.FullName);
|
||||
msg.Append(" to model");
|
||||
if (modelContent)
|
||||
{
|
||||
msg.Append(" content");
|
||||
}
|
||||
|
||||
msg.Append(" type ");
|
||||
msg.Append(modelType.FullName);
|
||||
msg.Append(".");
|
||||
|
||||
// raise event, to give model factories a chance at reporting
|
||||
// the error with more details, and optionally request that
|
||||
// the application restarts.
|
||||
var args = new ModelBindingErrorNotification(sourceType, modelType, msg);
|
||||
_eventAggregator.Publish(args);
|
||||
|
||||
throw new ModelBindingException(msg.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,30 +1,30 @@
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Binders;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
|
||||
namespace Umbraco.Cms.Web.Common.ModelBinders
|
||||
namespace Umbraco.Cms.Web.Common.ModelBinders;
|
||||
|
||||
/// <summary>
|
||||
/// The provider for <see cref="ContentModelBinder" /> mapping view models, supporting mapping to and from any
|
||||
/// IPublishedContent or IContentModel.
|
||||
/// </summary>
|
||||
public class ContentModelBinderProvider : IModelBinderProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// The provider for <see cref="ContentModelBinder"/> mapping view models, supporting mapping to and from any IPublishedContent or IContentModel.
|
||||
/// </summary>
|
||||
public class ContentModelBinderProvider : IModelBinderProvider
|
||||
public IModelBinder? GetBinder(ModelBinderProviderContext context)
|
||||
{
|
||||
public IModelBinder? GetBinder(ModelBinderProviderContext context)
|
||||
Type modelType = context.Metadata.ModelType;
|
||||
|
||||
// Can bind to ContentModel (exact type match)
|
||||
// or to ContentModel<TContent> (exact generic type match)
|
||||
// or to TContent where TContent : IPublishedContent (any IPublishedContent implementation)
|
||||
if (modelType == typeof(ContentModel) ||
|
||||
(modelType.IsGenericType && modelType.GetGenericTypeDefinition() == typeof(ContentModel<>)) ||
|
||||
typeof(IPublishedContent).IsAssignableFrom(modelType))
|
||||
{
|
||||
var modelType = context.Metadata.ModelType;
|
||||
|
||||
// Can bind to ContentModel (exact type match)
|
||||
// or to ContentModel<TContent> (exact generic type match)
|
||||
// or to TContent where TContent : IPublishedContent (any IPublishedContent implementation)
|
||||
if (modelType == typeof(ContentModel) ||
|
||||
(modelType.IsGenericType && modelType.GetGenericTypeDefinition() == typeof(ContentModel<>)) ||
|
||||
typeof(IPublishedContent).IsAssignableFrom(modelType))
|
||||
{
|
||||
return new BinderTypeModelBinder(typeof(ContentModelBinder));
|
||||
}
|
||||
|
||||
return null;
|
||||
return new BinderTypeModelBinder(typeof(ContentModelBinder));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,50 +1,49 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Web.Common.ModelBinders
|
||||
{
|
||||
/// <summary>
|
||||
/// Allows an Action to execute with an arbitrary number of QueryStrings
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Just like you can POST an arbitrary number of parameters to an Action, you can't GET an arbitrary number
|
||||
/// but this will allow you to do it.
|
||||
/// </remarks>
|
||||
public sealed class HttpQueryStringModelBinder : IModelBinder
|
||||
{
|
||||
public Task BindModelAsync(ModelBindingContext bindingContext)
|
||||
{
|
||||
var queryStrings = GetQueryAsDictionary(bindingContext.ActionContext.HttpContext.Request.Query);
|
||||
var queryStringKeys = queryStrings.Select(kvp => kvp.Key).ToArray();
|
||||
if (queryStringKeys.InvariantContains("culture") == false)
|
||||
{
|
||||
queryStrings.Add("culture", new StringValues(bindingContext.ActionContext.HttpContext.Request.ClientCulture()));
|
||||
}
|
||||
namespace Umbraco.Cms.Web.Common.ModelBinders;
|
||||
|
||||
var formData = new FormCollection(queryStrings);
|
||||
bindingContext.Result = ModelBindingResult.Success(formData);
|
||||
return Task.CompletedTask;
|
||||
/// <summary>
|
||||
/// Allows an Action to execute with an arbitrary number of QueryStrings
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Just like you can POST an arbitrary number of parameters to an Action, you can't GET an arbitrary number
|
||||
/// but this will allow you to do it.
|
||||
/// </remarks>
|
||||
public sealed class HttpQueryStringModelBinder : IModelBinder
|
||||
{
|
||||
public Task BindModelAsync(ModelBindingContext bindingContext)
|
||||
{
|
||||
Dictionary<string, StringValues> queryStrings =
|
||||
GetQueryAsDictionary(bindingContext.ActionContext.HttpContext.Request.Query);
|
||||
var queryStringKeys = queryStrings.Select(kvp => kvp.Key).ToArray();
|
||||
if (queryStringKeys.InvariantContains("culture") == false)
|
||||
{
|
||||
queryStrings.Add(
|
||||
"culture",
|
||||
new StringValues(bindingContext.ActionContext.HttpContext.Request.ClientCulture()));
|
||||
}
|
||||
|
||||
private Dictionary<string, StringValues> GetQueryAsDictionary(IQueryCollection query)
|
||||
var formData = new FormCollection(queryStrings);
|
||||
bindingContext.Result = ModelBindingResult.Success(formData);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private Dictionary<string, StringValues> GetQueryAsDictionary(IQueryCollection? query)
|
||||
{
|
||||
var result = new Dictionary<string, StringValues>();
|
||||
if (query == null)
|
||||
{
|
||||
var result = new Dictionary<string, StringValues>();
|
||||
if (query == null)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
foreach (var item in query)
|
||||
{
|
||||
result.Add(item.Key, item.Value);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
foreach (KeyValuePair<string, StringValues> item in query)
|
||||
{
|
||||
result.Add(item.Key, item.Value);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,46 +1,57 @@
|
||||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace Umbraco.Cms.Web.Common.ModelBinders
|
||||
namespace Umbraco.Cms.Web.Common.ModelBinders;
|
||||
|
||||
/// <summary>
|
||||
/// The exception that is thrown when an error occurs while binding a source to a model.
|
||||
/// </summary>
|
||||
/// <seealso cref="Exception" />
|
||||
/// Migrated to .NET Core
|
||||
[Serializable]
|
||||
public class ModelBindingException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// The exception that is thrown when an error occurs while binding a source to a model.
|
||||
/// Initializes a new instance of the <see cref="ModelBindingException" /> class.
|
||||
/// </summary>
|
||||
/// <seealso cref="Exception" />
|
||||
/// Migrated to .NET Core
|
||||
[Serializable]
|
||||
public class ModelBindingException : Exception
|
||||
public ModelBindingException()
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ModelBindingException" /> class.
|
||||
/// </summary>
|
||||
public ModelBindingException()
|
||||
{ }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ModelBindingException" /> class.
|
||||
/// </summary>
|
||||
/// <param name="message">The message that describes the error.</param>
|
||||
public ModelBindingException(string message)
|
||||
: base(message)
|
||||
{ }
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ModelBindingException" /> class.
|
||||
/// </summary>
|
||||
/// <param name="message">The message that describes the error.</param>
|
||||
public ModelBindingException(string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ModelBindingException" /> class.
|
||||
/// </summary>
|
||||
/// <param name="message">The error message that explains the reason for the exception.</param>
|
||||
/// <param name="innerException">The exception that is the cause of the current exception, or a null reference (<see langword="Nothing" /> in Visual Basic) if no inner exception is specified.</param>
|
||||
public ModelBindingException(string message, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{ }
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ModelBindingException" /> class.
|
||||
/// </summary>
|
||||
/// <param name="message">The error message that explains the reason for the exception.</param>
|
||||
/// <param name="innerException">
|
||||
/// The exception that is the cause of the current exception, or a null reference (
|
||||
/// <see langword="Nothing" /> in Visual Basic) if no inner exception is specified.
|
||||
/// </param>
|
||||
public ModelBindingException(string message, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ModelBindingException" /> class.
|
||||
/// </summary>
|
||||
/// <param name="info">The <see cref="T:System.Runtime.Serialization.SerializationInfo" /> that holds the serialized object data about the exception being thrown.</param>
|
||||
/// <param name="context">The <see cref="T:System.Runtime.Serialization.StreamingContext" /> that contains contextual information about the source or destination.</param>
|
||||
protected ModelBindingException(SerializationInfo info, StreamingContext context)
|
||||
: base(info, context)
|
||||
{ }
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ModelBindingException" /> class.
|
||||
/// </summary>
|
||||
/// <param name="info">
|
||||
/// The <see cref="T:System.Runtime.Serialization.SerializationInfo" /> that holds the serialized object
|
||||
/// data about the exception being thrown.
|
||||
/// </param>
|
||||
/// <param name="context">
|
||||
/// The <see cref="T:System.Runtime.Serialization.StreamingContext" /> that contains contextual
|
||||
/// information about the source or destination.
|
||||
/// </param>
|
||||
protected ModelBindingException(SerializationInfo info, StreamingContext context)
|
||||
: base(info, context)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,45 +2,46 @@ using System.Buffers;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Formatters;
|
||||
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Binders;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.ObjectPool;
|
||||
using Newtonsoft.Json;
|
||||
using Umbraco.Cms.Web.Common.Formatters;
|
||||
|
||||
namespace Umbraco.Cms.Web.Common.ModelBinders
|
||||
namespace Umbraco.Cms.Web.Common.ModelBinders;
|
||||
|
||||
/// <summary>
|
||||
/// A custom body model binder that only uses a <see cref="NewtonsoftJsonInputFormatter" /> to bind body action
|
||||
/// parameters
|
||||
/// </summary>
|
||||
public class UmbracoJsonModelBinder : BodyModelBinder
|
||||
{
|
||||
/// <summary>
|
||||
/// A custom body model binder that only uses a <see cref="NewtonsoftJsonInputFormatter"/> to bind body action parameters
|
||||
/// </summary>
|
||||
public class UmbracoJsonModelBinder : BodyModelBinder, IModelBinder
|
||||
public UmbracoJsonModelBinder(
|
||||
ArrayPool<char> arrayPool,
|
||||
ObjectPoolProvider objectPoolProvider,
|
||||
IHttpRequestStreamReaderFactory readerFactory,
|
||||
ILoggerFactory loggerFactory)
|
||||
: base(GetNewtonsoftJsonFormatter(loggerFactory, arrayPool, objectPoolProvider), readerFactory, loggerFactory)
|
||||
{
|
||||
public UmbracoJsonModelBinder(ArrayPool<char> arrayPool, ObjectPoolProvider objectPoolProvider, IHttpRequestStreamReaderFactory readerFactory, ILoggerFactory loggerFactory)
|
||||
: base(GetNewtonsoftJsonFormatter(loggerFactory, arrayPool, objectPoolProvider), readerFactory, loggerFactory)
|
||||
}
|
||||
|
||||
private static IInputFormatter[] GetNewtonsoftJsonFormatter(ILoggerFactory logger, ArrayPool<char> arrayPool, ObjectPoolProvider objectPoolProvider)
|
||||
{
|
||||
var jsonOptions = new MvcNewtonsoftJsonOptions { AllowInputFormatterExceptionMessages = true };
|
||||
|
||||
JsonSerializerSettings ss = jsonOptions.SerializerSettings; // Just use the defaults as base
|
||||
|
||||
// We need to ignore required attributes when serializing. E.g UserSave.ChangePassword. Otherwise the model is not model bound.
|
||||
ss.ContractResolver = new IgnoreRequiredAttributesResolver();
|
||||
return new IInputFormatter[]
|
||||
{
|
||||
}
|
||||
|
||||
private static IInputFormatter[] GetNewtonsoftJsonFormatter(ILoggerFactory logger, ArrayPool<char> arrayPool, ObjectPoolProvider objectPoolProvider)
|
||||
{
|
||||
var jsonOptions = new MvcNewtonsoftJsonOptions
|
||||
{
|
||||
AllowInputFormatterExceptionMessages = true
|
||||
};
|
||||
|
||||
var ss = jsonOptions.SerializerSettings; // Just use the defaults as base
|
||||
|
||||
// We need to ignore required attributes when serializing. E.g UserSave.ChangePassword. Otherwise the model is not model bound.
|
||||
ss.ContractResolver = new IgnoreRequiredAttributesResolver();
|
||||
return new IInputFormatter[]
|
||||
{
|
||||
new NewtonsoftJsonInputFormatter(
|
||||
logger.CreateLogger<UmbracoJsonModelBinder>(),
|
||||
jsonOptions.SerializerSettings, // Just use the defaults
|
||||
arrayPool,
|
||||
objectPoolProvider,
|
||||
new MvcOptions(), // The only option that NewtonsoftJsonInputFormatter uses is SuppressInputFormatterBuffering
|
||||
jsonOptions)
|
||||
};
|
||||
}
|
||||
new NewtonsoftJsonInputFormatter(
|
||||
logger.CreateLogger<UmbracoJsonModelBinder>(),
|
||||
jsonOptions.SerializerSettings, // Just use the defaults
|
||||
arrayPool,
|
||||
objectPoolProvider,
|
||||
new MvcOptions(), // The only option that NewtonsoftJsonInputFormatter uses is SuppressInputFormatterBuffering
|
||||
jsonOptions),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user