Fail more gracefully when option collections cleared
- #4690 - move `ModelBindingMessageProvider` init from `DefaultBindingMetadataProvider` to `DefaultModelMetadata` - in addition to avoiding error cases, this removes some boilerplate - add specific errors to `BodyModelBinderProvider`, `CompilerCache`, `CompositeViewEngine`, `ModelBinderFactory`, and `ObjectResultExecutor` - `DefaultRazorViewEngineFileProviderAccessor.FileProvider` now a `NullFileProvider` in empty case
This commit is contained in:
parent
a852352223
commit
42cea41737
|
|
@ -15,19 +15,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class DefaultBindingMetadataProvider : IBindingMetadataProvider
|
public class DefaultBindingMetadataProvider : IBindingMetadataProvider
|
||||||
{
|
{
|
||||||
private readonly ModelBindingMessageProvider _messageProvider;
|
|
||||||
|
|
||||||
public DefaultBindingMetadataProvider(ModelBindingMessageProvider messageProvider)
|
|
||||||
{
|
|
||||||
if (messageProvider == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(messageProvider));
|
|
||||||
}
|
|
||||||
|
|
||||||
_messageProvider = messageProvider;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public void CreateBindingMetadata(BindingMetadataProviderContext context)
|
public void CreateBindingMetadata(BindingMetadataProviderContext context)
|
||||||
{
|
{
|
||||||
if (context == null)
|
if (context == null)
|
||||||
|
|
@ -65,10 +52,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ModelBindingMessageProvider
|
|
||||||
// Provide a unique instance based on one passed to the constructor.
|
|
||||||
context.BindingMetadata.ModelBindingMessageProvider = new ModelBindingMessageProvider(_messageProvider);
|
|
||||||
|
|
||||||
// PropertyFilterProvider
|
// PropertyFilterProvider
|
||||||
var propertyFilterProviders = context.Attributes.OfType<IPropertyFilterProvider>().ToArray();
|
var propertyFilterProviders = context.Attributes.OfType<IPropertyFilterProvider>().ToArray();
|
||||||
if (propertyFilterProviders.Length == 0)
|
if (propertyFilterProviders.Length == 0)
|
||||||
|
|
|
||||||
|
|
@ -4,12 +4,10 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Mvc.Core;
|
|
||||||
using Microsoft.AspNetCore.Mvc.Formatters;
|
using Microsoft.AspNetCore.Mvc.Formatters;
|
||||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Binders;
|
using Microsoft.AspNetCore.Mvc.ModelBinding.Binders;
|
||||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
|
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
|
||||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.Internal
|
namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
|
|
@ -33,16 +31,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
|
|
||||||
public void Configure(MvcOptions options)
|
public void Configure(MvcOptions options)
|
||||||
{
|
{
|
||||||
// Set up default error messages
|
|
||||||
var messageProvider = options.ModelBindingMessageProvider;
|
|
||||||
messageProvider.MissingBindRequiredValueAccessor = Resources.FormatModelBinding_MissingBindRequiredMember;
|
|
||||||
messageProvider.MissingKeyOrValueAccessor = Resources.FormatKeyValuePair_BothKeyAndValueMustBePresent;
|
|
||||||
messageProvider.ValueMustNotBeNullAccessor = Resources.FormatModelBinding_NullValueNotValid;
|
|
||||||
messageProvider.AttemptedValueIsInvalidAccessor = Resources.FormatModelState_AttemptedValueIsInvalid;
|
|
||||||
messageProvider.UnknownValueIsInvalidAccessor = Resources.FormatModelState_UnknownValueIsInvalid;
|
|
||||||
messageProvider.ValueIsInvalidAccessor = Resources.FormatHtmlGeneration_ValueIsInvalid;
|
|
||||||
messageProvider.ValueMustBeANumberAccessor = Resources.FormatHtmlGeneration_ValueMustBeNumber;
|
|
||||||
|
|
||||||
// Set up ModelBinding
|
// Set up ModelBinding
|
||||||
options.ModelBinderProviders.Add(new BinderTypeModelBinderProvider());
|
options.ModelBinderProviders.Add(new BinderTypeModelBinderProvider());
|
||||||
options.ModelBinderProviders.Add(new ServicesModelBinderProvider());
|
options.ModelBinderProviders.Add(new ServicesModelBinderProvider());
|
||||||
|
|
@ -79,7 +67,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
// by altering the collection of providers.
|
// by altering the collection of providers.
|
||||||
options.ModelMetadataDetailsProviders.Add(new ExcludeBindingMetadataProvider(typeof(Type)));
|
options.ModelMetadataDetailsProviders.Add(new ExcludeBindingMetadataProvider(typeof(Type)));
|
||||||
|
|
||||||
options.ModelMetadataDetailsProviders.Add(new DefaultBindingMetadataProvider(messageProvider));
|
options.ModelMetadataDetailsProviders.Add(new DefaultBindingMetadataProvider());
|
||||||
options.ModelMetadataDetailsProviders.Add(new DefaultValidationMetadataProvider());
|
options.ModelMetadataDetailsProviders.Add(new DefaultValidationMetadataProvider());
|
||||||
|
|
||||||
// Set up validators
|
// Set up validators
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
@ -117,6 +118,16 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
if (formatters == null || formatters.Count == 0)
|
if (formatters == null || formatters.Count == 0)
|
||||||
{
|
{
|
||||||
formatters = OptionsFormatters;
|
formatters = OptionsFormatters;
|
||||||
|
|
||||||
|
// Complain about MvcOptions.OutputFormatters only if the result has an empty Formatters.
|
||||||
|
Debug.Assert(formatters != null, "MvcOptions.OutputFormatters cannot be null.");
|
||||||
|
if (formatters.Count == 0)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(Resources.FormatOutputFormattersAreRequired(
|
||||||
|
typeof(MvcOptions).FullName,
|
||||||
|
nameof(MvcOptions.OutputFormatters),
|
||||||
|
typeof(IOutputFormatter).FullName));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var objectType = result.DeclaredType;
|
var objectType = result.DeclaredType;
|
||||||
|
|
@ -136,7 +147,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
{
|
{
|
||||||
// No formatter supports this.
|
// No formatter supports this.
|
||||||
Logger.NoFormatter(formatterContext);
|
Logger.NoFormatter(formatterContext);
|
||||||
|
|
||||||
context.HttpContext.Response.StatusCode = StatusCodes.Status406NotAcceptable;
|
context.HttpContext.Response.StatusCode = StatusCodes.Status406NotAcceptable;
|
||||||
return TaskCache.CompletedTask;
|
return TaskCache.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
@ -189,7 +200,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
if (acceptableMediaTypes.Count == 0)
|
if (acceptableMediaTypes.Count == 0)
|
||||||
{
|
{
|
||||||
// There is either no Accept header value, or it contained */* and we
|
// There is either no Accept header value, or it contained */* and we
|
||||||
// are not currently respecting the 'browser accept header'.
|
// are not currently respecting the 'browser accept header'.
|
||||||
Logger.NoAcceptForNegotiation();
|
Logger.NoAcceptForNegotiation();
|
||||||
|
|
||||||
selectFormatterWithoutRegardingAcceptHeader = true;
|
selectFormatterWithoutRegardingAcceptHeader = true;
|
||||||
|
|
@ -399,7 +410,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Selects the <see cref="IOutputFormatter"/> to write the response based on the content type values
|
/// Selects the <see cref="IOutputFormatter"/> to write the response based on the content type values
|
||||||
/// present in <paramref name="sortedAcceptableContentTypes"/> and <paramref name="possibleOutputContentTypes"/>.
|
/// present in <paramref name="sortedAcceptableContentTypes"/> and <paramref name="possibleOutputContentTypes"/>.
|
||||||
|
|
@ -423,19 +434,19 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
IList<MediaTypeSegmentWithQuality> sortedAcceptableContentTypes,
|
IList<MediaTypeSegmentWithQuality> sortedAcceptableContentTypes,
|
||||||
MediaTypeCollection possibleOutputContentTypes)
|
MediaTypeCollection possibleOutputContentTypes)
|
||||||
{
|
{
|
||||||
for (var i = 0; i < sortedAcceptableContentTypes.Count; i++)
|
for (var i = 0; i < sortedAcceptableContentTypes.Count; i++)
|
||||||
{
|
{
|
||||||
var acceptableContentType = new MediaType(sortedAcceptableContentTypes[i].MediaType);
|
var acceptableContentType = new MediaType(sortedAcceptableContentTypes[i].MediaType);
|
||||||
for (var j = 0; j < possibleOutputContentTypes.Count; j++)
|
for (var j = 0; j < possibleOutputContentTypes.Count; j++)
|
||||||
{
|
{
|
||||||
var candidateContentType = new MediaType(possibleOutputContentTypes[j]);
|
var candidateContentType = new MediaType(possibleOutputContentTypes[j]);
|
||||||
if (candidateContentType.IsSubsetOf(acceptableContentType))
|
if (candidateContentType.IsSubsetOf(acceptableContentType))
|
||||||
{
|
{
|
||||||
for (var k = 0; k < formatters.Count; k++)
|
for (var k = 0; k < formatters.Count; k++)
|
||||||
{
|
{
|
||||||
var formatter = formatters[k];
|
var formatter = formatters[k];
|
||||||
formatterContext.ContentType = new StringSegment(possibleOutputContentTypes[j]);
|
formatterContext.ContentType = new StringSegment(possibleOutputContentTypes[j]);
|
||||||
if (formatter.CanWriteResult(formatterContext))
|
if (formatter.CanWriteResult(formatterContext))
|
||||||
{
|
{
|
||||||
return formatter;
|
return formatter;
|
||||||
}
|
}
|
||||||
|
|
@ -443,7 +454,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Core;
|
||||||
using Microsoft.AspNetCore.Mvc.Formatters;
|
using Microsoft.AspNetCore.Mvc.Formatters;
|
||||||
using Microsoft.AspNetCore.Mvc.Internal;
|
using Microsoft.AspNetCore.Mvc.Internal;
|
||||||
|
|
||||||
|
|
@ -48,6 +49,14 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||||
if (context.BindingInfo.BindingSource != null &&
|
if (context.BindingInfo.BindingSource != null &&
|
||||||
context.BindingInfo.BindingSource.CanAcceptDataFrom(BindingSource.Body))
|
context.BindingInfo.BindingSource.CanAcceptDataFrom(BindingSource.Body))
|
||||||
{
|
{
|
||||||
|
if (_formatters.Count == 0)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(Resources.FormatInputFormattersAreRequired(
|
||||||
|
typeof(MvcOptions).FullName,
|
||||||
|
nameof(MvcOptions.InputFormatters),
|
||||||
|
typeof(IInputFormatter).FullName));
|
||||||
|
}
|
||||||
|
|
||||||
return new BodyModelBinder(_formatters, _readerFactory);
|
return new BodyModelBinder(_formatters, _readerFactory);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,52 +1,25 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System.Collections.Generic;
|
||||||
using Microsoft.AspNetCore.Mvc.Core;
|
|
||||||
using Microsoft.AspNetCore.Mvc.Internal;
|
using Microsoft.AspNetCore.Mvc.Internal;
|
||||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
|
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
{
|
{
|
||||||
public class EmptyModelMetadataProvider : DefaultModelMetadataProvider
|
public class EmptyModelMetadataProvider : DefaultModelMetadataProvider
|
||||||
{
|
{
|
||||||
public EmptyModelMetadataProvider()
|
public EmptyModelMetadataProvider()
|
||||||
: base(new DefaultCompositeMetadataDetailsProvider(new IMetadataDetailsProvider[]
|
: base(
|
||||||
{
|
new DefaultCompositeMetadataDetailsProvider(new List<IMetadataDetailsProvider>()),
|
||||||
new MessageOnlyBindingProvider()
|
new OptionsAccessor())
|
||||||
}))
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
private class MessageOnlyBindingProvider : IBindingMetadataProvider
|
private class OptionsAccessor : IOptions<MvcOptions>
|
||||||
{
|
{
|
||||||
private readonly ModelBindingMessageProvider _messageProvider = CreateMessageProvider();
|
public MvcOptions Value { get; } = new MvcOptions();
|
||||||
|
|
||||||
public void CreateBindingMetadata(BindingMetadataProviderContext context)
|
|
||||||
{
|
|
||||||
if (context == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(context));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Don't bother with ModelBindingMessageProvider copy constructor. No other provider can change the
|
|
||||||
// delegates.
|
|
||||||
context.BindingMetadata.ModelBindingMessageProvider = _messageProvider;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ModelBindingMessageProvider CreateMessageProvider()
|
|
||||||
{
|
|
||||||
return new ModelBindingMessageProvider
|
|
||||||
{
|
|
||||||
MissingBindRequiredValueAccessor = Resources.FormatModelBinding_MissingBindRequiredMember,
|
|
||||||
MissingKeyOrValueAccessor = Resources.FormatKeyValuePair_BothKeyAndValueMustBePresent,
|
|
||||||
ValueMustNotBeNullAccessor = Resources.FormatModelBinding_NullValueNotValid,
|
|
||||||
AttemptedValueIsInvalidAccessor = Resources.FormatModelState_AttemptedValueIsInvalid,
|
|
||||||
UnknownValueIsInvalidAccessor = Resources.FormatModelState_UnknownValueIsInvalid,
|
|
||||||
ValueIsInvalidAccessor = Resources.FormatHtmlGeneration_ValueIsInvalid,
|
|
||||||
ValueMustBeANumberAccessor = Resources.FormatHtmlGeneration_ValueMustBeNumber,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -17,6 +17,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
||||||
private readonly ICompositeMetadataDetailsProvider _detailsProvider;
|
private readonly ICompositeMetadataDetailsProvider _detailsProvider;
|
||||||
private readonly DefaultMetadataDetails _details;
|
private readonly DefaultMetadataDetails _details;
|
||||||
|
|
||||||
|
// Default message provider for all DefaultModelMetadata instances; cloned before exposing to
|
||||||
|
// IBindingMetadataProvider instances to ensure customizations are not accidentally shared.
|
||||||
|
private readonly ModelBindingMessageProvider _modelBindingMessageProvider;
|
||||||
|
|
||||||
private ReadOnlyDictionary<object, object> _additionalValues;
|
private ReadOnlyDictionary<object, object> _additionalValues;
|
||||||
private ModelMetadata _elementMetadata;
|
private ModelMetadata _elementMetadata;
|
||||||
private bool? _isBindingRequired;
|
private bool? _isBindingRequired;
|
||||||
|
|
@ -36,6 +40,22 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
||||||
IModelMetadataProvider provider,
|
IModelMetadataProvider provider,
|
||||||
ICompositeMetadataDetailsProvider detailsProvider,
|
ICompositeMetadataDetailsProvider detailsProvider,
|
||||||
DefaultMetadataDetails details)
|
DefaultMetadataDetails details)
|
||||||
|
: this(provider, detailsProvider, details, new ModelBindingMessageProvider())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new <see cref="DefaultModelMetadata"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="provider">The <see cref="IModelMetadataProvider"/>.</param>
|
||||||
|
/// <param name="detailsProvider">The <see cref="ICompositeMetadataDetailsProvider"/>.</param>
|
||||||
|
/// <param name="details">The <see cref="DefaultMetadataDetails"/>.</param>
|
||||||
|
/// <param name="modelBindingMessageProvider">The <see cref="Metadata.ModelBindingMessageProvider"/>.</param>
|
||||||
|
public DefaultModelMetadata(
|
||||||
|
IModelMetadataProvider provider,
|
||||||
|
ICompositeMetadataDetailsProvider detailsProvider,
|
||||||
|
DefaultMetadataDetails details,
|
||||||
|
ModelBindingMessageProvider modelBindingMessageProvider)
|
||||||
: base(details.Key)
|
: base(details.Key)
|
||||||
{
|
{
|
||||||
if (provider == null)
|
if (provider == null)
|
||||||
|
|
@ -53,9 +73,15 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
||||||
throw new ArgumentNullException(nameof(details));
|
throw new ArgumentNullException(nameof(details));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (modelBindingMessageProvider == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(modelBindingMessageProvider));
|
||||||
|
}
|
||||||
|
|
||||||
_provider = provider;
|
_provider = provider;
|
||||||
_detailsProvider = detailsProvider;
|
_detailsProvider = detailsProvider;
|
||||||
_details = details;
|
_details = details;
|
||||||
|
_modelBindingMessageProvider = modelBindingMessageProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -82,6 +108,11 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
||||||
if (_details.BindingMetadata == null)
|
if (_details.BindingMetadata == null)
|
||||||
{
|
{
|
||||||
var context = new BindingMetadataProviderContext(Identity, _details.ModelAttributes);
|
var context = new BindingMetadataProviderContext(Identity, _details.ModelAttributes);
|
||||||
|
|
||||||
|
// Provide a unique ModelBindingMessageProvider instance so providers' customizations are per-type.
|
||||||
|
context.BindingMetadata.ModelBindingMessageProvider =
|
||||||
|
new ModelBindingMessageProvider(_modelBindingMessageProvider);
|
||||||
|
|
||||||
_detailsProvider.CreateBindingMetadata(context);
|
_detailsProvider.CreateBindingMetadata(context);
|
||||||
_details.BindingMetadata = context.BindingMetadata;
|
_details.BindingMetadata = context.BindingMetadata;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using Microsoft.Extensions.Internal;
|
using Microsoft.Extensions.Internal;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
||||||
{
|
{
|
||||||
|
|
@ -23,11 +24,35 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="detailsProvider">The <see cref="ICompositeMetadataDetailsProvider"/>.</param>
|
/// <param name="detailsProvider">The <see cref="ICompositeMetadataDetailsProvider"/>.</param>
|
||||||
public DefaultModelMetadataProvider(ICompositeMetadataDetailsProvider detailsProvider)
|
public DefaultModelMetadataProvider(ICompositeMetadataDetailsProvider detailsProvider)
|
||||||
|
: this(detailsProvider, new ModelBindingMessageProvider())
|
||||||
{
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new <see cref="DefaultModelMetadataProvider"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="detailsProvider">The <see cref="ICompositeMetadataDetailsProvider"/>.</param>
|
||||||
|
/// <param name="optionsAccessor">The accessor for <see cref="MvcOptions"/>.</param>
|
||||||
|
public DefaultModelMetadataProvider(
|
||||||
|
ICompositeMetadataDetailsProvider detailsProvider,
|
||||||
|
IOptions<MvcOptions> optionsAccessor)
|
||||||
|
: this(detailsProvider, GetMessageProvider(optionsAccessor))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private DefaultModelMetadataProvider(
|
||||||
|
ICompositeMetadataDetailsProvider detailsProvider,
|
||||||
|
ModelBindingMessageProvider modelBindingMessageProvider)
|
||||||
|
{
|
||||||
|
if (detailsProvider == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(detailsProvider));
|
||||||
|
}
|
||||||
|
|
||||||
DetailsProvider = detailsProvider;
|
DetailsProvider = detailsProvider;
|
||||||
|
ModelBindingMessageProvider = modelBindingMessageProvider;
|
||||||
|
|
||||||
_cacheEntryFactory = CreateCacheEntry;
|
_cacheEntryFactory = CreateCacheEntry;
|
||||||
|
|
||||||
_metadataCacheEntryForObjectType = GetMetadataCacheEntryForObjectType();
|
_metadataCacheEntryForObjectType = GetMetadataCacheEntryForObjectType();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -36,6 +61,12 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected ICompositeMetadataDetailsProvider DetailsProvider { get; }
|
protected ICompositeMetadataDetailsProvider DetailsProvider { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the <see cref="Metadata.ModelBindingMessageProvider"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>Same as <see cref="MvcOptions.ModelBindingMessageProvider"/> in all production scenarios.</value>
|
||||||
|
protected ModelBindingMessageProvider ModelBindingMessageProvider { get; }
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public virtual IEnumerable<ModelMetadata> GetMetadataForProperties(Type modelType)
|
public virtual IEnumerable<ModelMetadata> GetMetadataForProperties(Type modelType)
|
||||||
{
|
{
|
||||||
|
|
@ -78,6 +109,16 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
||||||
return cacheEntry.Metadata;
|
return cacheEntry.Metadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static ModelBindingMessageProvider GetMessageProvider(IOptions<MvcOptions> optionsAccessor)
|
||||||
|
{
|
||||||
|
if (optionsAccessor == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(optionsAccessor));
|
||||||
|
}
|
||||||
|
|
||||||
|
return optionsAccessor.Value.ModelBindingMessageProvider;
|
||||||
|
}
|
||||||
|
|
||||||
private ModelMetadataCacheEntry GetCacheEntry(Type modelType)
|
private ModelMetadataCacheEntry GetCacheEntry(Type modelType)
|
||||||
{
|
{
|
||||||
ModelMetadataCacheEntry cacheEntry;
|
ModelMetadataCacheEntry cacheEntry;
|
||||||
|
|
@ -123,7 +164,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
protected virtual ModelMetadata CreateModelMetadata(DefaultMetadataDetails entry)
|
protected virtual ModelMetadata CreateModelMetadata(DefaultMetadataDetails entry)
|
||||||
{
|
{
|
||||||
return new DefaultModelMetadata(this, DetailsProvider, entry);
|
return new DefaultModelMetadata(this, DetailsProvider, entry, ModelBindingMessageProvider);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Core;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
||||||
{
|
{
|
||||||
|
|
@ -23,6 +24,13 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ModelBindingMessageProvider()
|
public ModelBindingMessageProvider()
|
||||||
{
|
{
|
||||||
|
MissingBindRequiredValueAccessor = Resources.FormatModelBinding_MissingBindRequiredMember;
|
||||||
|
MissingKeyOrValueAccessor = Resources.FormatKeyValuePair_BothKeyAndValueMustBePresent;
|
||||||
|
ValueMustNotBeNullAccessor = Resources.FormatModelBinding_NullValueNotValid;
|
||||||
|
AttemptedValueIsInvalidAccessor = Resources.FormatModelState_AttemptedValueIsInvalid;
|
||||||
|
UnknownValueIsInvalidAccessor = Resources.FormatModelState_UnknownValueIsInvalid;
|
||||||
|
ValueIsInvalidAccessor = Resources.FormatHtmlGeneration_ValueIsInvalid;
|
||||||
|
ValueMustBeANumberAccessor = Resources.FormatHtmlGeneration_ValueMustBeNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -46,12 +46,20 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
throw new ArgumentNullException(nameof(context));
|
throw new ArgumentNullException(nameof(context));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_providers.Length == 0)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(Resources.FormatModelBinderProvidersAreRequired(
|
||||||
|
typeof(MvcOptions).FullName,
|
||||||
|
nameof(MvcOptions.ModelBinderProviders),
|
||||||
|
typeof(IModelBinderProvider).FullName));
|
||||||
|
}
|
||||||
|
|
||||||
IModelBinder binder;
|
IModelBinder binder;
|
||||||
if (TryGetCachedBinder(context.Metadata, context.CacheToken, out binder))
|
if (TryGetCachedBinder(context.Metadata, context.CacheToken, out binder))
|
||||||
{
|
{
|
||||||
return binder;
|
return binder;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Perf: We're calling the Uncached version of the API here so we can:
|
// Perf: We're calling the Uncached version of the API here so we can:
|
||||||
// 1. avoid allocating a context when the value is already cached
|
// 1. avoid allocating a context when the value is already cached
|
||||||
// 2. avoid checking the cache twice when the value is not cached
|
// 2. avoid checking the cache twice when the value is not cached
|
||||||
|
|
|
||||||
|
|
@ -1162,6 +1162,54 @@ namespace Microsoft.AspNetCore.Mvc.Core
|
||||||
return string.Format(CultureInfo.CurrentCulture, GetString("CouldNotCreateIModelBinder"), p0);
|
return string.Format(CultureInfo.CurrentCulture, GetString("CouldNotCreateIModelBinder"), p0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// '{0}.{1}' must not be empty. At least one '{2}' is required to bind from the body.
|
||||||
|
/// </summary>
|
||||||
|
internal static string InputFormattersAreRequired
|
||||||
|
{
|
||||||
|
get { return GetString("InputFormattersAreRequired"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// '{0}.{1}' must not be empty. At least one '{2}' is required to bind from the body.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatInputFormattersAreRequired(object p0, object p1, object p2)
|
||||||
|
{
|
||||||
|
return string.Format(CultureInfo.CurrentCulture, GetString("InputFormattersAreRequired"), p0, p1, p2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// '{0}.{1}' must not be empty. At least one '{2}' is required to model bind.
|
||||||
|
/// </summary>
|
||||||
|
internal static string ModelBinderProvidersAreRequired
|
||||||
|
{
|
||||||
|
get { return GetString("ModelBinderProvidersAreRequired"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// '{0}.{1}' must not be empty. At least one '{2}' is required to model bind.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatModelBinderProvidersAreRequired(object p0, object p1, object p2)
|
||||||
|
{
|
||||||
|
return string.Format(CultureInfo.CurrentCulture, GetString("ModelBinderProvidersAreRequired"), p0, p1, p2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// '{0}.{1}' must not be empty. At least one '{2}' is required to format a response.
|
||||||
|
/// </summary>
|
||||||
|
internal static string OutputFormattersAreRequired
|
||||||
|
{
|
||||||
|
get { return GetString("OutputFormattersAreRequired"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// '{0}.{1}' must not be empty. At least one '{2}' is required to format a response.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatOutputFormattersAreRequired(object p0, object p1, object p2)
|
||||||
|
{
|
||||||
|
return string.Format(CultureInfo.CurrentCulture, GetString("OutputFormattersAreRequired"), p0, p1, p2);
|
||||||
|
}
|
||||||
|
|
||||||
private static string GetString(string name, params string[] formatterNames)
|
private static string GetString(string name, params string[] formatterNames)
|
||||||
{
|
{
|
||||||
var value = _resourceManager.GetString(name);
|
var value = _resourceManager.GetString(name);
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,17 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<root>
|
<root>
|
||||||
<!--
|
<!--
|
||||||
Microsoft ResX Schema
|
Microsoft ResX Schema
|
||||||
|
|
||||||
Version 2.0
|
Version 2.0
|
||||||
|
|
||||||
The primary goals of this format is to allow a simple XML format
|
The primary goals of this format is to allow a simple XML format
|
||||||
that is mostly human readable. The generation and parsing of the
|
that is mostly human readable. The generation and parsing of the
|
||||||
various data types are done through the TypeConverter classes
|
various data types are done through the TypeConverter classes
|
||||||
associated with the data types.
|
associated with the data types.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
... ado.net/XML headers & schema ...
|
... ado.net/XML headers & schema ...
|
||||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||||
<resheader name="version">2.0</resheader>
|
<resheader name="version">2.0</resheader>
|
||||||
|
|
@ -26,36 +26,36 @@
|
||||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||||
<comment>This is a comment</comment>
|
<comment>This is a comment</comment>
|
||||||
</data>
|
</data>
|
||||||
|
|
||||||
There are any number of "resheader" rows that contain simple
|
There are any number of "resheader" rows that contain simple
|
||||||
name/value pairs.
|
name/value pairs.
|
||||||
|
|
||||||
Each data row contains a name, and value. The row also contains a
|
Each data row contains a name, and value. The row also contains a
|
||||||
type or mimetype. Type corresponds to a .NET class that support
|
type or mimetype. Type corresponds to a .NET class that support
|
||||||
text/value conversion through the TypeConverter architecture.
|
text/value conversion through the TypeConverter architecture.
|
||||||
Classes that don't support this are serialized and stored with the
|
Classes that don't support this are serialized and stored with the
|
||||||
mimetype set.
|
mimetype set.
|
||||||
|
|
||||||
The mimetype is used for serialized objects, and tells the
|
The mimetype is used for serialized objects, and tells the
|
||||||
ResXResourceReader how to depersist the object. This is currently not
|
ResXResourceReader how to depersist the object. This is currently not
|
||||||
extensible. For a given mimetype the value must be set accordingly:
|
extensible. For a given mimetype the value must be set accordingly:
|
||||||
|
|
||||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||||
that the ResXResourceWriter will generate, however the reader can
|
that the ResXResourceWriter will generate, however the reader can
|
||||||
read any of the formats listed below.
|
read any of the formats listed below.
|
||||||
|
|
||||||
mimetype: application/x-microsoft.net.object.binary.base64
|
mimetype: application/x-microsoft.net.object.binary.base64
|
||||||
value : The object must be serialized with
|
value : The object must be serialized with
|
||||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||||
: and then encoded with base64 encoding.
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
mimetype: application/x-microsoft.net.object.soap.base64
|
mimetype: application/x-microsoft.net.object.soap.base64
|
||||||
value : The object must be serialized with
|
value : The object must be serialized with
|
||||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||||
: and then encoded with base64 encoding.
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||||
value : The object must be serialized into a byte array
|
value : The object must be serialized into a byte array
|
||||||
: using a System.ComponentModel.TypeConverter
|
: using a System.ComponentModel.TypeConverter
|
||||||
: and then encoded with base64 encoding.
|
: and then encoded with base64 encoding.
|
||||||
-->
|
-->
|
||||||
|
|
@ -343,4 +343,13 @@
|
||||||
<data name="CouldNotCreateIModelBinder" xml:space="preserve">
|
<data name="CouldNotCreateIModelBinder" xml:space="preserve">
|
||||||
<value>Could not create a model binder for model object of type '{0}'.</value>
|
<value>Could not create a model binder for model object of type '{0}'.</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="InputFormattersAreRequired" xml:space="preserve">
|
||||||
|
<value>'{0}.{1}' must not be empty. At least one '{2}' is required to bind from the body.</value>
|
||||||
|
</data>
|
||||||
|
<data name="ModelBinderProvidersAreRequired" xml:space="preserve">
|
||||||
|
<value>'{0}.{1}' must not be empty. At least one '{2}' is required to model bind.</value>
|
||||||
|
</data>
|
||||||
|
<data name="OutputFormattersAreRequired" xml:space="preserve">
|
||||||
|
<value>'{0}.{1}' must not be empty. At least one '{2}' is required to format a response.</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|
@ -116,6 +116,15 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
||||||
return cacheEntry;
|
return cacheEntry;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_fileProvider is NullFileProvider)
|
||||||
|
{
|
||||||
|
var message = Resources.FormatFileProvidersAreRequired(
|
||||||
|
typeof(RazorViewEngineOptions).FullName,
|
||||||
|
nameof(RazorViewEngineOptions.FileProviders),
|
||||||
|
typeof(IFileProvider).FullName);
|
||||||
|
throw new InvalidOperationException(message);
|
||||||
|
}
|
||||||
|
|
||||||
fileInfo = _fileProvider.GetFileInfo(normalizedPath);
|
fileInfo = _fileProvider.GetFileInfo(normalizedPath);
|
||||||
if (!fileInfo.Exists)
|
if (!fileInfo.Exists)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,11 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
||||||
public DefaultRazorViewEngineFileProviderAccessor(IOptions<RazorViewEngineOptions> optionsAccessor)
|
public DefaultRazorViewEngineFileProviderAccessor(IOptions<RazorViewEngineOptions> optionsAccessor)
|
||||||
{
|
{
|
||||||
var fileProviders = optionsAccessor.Value.FileProviders;
|
var fileProviders = optionsAccessor.Value.FileProviders;
|
||||||
if (fileProviders.Count == 1)
|
if (fileProviders.Count == 0)
|
||||||
|
{
|
||||||
|
FileProvider = new NullFileProvider();
|
||||||
|
}
|
||||||
|
else if (fileProviders.Count == 1)
|
||||||
{
|
{
|
||||||
FileProvider = fileProviders[0];
|
FileProvider = fileProviders[0];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -494,6 +494,22 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
||||||
return GetString("RazorPage_NestingAttributeWritingScopesNotSupported");
|
return GetString("RazorPage_NestingAttributeWritingScopesNotSupported");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// '{0}.{1}' must not be empty. At least one '{2}' is required to locate a view for rendering.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FileProvidersAreRequired
|
||||||
|
{
|
||||||
|
get { return GetString("FileProvidersAreRequired"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// '{0}.{1}' must not be empty. At least one '{2}' is required to locate a view for rendering.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatFileProvidersAreRequired(object p0, object p1, object p2)
|
||||||
|
{
|
||||||
|
return string.Format(CultureInfo.CurrentCulture, GetString("FileProvidersAreRequired"), p0, p1, p2);
|
||||||
|
}
|
||||||
|
|
||||||
private static string GetString(string name, params string[] formatterNames)
|
private static string GetString(string name, params string[] formatterNames)
|
||||||
{
|
{
|
||||||
var value = _resourceManager.GetString(name);
|
var value = _resourceManager.GetString(name);
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,17 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<root>
|
<root>
|
||||||
<!--
|
<!--
|
||||||
Microsoft ResX Schema
|
Microsoft ResX Schema
|
||||||
|
|
||||||
Version 2.0
|
Version 2.0
|
||||||
|
|
||||||
The primary goals of this format is to allow a simple XML format
|
The primary goals of this format is to allow a simple XML format
|
||||||
that is mostly human readable. The generation and parsing of the
|
that is mostly human readable. The generation and parsing of the
|
||||||
various data types are done through the TypeConverter classes
|
various data types are done through the TypeConverter classes
|
||||||
associated with the data types.
|
associated with the data types.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
... ado.net/XML headers & schema ...
|
... ado.net/XML headers & schema ...
|
||||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||||
<resheader name="version">2.0</resheader>
|
<resheader name="version">2.0</resheader>
|
||||||
|
|
@ -26,36 +26,36 @@
|
||||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||||
<comment>This is a comment</comment>
|
<comment>This is a comment</comment>
|
||||||
</data>
|
</data>
|
||||||
|
|
||||||
There are any number of "resheader" rows that contain simple
|
There are any number of "resheader" rows that contain simple
|
||||||
name/value pairs.
|
name/value pairs.
|
||||||
|
|
||||||
Each data row contains a name, and value. The row also contains a
|
Each data row contains a name, and value. The row also contains a
|
||||||
type or mimetype. Type corresponds to a .NET class that support
|
type or mimetype. Type corresponds to a .NET class that support
|
||||||
text/value conversion through the TypeConverter architecture.
|
text/value conversion through the TypeConverter architecture.
|
||||||
Classes that don't support this are serialized and stored with the
|
Classes that don't support this are serialized and stored with the
|
||||||
mimetype set.
|
mimetype set.
|
||||||
|
|
||||||
The mimetype is used for serialized objects, and tells the
|
The mimetype is used for serialized objects, and tells the
|
||||||
ResXResourceReader how to depersist the object. This is currently not
|
ResXResourceReader how to depersist the object. This is currently not
|
||||||
extensible. For a given mimetype the value must be set accordingly:
|
extensible. For a given mimetype the value must be set accordingly:
|
||||||
|
|
||||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||||
that the ResXResourceWriter will generate, however the reader can
|
that the ResXResourceWriter will generate, however the reader can
|
||||||
read any of the formats listed below.
|
read any of the formats listed below.
|
||||||
|
|
||||||
mimetype: application/x-microsoft.net.object.binary.base64
|
mimetype: application/x-microsoft.net.object.binary.base64
|
||||||
value : The object must be serialized with
|
value : The object must be serialized with
|
||||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||||
: and then encoded with base64 encoding.
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
mimetype: application/x-microsoft.net.object.soap.base64
|
mimetype: application/x-microsoft.net.object.soap.base64
|
||||||
value : The object must be serialized with
|
value : The object must be serialized with
|
||||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||||
: and then encoded with base64 encoding.
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||||
value : The object must be serialized into a byte array
|
value : The object must be serialized into a byte array
|
||||||
: using a System.ComponentModel.TypeConverter
|
: using a System.ComponentModel.TypeConverter
|
||||||
: and then encoded with base64 encoding.
|
: and then encoded with base64 encoding.
|
||||||
-->
|
-->
|
||||||
|
|
@ -209,4 +209,7 @@
|
||||||
<data name="RazorPage_NestingAttributeWritingScopesNotSupported" xml:space="preserve">
|
<data name="RazorPage_NestingAttributeWritingScopesNotSupported" xml:space="preserve">
|
||||||
<value>Nesting of TagHelper attribute writing scopes is not supported.</value>
|
<value>Nesting of TagHelper attribute writing scopes is not supported.</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="FileProvidersAreRequired" xml:space="preserve">
|
||||||
|
<value>'{0}.{1}' must not be empty. At least one '{2}' is required to locate a view for rendering.</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|
@ -890,6 +890,22 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
||||||
return string.Format(CultureInfo.CurrentCulture, GetString("CreateModelExpression_NullModelMetadata"), p0, p1);
|
return string.Format(CultureInfo.CurrentCulture, GetString("CreateModelExpression_NullModelMetadata"), p0, p1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// '{0}.{1}' must not be empty. At least one '{2}' is required to locate a view for rendering.
|
||||||
|
/// </summary>
|
||||||
|
internal static string ViewEnginesAreRequired
|
||||||
|
{
|
||||||
|
get { return GetString("ViewEnginesAreRequired"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// '{0}.{1}' must not be empty. At least one '{2}' is required to locate a view for rendering.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatViewEnginesAreRequired(object p0, object p1, object p2)
|
||||||
|
{
|
||||||
|
return string.Format(CultureInfo.CurrentCulture, GetString("ViewEnginesAreRequired"), p0, p1, p2);
|
||||||
|
}
|
||||||
|
|
||||||
private static string GetString(string name, params string[] formatterNames)
|
private static string GetString(string name, params string[] formatterNames)
|
||||||
{
|
{
|
||||||
var value = _resourceManager.GetString(name);
|
var value = _resourceManager.GetString(name);
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,17 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<root>
|
<root>
|
||||||
<!--
|
<!--
|
||||||
Microsoft ResX Schema
|
Microsoft ResX Schema
|
||||||
|
|
||||||
Version 2.0
|
Version 2.0
|
||||||
|
|
||||||
The primary goals of this format is to allow a simple XML format
|
The primary goals of this format is to allow a simple XML format
|
||||||
that is mostly human readable. The generation and parsing of the
|
that is mostly human readable. The generation and parsing of the
|
||||||
various data types are done through the TypeConverter classes
|
various data types are done through the TypeConverter classes
|
||||||
associated with the data types.
|
associated with the data types.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
... ado.net/XML headers & schema ...
|
... ado.net/XML headers & schema ...
|
||||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||||
<resheader name="version">2.0</resheader>
|
<resheader name="version">2.0</resheader>
|
||||||
|
|
@ -26,36 +26,36 @@
|
||||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||||
<comment>This is a comment</comment>
|
<comment>This is a comment</comment>
|
||||||
</data>
|
</data>
|
||||||
|
|
||||||
There are any number of "resheader" rows that contain simple
|
There are any number of "resheader" rows that contain simple
|
||||||
name/value pairs.
|
name/value pairs.
|
||||||
|
|
||||||
Each data row contains a name, and value. The row also contains a
|
Each data row contains a name, and value. The row also contains a
|
||||||
type or mimetype. Type corresponds to a .NET class that support
|
type or mimetype. Type corresponds to a .NET class that support
|
||||||
text/value conversion through the TypeConverter architecture.
|
text/value conversion through the TypeConverter architecture.
|
||||||
Classes that don't support this are serialized and stored with the
|
Classes that don't support this are serialized and stored with the
|
||||||
mimetype set.
|
mimetype set.
|
||||||
|
|
||||||
The mimetype is used for serialized objects, and tells the
|
The mimetype is used for serialized objects, and tells the
|
||||||
ResXResourceReader how to depersist the object. This is currently not
|
ResXResourceReader how to depersist the object. This is currently not
|
||||||
extensible. For a given mimetype the value must be set accordingly:
|
extensible. For a given mimetype the value must be set accordingly:
|
||||||
|
|
||||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||||
that the ResXResourceWriter will generate, however the reader can
|
that the ResXResourceWriter will generate, however the reader can
|
||||||
read any of the formats listed below.
|
read any of the formats listed below.
|
||||||
|
|
||||||
mimetype: application/x-microsoft.net.object.binary.base64
|
mimetype: application/x-microsoft.net.object.binary.base64
|
||||||
value : The object must be serialized with
|
value : The object must be serialized with
|
||||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||||
: and then encoded with base64 encoding.
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
mimetype: application/x-microsoft.net.object.soap.base64
|
mimetype: application/x-microsoft.net.object.soap.base64
|
||||||
value : The object must be serialized with
|
value : The object must be serialized with
|
||||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||||
: and then encoded with base64 encoding.
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||||
value : The object must be serialized into a byte array
|
value : The object must be serialized into a byte array
|
||||||
: using a System.ComponentModel.TypeConverter
|
: using a System.ComponentModel.TypeConverter
|
||||||
: and then encoded with base64 encoding.
|
: and then encoded with base64 encoding.
|
||||||
-->
|
-->
|
||||||
|
|
@ -283,4 +283,7 @@
|
||||||
<data name="CreateModelExpression_NullModelMetadata" xml:space="preserve">
|
<data name="CreateModelExpression_NullModelMetadata" xml:space="preserve">
|
||||||
<value>The {0} was unable to provide metadata for expression '{1}'.</value>
|
<value>The {0} was unable to provide metadata for expression '{1}'.</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="ViewEnginesAreRequired" xml:space="preserve">
|
||||||
|
<value>'{0}.{1}' must not be empty. At least one '{2}' is required to locate a view for rendering.</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|
@ -37,6 +37,14 @@ namespace Microsoft.AspNetCore.Mvc.ViewEngines
|
||||||
throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(viewName));
|
throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(viewName));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ViewEngines.Count == 0)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(Resources.FormatViewEnginesAreRequired(
|
||||||
|
typeof(MvcViewOptions).FullName,
|
||||||
|
nameof(MvcViewOptions.ViewEngines),
|
||||||
|
typeof(IViewEngine).FullName));
|
||||||
|
}
|
||||||
|
|
||||||
// Do not allocate in the common cases: ViewEngines contains one entry or initial attempt is successful.
|
// Do not allocate in the common cases: ViewEngines contains one entry or initial attempt is successful.
|
||||||
IEnumerable<string> searchedLocations = null;
|
IEnumerable<string> searchedLocations = null;
|
||||||
List<string> searchedList = null;
|
List<string> searchedList = null;
|
||||||
|
|
@ -77,6 +85,14 @@ namespace Microsoft.AspNetCore.Mvc.ViewEngines
|
||||||
throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(viewPath));
|
throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(viewPath));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ViewEngines.Count == 0)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(Resources.FormatViewEnginesAreRequired(
|
||||||
|
typeof(MvcViewOptions).FullName,
|
||||||
|
nameof(MvcViewOptions.ViewEngines),
|
||||||
|
typeof(IViewEngine).FullName));
|
||||||
|
}
|
||||||
|
|
||||||
// Do not allocate in the common cases: ViewEngines contains one entry or initial attempt is successful.
|
// Do not allocate in the common cases: ViewEngines contains one entry or initial attempt is successful.
|
||||||
IEnumerable<string> searchedLocations = null;
|
IEnumerable<string> searchedLocations = null;
|
||||||
List<string> searchedList = null;
|
List<string> searchedList = null;
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,9 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
|
||||||
using Microsoft.AspNetCore.Mvc.Internal;
|
using Microsoft.AspNetCore.Mvc.Internal;
|
||||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
|
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
|
|
@ -887,20 +887,13 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
var expected = "Hmm, the supplied value is not valid for Length.";
|
var expected = "Hmm, the supplied value is not valid for Length.";
|
||||||
var dictionary = new ModelStateDictionary();
|
var dictionary = new ModelStateDictionary();
|
||||||
|
|
||||||
var messageProvider = new ModelBindingMessageProvider
|
var bindingMetadataProvider = new DefaultBindingMetadataProvider();
|
||||||
{
|
|
||||||
MissingBindRequiredValueAccessor = name => "Unexpected MissingBindRequiredValueAccessor use",
|
|
||||||
MissingKeyOrValueAccessor = () => "Unexpected MissingKeyOrValueAccessor use",
|
|
||||||
ValueMustNotBeNullAccessor = value => "Unexpected ValueMustNotBeNullAccessor use",
|
|
||||||
AttemptedValueIsInvalidAccessor =
|
|
||||||
(value, name) => "Unexpected InvalidValueWithKnownAttemptedValueAccessor use",
|
|
||||||
UnknownValueIsInvalidAccessor = name => $"Hmm, the supplied value is not valid for { name }.",
|
|
||||||
ValueIsInvalidAccessor = value => "Unexpected InvalidValueWithUnknownModelErrorAccessor use",
|
|
||||||
ValueMustBeANumberAccessor = name => "Unexpected ValueMustBeANumberAccessor use",
|
|
||||||
};
|
|
||||||
var bindingMetadataProvider = new DefaultBindingMetadataProvider(messageProvider);
|
|
||||||
var compositeProvider = new DefaultCompositeMetadataDetailsProvider(new[] { bindingMetadataProvider });
|
var compositeProvider = new DefaultCompositeMetadataDetailsProvider(new[] { bindingMetadataProvider });
|
||||||
var provider = new DefaultModelMetadataProvider(compositeProvider);
|
var optionsAccessor = new OptionsAccessor();
|
||||||
|
optionsAccessor.Value.ModelBindingMessageProvider.UnknownValueIsInvalidAccessor =
|
||||||
|
name => $"Hmm, the supplied value is not valid for { name }.";
|
||||||
|
|
||||||
|
var provider = new DefaultModelMetadataProvider(compositeProvider, optionsAccessor);
|
||||||
var metadata = provider.GetMetadataForProperty(typeof(string), nameof(string.Length));
|
var metadata = provider.GetMetadataForProperty(typeof(string), nameof(string.Length));
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
|
|
@ -939,20 +932,13 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
var dictionary = new ModelStateDictionary();
|
var dictionary = new ModelStateDictionary();
|
||||||
dictionary.SetModelValue("key", new string[] { "some value" }, "some value");
|
dictionary.SetModelValue("key", new string[] { "some value" }, "some value");
|
||||||
|
|
||||||
var messageProvider = new ModelBindingMessageProvider
|
var bindingMetadataProvider = new DefaultBindingMetadataProvider();
|
||||||
{
|
|
||||||
MissingBindRequiredValueAccessor = name => "Unexpected MissingBindRequiredValueAccessor use",
|
|
||||||
MissingKeyOrValueAccessor = () => "Unexpected MissingKeyOrValueAccessor use",
|
|
||||||
ValueMustNotBeNullAccessor = value => "Unexpected ValueMustNotBeNullAccessor use",
|
|
||||||
AttemptedValueIsInvalidAccessor =
|
|
||||||
(value, name) => $"Hmm, the value '{ value }' is not valid for { name }.",
|
|
||||||
UnknownValueIsInvalidAccessor = name => "Unexpected InvalidValueWithUnknownAttemptedValueAccessor use",
|
|
||||||
ValueIsInvalidAccessor = value => "Unexpected InvalidValueWithUnknownModelErrorAccessor use",
|
|
||||||
ValueMustBeANumberAccessor = name => "Unexpected ValueMustBeANumberAccessor use",
|
|
||||||
};
|
|
||||||
var bindingMetadataProvider = new DefaultBindingMetadataProvider(messageProvider);
|
|
||||||
var compositeProvider = new DefaultCompositeMetadataDetailsProvider(new[] { bindingMetadataProvider });
|
var compositeProvider = new DefaultCompositeMetadataDetailsProvider(new[] { bindingMetadataProvider });
|
||||||
var provider = new DefaultModelMetadataProvider(compositeProvider);
|
var optionsAccessor = new OptionsAccessor();
|
||||||
|
optionsAccessor.Value.ModelBindingMessageProvider.AttemptedValueIsInvalidAccessor =
|
||||||
|
(value, name) => $"Hmm, the value '{ value }' is not valid for { name }.";
|
||||||
|
|
||||||
|
var provider = new DefaultModelMetadataProvider(compositeProvider, optionsAccessor);
|
||||||
var metadata = provider.GetMetadataForProperty(typeof(string), nameof(string.Length));
|
var metadata = provider.GetMetadataForProperty(typeof(string), nameof(string.Length));
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
|
|
@ -1303,5 +1289,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal("value1", property.RawValue);
|
Assert.Equal("value1", property.RawValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class OptionsAccessor : IOptions<MvcOptions>
|
||||||
|
{
|
||||||
|
public MvcOptions Value { get; } = new MvcOptions();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -393,6 +393,25 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
actionContext.HttpContext.Response.Headers[HeaderNames.ContentType]);
|
actionContext.HttpContext.Response.Headers[HeaderNames.ContentType]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ExecuteAsync_ThrowsWithNoFormatters()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var expected = $"'{typeof(MvcOptions).FullName}.{nameof(MvcOptions.OutputFormatters)}' must not be " +
|
||||||
|
$"empty. At least one '{typeof(IOutputFormatter).FullName}' is required to format a response.";
|
||||||
|
var executor = CreateExecutor();
|
||||||
|
var actionContext = new ActionContext
|
||||||
|
{
|
||||||
|
HttpContext = GetHttpContext(),
|
||||||
|
};
|
||||||
|
var result = new ObjectResult("some value");
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
var exception = await Assert.ThrowsAsync<InvalidOperationException>(
|
||||||
|
() => executor.ExecuteAsync(actionContext, result));
|
||||||
|
Assert.Equal(expected, exception.Message);
|
||||||
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData(new[] { "application/*" }, "application/*")]
|
[InlineData(new[] { "application/*" }, "application/*")]
|
||||||
[InlineData(new[] { "application/xml", "application/*", "application/json" }, "application/*")]
|
[InlineData(new[] { "application/xml", "application/*", "application/json" }, "application/*")]
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using Microsoft.AspNetCore.Mvc.Core;
|
|
||||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Binders;
|
using Microsoft.AspNetCore.Mvc.ModelBinding.Binders;
|
||||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
|
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
|
||||||
|
|
@ -25,7 +24,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
ModelMetadataIdentity.ForType(typeof(string)),
|
ModelMetadataIdentity.ForType(typeof(string)),
|
||||||
new ModelAttributes(attributes));
|
new ModelAttributes(attributes));
|
||||||
|
|
||||||
var provider = new DefaultBindingMetadataProvider(CreateMessageProvider());
|
var provider = new DefaultBindingMetadataProvider();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
provider.CreateBindingMetadata(context);
|
provider.CreateBindingMetadata(context);
|
||||||
|
|
@ -49,7 +48,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
ModelMetadataIdentity.ForType(typeof(string)),
|
ModelMetadataIdentity.ForType(typeof(string)),
|
||||||
new ModelAttributes(attributes));
|
new ModelAttributes(attributes));
|
||||||
|
|
||||||
var provider = new DefaultBindingMetadataProvider(CreateMessageProvider());
|
var provider = new DefaultBindingMetadataProvider();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
provider.CreateBindingMetadata(context);
|
provider.CreateBindingMetadata(context);
|
||||||
|
|
@ -72,7 +71,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
ModelMetadataIdentity.ForType(typeof(string)),
|
ModelMetadataIdentity.ForType(typeof(string)),
|
||||||
new ModelAttributes(attributes));
|
new ModelAttributes(attributes));
|
||||||
|
|
||||||
var provider = new DefaultBindingMetadataProvider(CreateMessageProvider());
|
var provider = new DefaultBindingMetadataProvider();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
provider.CreateBindingMetadata(context);
|
provider.CreateBindingMetadata(context);
|
||||||
|
|
@ -96,7 +95,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
ModelMetadataIdentity.ForType(typeof(string)),
|
ModelMetadataIdentity.ForType(typeof(string)),
|
||||||
new ModelAttributes(attributes));
|
new ModelAttributes(attributes));
|
||||||
|
|
||||||
var provider = new DefaultBindingMetadataProvider(CreateMessageProvider());
|
var provider = new DefaultBindingMetadataProvider();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
provider.CreateBindingMetadata(context);
|
provider.CreateBindingMetadata(context);
|
||||||
|
|
@ -119,7 +118,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
ModelMetadataIdentity.ForType(typeof(string)),
|
ModelMetadataIdentity.ForType(typeof(string)),
|
||||||
new ModelAttributes(attributes));
|
new ModelAttributes(attributes));
|
||||||
|
|
||||||
var provider = new DefaultBindingMetadataProvider(CreateMessageProvider());
|
var provider = new DefaultBindingMetadataProvider();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
provider.CreateBindingMetadata(context);
|
provider.CreateBindingMetadata(context);
|
||||||
|
|
@ -143,7 +142,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
ModelMetadataIdentity.ForType(typeof(string)),
|
ModelMetadataIdentity.ForType(typeof(string)),
|
||||||
new ModelAttributes(attributes));
|
new ModelAttributes(attributes));
|
||||||
|
|
||||||
var provider = new DefaultBindingMetadataProvider(CreateMessageProvider());
|
var provider = new DefaultBindingMetadataProvider();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
provider.CreateBindingMetadata(context);
|
provider.CreateBindingMetadata(context);
|
||||||
|
|
@ -165,7 +164,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
ModelMetadataIdentity.ForProperty(typeof(int), "Length", typeof(string)),
|
ModelMetadataIdentity.ForProperty(typeof(int), "Length", typeof(string)),
|
||||||
new ModelAttributes(propertyAttributes, typeAttributes: new object[0]));
|
new ModelAttributes(propertyAttributes, typeAttributes: new object[0]));
|
||||||
|
|
||||||
var provider = new DefaultBindingMetadataProvider(CreateMessageProvider());
|
var provider = new DefaultBindingMetadataProvider();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
provider.CreateBindingMetadata(context);
|
provider.CreateBindingMetadata(context);
|
||||||
|
|
@ -188,7 +187,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
ModelMetadataIdentity.ForProperty(typeof(int), "Length", typeof(string)),
|
ModelMetadataIdentity.ForProperty(typeof(int), "Length", typeof(string)),
|
||||||
new ModelAttributes(propertyAttributes, typeAttributes: new object[0]));
|
new ModelAttributes(propertyAttributes, typeAttributes: new object[0]));
|
||||||
|
|
||||||
var provider = new DefaultBindingMetadataProvider(CreateMessageProvider());
|
var provider = new DefaultBindingMetadataProvider();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
provider.CreateBindingMetadata(context);
|
provider.CreateBindingMetadata(context);
|
||||||
|
|
@ -211,7 +210,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
ModelMetadataIdentity.ForProperty(typeof(int), "Length", typeof(string)),
|
ModelMetadataIdentity.ForProperty(typeof(int), "Length", typeof(string)),
|
||||||
new ModelAttributes(propertyAttributes, typeAttributes: new object[0]));
|
new ModelAttributes(propertyAttributes, typeAttributes: new object[0]));
|
||||||
|
|
||||||
var provider = new DefaultBindingMetadataProvider(CreateMessageProvider());
|
var provider = new DefaultBindingMetadataProvider();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
provider.CreateBindingMetadata(context);
|
provider.CreateBindingMetadata(context);
|
||||||
|
|
@ -234,7 +233,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
ModelMetadataIdentity.ForProperty(typeof(int), "Length", typeof(string)),
|
ModelMetadataIdentity.ForProperty(typeof(int), "Length", typeof(string)),
|
||||||
new ModelAttributes(propertyAttributes, typeAttributes: new object[0]));
|
new ModelAttributes(propertyAttributes, typeAttributes: new object[0]));
|
||||||
|
|
||||||
var provider = new DefaultBindingMetadataProvider(CreateMessageProvider());
|
var provider = new DefaultBindingMetadataProvider();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
provider.CreateBindingMetadata(context);
|
provider.CreateBindingMetadata(context);
|
||||||
|
|
@ -257,7 +256,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
ModelMetadataIdentity.ForProperty(typeof(int), "Length", typeof(string)),
|
ModelMetadataIdentity.ForProperty(typeof(int), "Length", typeof(string)),
|
||||||
new ModelAttributes(propertyAttributes, typeAttributes: new object[0]));
|
new ModelAttributes(propertyAttributes, typeAttributes: new object[0]));
|
||||||
|
|
||||||
var provider = new DefaultBindingMetadataProvider(CreateMessageProvider());
|
var provider = new DefaultBindingMetadataProvider();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
provider.CreateBindingMetadata(context);
|
provider.CreateBindingMetadata(context);
|
||||||
|
|
@ -283,7 +282,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
ModelMetadataIdentity.ForProperty(typeof(int), "Length", typeof(string)),
|
ModelMetadataIdentity.ForProperty(typeof(int), "Length", typeof(string)),
|
||||||
new ModelAttributes(propertyAttributes, typeAttributes: new object[0]));
|
new ModelAttributes(propertyAttributes, typeAttributes: new object[0]));
|
||||||
|
|
||||||
var provider = new DefaultBindingMetadataProvider(CreateMessageProvider());
|
var provider = new DefaultBindingMetadataProvider();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
provider.CreateBindingMetadata(context);
|
provider.CreateBindingMetadata(context);
|
||||||
|
|
@ -301,7 +300,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
ModelMetadataIdentity.ForProperty(typeof(string), "Property", typeof(BindRequiredOnClass)),
|
ModelMetadataIdentity.ForProperty(typeof(string), "Property", typeof(BindRequiredOnClass)),
|
||||||
new ModelAttributes(propertyAttributes: new object[0], typeAttributes: new object[0]));
|
new ModelAttributes(propertyAttributes: new object[0], typeAttributes: new object[0]));
|
||||||
|
|
||||||
var provider = new DefaultBindingMetadataProvider(CreateMessageProvider());
|
var provider = new DefaultBindingMetadataProvider();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
provider.CreateBindingMetadata(context);
|
provider.CreateBindingMetadata(context);
|
||||||
|
|
@ -319,7 +318,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
ModelMetadataIdentity.ForProperty(typeof(string), "Property", typeof(BindNeverOnClass)),
|
ModelMetadataIdentity.ForProperty(typeof(string), "Property", typeof(BindNeverOnClass)),
|
||||||
new ModelAttributes(propertyAttributes: new object[0], typeAttributes: new object[0]));
|
new ModelAttributes(propertyAttributes: new object[0], typeAttributes: new object[0]));
|
||||||
|
|
||||||
var provider = new DefaultBindingMetadataProvider(CreateMessageProvider());
|
var provider = new DefaultBindingMetadataProvider();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
provider.CreateBindingMetadata(context);
|
provider.CreateBindingMetadata(context);
|
||||||
|
|
@ -337,7 +336,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
ModelMetadataIdentity.ForProperty(typeof(string), "Property", typeof(InheritedBindNeverOnClass)),
|
ModelMetadataIdentity.ForProperty(typeof(string), "Property", typeof(InheritedBindNeverOnClass)),
|
||||||
new ModelAttributes(propertyAttributes: new object[0], typeAttributes: new object[0]));
|
new ModelAttributes(propertyAttributes: new object[0], typeAttributes: new object[0]));
|
||||||
|
|
||||||
var provider = new DefaultBindingMetadataProvider(CreateMessageProvider());
|
var provider = new DefaultBindingMetadataProvider();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
provider.CreateBindingMetadata(context);
|
provider.CreateBindingMetadata(context);
|
||||||
|
|
@ -360,7 +359,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
ModelMetadataIdentity.ForProperty(typeof(string), "Property", typeof(BindNeverOnClass)),
|
ModelMetadataIdentity.ForProperty(typeof(string), "Property", typeof(BindNeverOnClass)),
|
||||||
new ModelAttributes(propertyAttributes, typeAttributes: new object[0]));
|
new ModelAttributes(propertyAttributes, typeAttributes: new object[0]));
|
||||||
|
|
||||||
var provider = new DefaultBindingMetadataProvider(CreateMessageProvider());
|
var provider = new DefaultBindingMetadataProvider();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
provider.CreateBindingMetadata(context);
|
provider.CreateBindingMetadata(context);
|
||||||
|
|
@ -383,7 +382,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
ModelMetadataIdentity.ForProperty(typeof(string), "Property", typeof(BindNeverOnClass)),
|
ModelMetadataIdentity.ForProperty(typeof(string), "Property", typeof(BindNeverOnClass)),
|
||||||
new ModelAttributes(propertyAttributes, typeAttributes: new object[0]));
|
new ModelAttributes(propertyAttributes, typeAttributes: new object[0]));
|
||||||
|
|
||||||
var provider = new DefaultBindingMetadataProvider(CreateMessageProvider());
|
var provider = new DefaultBindingMetadataProvider();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
provider.CreateBindingMetadata(context);
|
provider.CreateBindingMetadata(context);
|
||||||
|
|
@ -406,7 +405,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
ModelMetadataIdentity.ForProperty(typeof(string), "Property", typeof(InheritedBindNeverOnClass)),
|
ModelMetadataIdentity.ForProperty(typeof(string), "Property", typeof(InheritedBindNeverOnClass)),
|
||||||
new ModelAttributes(propertyAttributes, typeAttributes: new object[0]));
|
new ModelAttributes(propertyAttributes, typeAttributes: new object[0]));
|
||||||
|
|
||||||
var provider = new DefaultBindingMetadataProvider(CreateMessageProvider());
|
var provider = new DefaultBindingMetadataProvider();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
provider.CreateBindingMetadata(context);
|
provider.CreateBindingMetadata(context);
|
||||||
|
|
@ -429,7 +428,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
ModelMetadataIdentity.ForProperty(typeof(string), "Property", typeof(BindRequiredOnClass)),
|
ModelMetadataIdentity.ForProperty(typeof(string), "Property", typeof(BindRequiredOnClass)),
|
||||||
new ModelAttributes(propertyAttributes, typeAttributes: new object[0]));
|
new ModelAttributes(propertyAttributes, typeAttributes: new object[0]));
|
||||||
|
|
||||||
var provider = new DefaultBindingMetadataProvider(CreateMessageProvider());
|
var provider = new DefaultBindingMetadataProvider();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
provider.CreateBindingMetadata(context);
|
provider.CreateBindingMetadata(context);
|
||||||
|
|
@ -448,7 +447,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
ModelMetadataIdentity.ForProperty(typeof(string), "Property", typeof(BindRequiredOverridesInheritedBindNever)),
|
ModelMetadataIdentity.ForProperty(typeof(string), "Property", typeof(BindRequiredOverridesInheritedBindNever)),
|
||||||
new ModelAttributes(propertyAttributes: new object[0], typeAttributes: new object[0]));
|
new ModelAttributes(propertyAttributes: new object[0], typeAttributes: new object[0]));
|
||||||
|
|
||||||
var provider = new DefaultBindingMetadataProvider(CreateMessageProvider());
|
var provider = new DefaultBindingMetadataProvider();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
provider.CreateBindingMetadata(context);
|
provider.CreateBindingMetadata(context);
|
||||||
|
|
@ -477,7 +476,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
context.BindingMetadata.IsBindingAllowed = initialValue;
|
context.BindingMetadata.IsBindingAllowed = initialValue;
|
||||||
context.BindingMetadata.IsBindingRequired = initialValue;
|
context.BindingMetadata.IsBindingRequired = initialValue;
|
||||||
|
|
||||||
var provider = new DefaultBindingMetadataProvider(CreateMessageProvider());
|
var provider = new DefaultBindingMetadataProvider();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
provider.CreateBindingMetadata(context);
|
provider.CreateBindingMetadata(context);
|
||||||
|
|
@ -508,7 +507,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
context.BindingMetadata.IsBindingAllowed = initialValue;
|
context.BindingMetadata.IsBindingAllowed = initialValue;
|
||||||
context.BindingMetadata.IsBindingRequired = initialValue;
|
context.BindingMetadata.IsBindingRequired = initialValue;
|
||||||
|
|
||||||
var provider = new DefaultBindingMetadataProvider(CreateMessageProvider());
|
var provider = new DefaultBindingMetadataProvider();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
provider.CreateBindingMetadata(context);
|
provider.CreateBindingMetadata(context);
|
||||||
|
|
@ -518,20 +517,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
Assert.Equal(initialValue, context.BindingMetadata.IsBindingRequired);
|
Assert.Equal(initialValue, context.BindingMetadata.IsBindingRequired);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ModelBindingMessageProvider CreateMessageProvider()
|
|
||||||
{
|
|
||||||
return new ModelBindingMessageProvider
|
|
||||||
{
|
|
||||||
MissingBindRequiredValueAccessor = Resources.FormatModelBinding_MissingBindRequiredMember,
|
|
||||||
MissingKeyOrValueAccessor = Resources.FormatKeyValuePair_BothKeyAndValueMustBePresent,
|
|
||||||
ValueMustNotBeNullAccessor = Resources.FormatModelBinding_NullValueNotValid,
|
|
||||||
AttemptedValueIsInvalidAccessor = Resources.FormatModelState_AttemptedValueIsInvalid,
|
|
||||||
UnknownValueIsInvalidAccessor = Resources.FormatModelState_UnknownValueIsInvalid,
|
|
||||||
ValueIsInvalidAccessor = Resources.FormatHtmlGeneration_ValueIsInvalid,
|
|
||||||
ValueMustBeANumberAccessor = Resources.FormatHtmlGeneration_ValueMustBeNumber,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
[BindNever]
|
[BindNever]
|
||||||
private class BindNeverOnClass
|
private class BindNeverOnClass
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Mvc.Formatters;
|
using Microsoft.AspNetCore.Mvc.Formatters;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
|
|
@ -24,7 +26,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[MemberData(nameof(NonBodyBindingSources))]
|
[MemberData(nameof(NonBodyBindingSources))]
|
||||||
public void Create_WhenBindingSourceIsNotFromBody_ReturnsNull(BindingSource source)
|
public void GetBinder_WhenBindingSourceIsNotFromBody_ReturnsNull(BindingSource source)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var provider = CreateProvider();
|
var provider = CreateProvider();
|
||||||
|
|
@ -39,12 +41,25 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||||
Assert.Null(result);
|
Assert.Null(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
public void GetBinder_WhenNoInputFormatters_Throws()
|
||||||
public void Create_WhenBindingSourceIsFromBody_ReturnsBinder()
|
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
|
var expected = $"'{typeof(MvcOptions).FullName}.{nameof(MvcOptions.InputFormatters)}' must not be empty. " +
|
||||||
|
$"At least one '{typeof(IInputFormatter).FullName}' is required to bind from the body.";
|
||||||
var provider = CreateProvider();
|
var provider = CreateProvider();
|
||||||
|
var context = new TestModelBinderProviderContext(typeof(Person));
|
||||||
|
context.BindingInfo.BindingSource = BindingSource.Body;
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
var exception = Assert.Throws<InvalidOperationException>(() => provider.GetBinder(context));
|
||||||
|
Assert.Equal(expected, exception.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetBinder_WhenBindingSourceIsFromBody_ReturnsBinder()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var provider = CreateProvider(new TestInputFormatter());
|
||||||
var context = new TestModelBinderProviderContext(typeof(Person));
|
var context = new TestModelBinderProviderContext(typeof(Person));
|
||||||
context.BindingInfo.BindingSource = BindingSource.Body;
|
context.BindingInfo.BindingSource = BindingSource.Body;
|
||||||
|
|
||||||
|
|
@ -55,9 +70,11 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||||
Assert.IsType<BodyModelBinder>(result);
|
Assert.IsType<BodyModelBinder>(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static BodyModelBinderProvider CreateProvider()
|
private static BodyModelBinderProvider CreateProvider(params IInputFormatter[] formatters)
|
||||||
{
|
{
|
||||||
return new BodyModelBinderProvider(new List<IInputFormatter>(), new TestHttpRequestStreamReaderFactory());
|
return new BodyModelBinderProvider(
|
||||||
|
new List<IInputFormatter>(formatters),
|
||||||
|
new TestHttpRequestStreamReaderFactory());
|
||||||
}
|
}
|
||||||
|
|
||||||
private class Person
|
private class Person
|
||||||
|
|
@ -66,5 +83,18 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||||
|
|
||||||
public int Age { get; set; }
|
public int Age { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class TestInputFormatter : IInputFormatter
|
||||||
|
{
|
||||||
|
public bool CanRead(InputFormatterContext context)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<InputFormatterResult> ReadAsync(InputFormatterContext context)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
||||||
|
|
@ -173,7 +172,9 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
||||||
|
|
||||||
private static DefaultModelMetadataProvider CreateProvider()
|
private static DefaultModelMetadataProvider CreateProvider()
|
||||||
{
|
{
|
||||||
return new DefaultModelMetadataProvider(new EmptyCompositeMetadataDetailsProvider());
|
return new DefaultModelMetadataProvider(
|
||||||
|
new EmptyCompositeMetadataDetailsProvider(),
|
||||||
|
new TestOptionsManager<MvcOptions>());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Model("OnType")]
|
[Model("OnType")]
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,8 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using Microsoft.AspNetCore.Mvc.Internal;
|
using Microsoft.AspNetCore.Mvc.Internal;
|
||||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Binders;
|
using Microsoft.AspNetCore.Mvc.ModelBinding.Binders;
|
||||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Internal;
|
|
||||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
|
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
|
||||||
using Moq;
|
using Moq;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
@ -14,24 +12,41 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
{
|
{
|
||||||
public class ModelBinderFactoryTest
|
public class ModelBinderFactoryTest
|
||||||
{
|
{
|
||||||
// No providers => can't create a binder
|
[Fact]
|
||||||
|
public void CreateBinder_Throws_WhenNoProviders()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var expected = $"'{typeof(MvcOptions).FullName}.{nameof(MvcOptions.ModelBinderProviders)}' must not be " +
|
||||||
|
$"empty. At least one '{typeof(IModelBinderProvider).FullName}' is required to model bind.";
|
||||||
|
var metadataProvider = new TestModelMetadataProvider();
|
||||||
|
var options = new TestOptionsManager<MvcOptions>();
|
||||||
|
var factory = new ModelBinderFactory(metadataProvider, options);
|
||||||
|
var context = new ModelBinderFactoryContext()
|
||||||
|
{
|
||||||
|
Metadata = metadataProvider.GetMetadataForType(typeof(string)),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
var exception = Assert.Throws<InvalidOperationException>(() => factory.CreateBinder(context));
|
||||||
|
Assert.Equal(expected, exception.Message);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void CreateBinder_Throws_WhenBinderNotCreated()
|
public void CreateBinder_Throws_WhenBinderNotCreated()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var metadataProvider = new TestModelMetadataProvider();
|
var metadataProvider = new TestModelMetadataProvider();
|
||||||
var options = new TestOptionsManager<MvcOptions>();
|
var options = new TestOptionsManager<MvcOptions>();
|
||||||
var factory = new ModelBinderFactory(metadataProvider, options);
|
options.Value.ModelBinderProviders.Add(new TestModelBinderProvider(_ => null));
|
||||||
|
|
||||||
|
var factory = new ModelBinderFactory(metadataProvider, options);
|
||||||
var context = new ModelBinderFactoryContext()
|
var context = new ModelBinderFactoryContext()
|
||||||
{
|
{
|
||||||
Metadata = metadataProvider.GetMetadataForType(typeof(string)),
|
Metadata = metadataProvider.GetMetadataForType(typeof(string)),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Act
|
// Act & Assert
|
||||||
var exception = Assert.Throws<InvalidOperationException>(() => factory.CreateBinder(context));
|
var exception = Assert.Throws<InvalidOperationException>(() => factory.CreateBinder(context));
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.Equal(
|
Assert.Equal(
|
||||||
$"Could not create a model binder for model object of type '{typeof(string).FullName}'.",
|
$"Could not create a model binder for model object of type '{typeof(string).FullName}'.",
|
||||||
exception.Message);
|
exception.Message);
|
||||||
|
|
|
||||||
|
|
@ -1046,31 +1046,17 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
|
||||||
private readonly object[] _attributes;
|
private readonly object[] _attributes;
|
||||||
|
|
||||||
public AttributeInjectModelMetadataProvider(object[] attributes)
|
public AttributeInjectModelMetadataProvider(object[] attributes)
|
||||||
: base(new DefaultCompositeMetadataDetailsProvider(new IMetadataDetailsProvider[]
|
: base(
|
||||||
{
|
new DefaultCompositeMetadataDetailsProvider(new IMetadataDetailsProvider[]
|
||||||
new DefaultBindingMetadataProvider(CreateMessageProvider()),
|
{
|
||||||
new DataAnnotationsMetadataProvider(),
|
new DefaultBindingMetadataProvider(),
|
||||||
}))
|
new DataAnnotationsMetadataProvider(),
|
||||||
|
}),
|
||||||
|
new TestOptionsManager<MvcOptions>())
|
||||||
{
|
{
|
||||||
_attributes = attributes;
|
_attributes = attributes;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ModelBindingMessageProvider CreateMessageProvider()
|
|
||||||
{
|
|
||||||
return new ModelBindingMessageProvider
|
|
||||||
{
|
|
||||||
MissingBindRequiredValueAccessor =
|
|
||||||
name => $"A value for the '{ name }' property was not provided.",
|
|
||||||
MissingKeyOrValueAccessor = () => $"A value is required.",
|
|
||||||
ValueMustNotBeNullAccessor = value => $"The value '{ value }' is invalid.",
|
|
||||||
AttemptedValueIsInvalidAccessor =
|
|
||||||
(value, name) => $"The value '{ value }' is not valid for { name }.",
|
|
||||||
UnknownValueIsInvalidAccessor = name => $"The supplied value is invalid for { name }.",
|
|
||||||
ValueIsInvalidAccessor = value => $"The value '{ value }' is invalid.",
|
|
||||||
ValueMustBeANumberAccessor = name => $"The field { name } must be a number.",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override DefaultMetadataDetails CreateTypeDetails(ModelMetadataIdentity key)
|
protected override DefaultMetadataDetails CreateTypeDetails(ModelMetadataIdentity key)
|
||||||
{
|
{
|
||||||
var entry = base.CreateTypeDetails(key);
|
var entry = base.CreateTypeDetails(key);
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Diagnostics;
|
using Microsoft.AspNetCore.Diagnostics;
|
||||||
using Microsoft.AspNetCore.Mvc.Razor.Compilation;
|
using Microsoft.AspNetCore.Mvc.Razor.Compilation;
|
||||||
|
using Microsoft.Extensions.FileProviders;
|
||||||
using Moq;
|
using Moq;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
|
|
@ -445,6 +446,23 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
||||||
Assert.Same(result1.PageFactory, result2.PageFactory);
|
Assert.Same(result1.PageFactory, result2.PageFactory);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetOrAdd_ThrowsIfNullFileProvider()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var expected =
|
||||||
|
$"'{typeof(RazorViewEngineOptions).FullName}.{nameof(RazorViewEngineOptions.FileProviders)}' must " +
|
||||||
|
$"not be empty. At least one '{typeof(IFileProvider).FullName}' is required to locate a view for " +
|
||||||
|
"rendering.";
|
||||||
|
var fileProvider = new NullFileProvider();
|
||||||
|
var cache = new CompilerCache(fileProvider);
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
var exception = Assert.Throws<InvalidOperationException>(
|
||||||
|
() => cache.GetOrAdd(ViewPath, _ => { throw new InvalidTimeZoneException(); }));
|
||||||
|
Assert.Equal(expected, exception.Message);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void GetOrAdd_CachesCompilationExceptions()
|
public void GetOrAdd_CachesCompilationExceptions()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
||||||
public class DefaultRazorViewEngineFileProviderAccessorTest
|
public class DefaultRazorViewEngineFileProviderAccessorTest
|
||||||
{
|
{
|
||||||
[Fact]
|
[Fact]
|
||||||
public void FileProvider_ReturnsInstanceIfExactlyOneFileProviderIsSpecified()
|
public void FileProvider_ReturnsInstance_IfExactlyOneFileProviderIsRegistered()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var fileProvider = new TestFileProvider();
|
var fileProvider = new TestFileProvider();
|
||||||
|
|
@ -29,7 +29,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void FileProvider_ReturnsCompositeFileProviderIfNoInstancesAreRegistered()
|
public void FileProvider_ReturnsNullFileProvider_IfNoInstancesAreRegistered()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var options = new RazorViewEngineOptions();
|
var options = new RazorViewEngineOptions();
|
||||||
|
|
@ -41,11 +41,11 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
||||||
var actual = fileProviderAccessor.FileProvider;
|
var actual = fileProviderAccessor.FileProvider;
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.IsType<CompositeFileProvider>(actual);
|
Assert.IsType<NullFileProvider>(actual);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void FileProvider_ReturnsCompositeFileProviderIfMoreThanOneInstanceIsRegistered()
|
public void FileProvider_ReturnsCompositeFileProvider_IfMoreThanOneInstanceIsRegistered()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var options = new RazorViewEngineOptions();
|
var options = new RazorViewEngineOptions();
|
||||||
|
|
|
||||||
|
|
@ -18,21 +18,21 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
{
|
{
|
||||||
var detailsProviders = new IMetadataDetailsProvider[]
|
var detailsProviders = new IMetadataDetailsProvider[]
|
||||||
{
|
{
|
||||||
new DefaultBindingMetadataProvider(CreateMessageProvider()),
|
new DefaultBindingMetadataProvider(),
|
||||||
new DefaultValidationMetadataProvider(),
|
new DefaultValidationMetadataProvider(),
|
||||||
new DataAnnotationsMetadataProvider(),
|
new DataAnnotationsMetadataProvider(),
|
||||||
new DataMemberRequiredBindingMetadataProvider(),
|
new DataMemberRequiredBindingMetadataProvider(),
|
||||||
};
|
};
|
||||||
|
|
||||||
var compositeDetailsProvider = new DefaultCompositeMetadataDetailsProvider(detailsProviders);
|
var compositeDetailsProvider = new DefaultCompositeMetadataDetailsProvider(detailsProviders);
|
||||||
return new DefaultModelMetadataProvider(compositeDetailsProvider);
|
return new DefaultModelMetadataProvider(compositeDetailsProvider, new TestOptionsManager<MvcOptions>());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IModelMetadataProvider CreateDefaultProvider(IList<IMetadataDetailsProvider> providers)
|
public static IModelMetadataProvider CreateDefaultProvider(IList<IMetadataDetailsProvider> providers)
|
||||||
{
|
{
|
||||||
var detailsProviders = new List<IMetadataDetailsProvider>()
|
var detailsProviders = new List<IMetadataDetailsProvider>()
|
||||||
{
|
{
|
||||||
new DefaultBindingMetadataProvider(CreateMessageProvider()),
|
new DefaultBindingMetadataProvider(),
|
||||||
new DefaultValidationMetadataProvider(),
|
new DefaultValidationMetadataProvider(),
|
||||||
new DataAnnotationsMetadataProvider(),
|
new DataAnnotationsMetadataProvider(),
|
||||||
new DataMemberRequiredBindingMetadataProvider(),
|
new DataMemberRequiredBindingMetadataProvider(),
|
||||||
|
|
@ -41,7 +41,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
detailsProviders.AddRange(providers);
|
detailsProviders.AddRange(providers);
|
||||||
|
|
||||||
var compositeDetailsProvider = new DefaultCompositeMetadataDetailsProvider(detailsProviders);
|
var compositeDetailsProvider = new DefaultCompositeMetadataDetailsProvider(detailsProviders);
|
||||||
return new DefaultModelMetadataProvider(compositeDetailsProvider);
|
return new DefaultModelMetadataProvider(compositeDetailsProvider, new TestOptionsManager<MvcOptions>());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IModelMetadataProvider CreateProvider(IList<IMetadataDetailsProvider> providers)
|
public static IModelMetadataProvider CreateProvider(IList<IMetadataDetailsProvider> providers)
|
||||||
|
|
@ -53,7 +53,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
}
|
}
|
||||||
|
|
||||||
var compositeDetailsProvider = new DefaultCompositeMetadataDetailsProvider(detailsProviders);
|
var compositeDetailsProvider = new DefaultCompositeMetadataDetailsProvider(detailsProviders);
|
||||||
return new DefaultModelMetadataProvider(compositeDetailsProvider);
|
return new DefaultModelMetadataProvider(compositeDetailsProvider, new TestOptionsManager<MvcOptions>());
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly TestModelMetadataDetailsProvider _detailsProvider;
|
private readonly TestModelMetadataDetailsProvider _detailsProvider;
|
||||||
|
|
@ -64,13 +64,15 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
}
|
}
|
||||||
|
|
||||||
private TestModelMetadataProvider(TestModelMetadataDetailsProvider detailsProvider)
|
private TestModelMetadataProvider(TestModelMetadataDetailsProvider detailsProvider)
|
||||||
: base(new DefaultCompositeMetadataDetailsProvider(new IMetadataDetailsProvider[]
|
: base(
|
||||||
{
|
new DefaultCompositeMetadataDetailsProvider(new IMetadataDetailsProvider[]
|
||||||
new DefaultBindingMetadataProvider(CreateMessageProvider()),
|
{
|
||||||
new DefaultValidationMetadataProvider(),
|
new DefaultBindingMetadataProvider(),
|
||||||
new DataAnnotationsMetadataProvider(),
|
new DefaultValidationMetadataProvider(),
|
||||||
detailsProvider
|
new DataAnnotationsMetadataProvider(),
|
||||||
}))
|
detailsProvider
|
||||||
|
}),
|
||||||
|
new TestOptionsManager<MvcOptions>())
|
||||||
{
|
{
|
||||||
_detailsProvider = detailsProvider;
|
_detailsProvider = detailsProvider;
|
||||||
}
|
}
|
||||||
|
|
@ -106,20 +108,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
return ForProperty(typeof(TContainer), propertyName);
|
return ForProperty(typeof(TContainer), propertyName);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ModelBindingMessageProvider CreateMessageProvider()
|
|
||||||
{
|
|
||||||
return new ModelBindingMessageProvider
|
|
||||||
{
|
|
||||||
MissingBindRequiredValueAccessor = name => $"A value for the '{ name }' property was not provided.",
|
|
||||||
MissingKeyOrValueAccessor = () => $"A value is required.",
|
|
||||||
ValueMustNotBeNullAccessor = value => $"The value '{ value }' is invalid.",
|
|
||||||
AttemptedValueIsInvalidAccessor = (value, name) => $"The value '{ value }' is not valid for { name }.",
|
|
||||||
UnknownValueIsInvalidAccessor = name => $"The supplied value is invalid for { name }.",
|
|
||||||
ValueIsInvalidAccessor = value => $"The value '{ value }' is invalid.",
|
|
||||||
ValueMustBeANumberAccessor = name => $"The field { name } must be a number.",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private class TestModelMetadataDetailsProvider :
|
private class TestModelMetadataDetailsProvider :
|
||||||
IBindingMetadataProvider,
|
IBindingMetadataProvider,
|
||||||
IDisplayMetadataProvider,
|
IDisplayMetadataProvider,
|
||||||
|
|
|
||||||
|
|
@ -32,20 +32,20 @@ namespace Microsoft.AspNetCore.Mvc.ViewEngines
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void FindView_IsMainPage_ReturnsNotFoundResult_WhenNoViewEnginesAreRegistered()
|
public void FindView_IsMainPage_Throws_WhenNoViewEnginesAreRegistered()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
|
var expected = $"'{typeof(MvcViewOptions).FullName}.{nameof(MvcViewOptions.ViewEngines)}' must not be " +
|
||||||
|
$"empty. At least one '{typeof(IViewEngine).FullName}' is required to locate a view for rendering.";
|
||||||
var viewName = "test-view";
|
var viewName = "test-view";
|
||||||
var actionContext = GetActionContext();
|
var actionContext = GetActionContext();
|
||||||
var optionsAccessor = new TestOptionsManager<MvcViewOptions>();
|
var optionsAccessor = new TestOptionsManager<MvcViewOptions>();
|
||||||
var compositeViewEngine = new CompositeViewEngine(optionsAccessor);
|
var compositeViewEngine = new CompositeViewEngine(optionsAccessor);
|
||||||
|
|
||||||
// Act
|
// Act & Assert
|
||||||
var result = compositeViewEngine.FindView(actionContext, viewName, isMainPage: true);
|
var exception = Assert.Throws<InvalidOperationException>(
|
||||||
|
() => compositeViewEngine.FindView(actionContext, viewName, isMainPage: true));
|
||||||
// Assert
|
Assert.Equal(expected, exception.Message);
|
||||||
Assert.False(result.Success);
|
|
||||||
Assert.Empty(result.SearchedLocations);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -165,16 +165,16 @@ namespace Microsoft.AspNetCore.Mvc.ViewEngines
|
||||||
public void GetView_ReturnsNotFoundResult_WhenNoViewEnginesAreRegistered(bool isMainPage)
|
public void GetView_ReturnsNotFoundResult_WhenNoViewEnginesAreRegistered(bool isMainPage)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
|
var expected = $"'{typeof(MvcViewOptions).FullName}.{nameof(MvcViewOptions.ViewEngines)}' must not be " +
|
||||||
|
$"empty. At least one '{typeof(IViewEngine).FullName}' is required to locate a view for rendering.";
|
||||||
var viewName = "test-view.cshtml";
|
var viewName = "test-view.cshtml";
|
||||||
var optionsAccessor = new TestOptionsManager<MvcViewOptions>();
|
var optionsAccessor = new TestOptionsManager<MvcViewOptions>();
|
||||||
var compositeViewEngine = new CompositeViewEngine(optionsAccessor);
|
var compositeViewEngine = new CompositeViewEngine(optionsAccessor);
|
||||||
|
|
||||||
// Act
|
// Act & Assert
|
||||||
var result = compositeViewEngine.GetView("~/Index.html", viewName, isMainPage);
|
var exception = Assert.Throws<InvalidOperationException>(
|
||||||
|
() => compositeViewEngine.GetView("~/Index.html", viewName, isMainPage));
|
||||||
// Assert
|
Assert.Equal(expected, exception.Message);
|
||||||
Assert.False(result.Success);
|
|
||||||
Assert.Empty(result.SearchedLocations);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -305,16 +305,16 @@ namespace Microsoft.AspNetCore.Mvc.ViewEngines
|
||||||
public void FindView_ReturnsNotFoundResult_WhenNoViewEnginesAreRegistered()
|
public void FindView_ReturnsNotFoundResult_WhenNoViewEnginesAreRegistered()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
|
var expected = $"'{typeof(MvcViewOptions).FullName}.{nameof(MvcViewOptions.ViewEngines)}' must not be " +
|
||||||
|
$"empty. At least one '{typeof(IViewEngine).FullName}' is required to locate a view for rendering.";
|
||||||
var viewName = "my-partial-view";
|
var viewName = "my-partial-view";
|
||||||
var optionsAccessor = new TestOptionsManager<MvcViewOptions>();
|
var optionsAccessor = new TestOptionsManager<MvcViewOptions>();
|
||||||
var compositeViewEngine = new CompositeViewEngine(optionsAccessor);
|
var compositeViewEngine = new CompositeViewEngine(optionsAccessor);
|
||||||
|
|
||||||
// Act
|
// Act & AssertS
|
||||||
var result = compositeViewEngine.FindView(GetActionContext(), viewName, isMainPage: false);
|
var exception = Assert.Throws<InvalidOperationException>(
|
||||||
|
() => compositeViewEngine.FindView(GetActionContext(), viewName, isMainPage: false));
|
||||||
// Assert
|
Assert.Equal(expected, exception.Message);
|
||||||
Assert.False(result.Success);
|
|
||||||
Assert.Empty(result.SearchedLocations);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue