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>
|
||||
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)
|
||||
{
|
||||
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
|
||||
var propertyFilterProviders = context.Attributes.OfType<IPropertyFilterProvider>().ToArray();
|
||||
if (propertyFilterProviders.Length == 0)
|
||||
|
|
|
|||
|
|
@ -4,12 +4,10 @@
|
|||
using System;
|
||||
using System.Threading;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc.Core;
|
||||
using Microsoft.AspNetCore.Mvc.Formatters;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Binders;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Internal
|
||||
|
|
@ -33,16 +31,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
|
||||
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
|
||||
options.ModelBinderProviders.Add(new BinderTypeModelBinderProvider());
|
||||
options.ModelBinderProviders.Add(new ServicesModelBinderProvider());
|
||||
|
|
@ -79,7 +67,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
// by altering the collection of providers.
|
||||
options.ModelMetadataDetailsProviders.Add(new ExcludeBindingMetadataProvider(typeof(Type)));
|
||||
|
||||
options.ModelMetadataDetailsProviders.Add(new DefaultBindingMetadataProvider(messageProvider));
|
||||
options.ModelMetadataDetailsProviders.Add(new DefaultBindingMetadataProvider());
|
||||
options.ModelMetadataDetailsProviders.Add(new DefaultValidationMetadataProvider());
|
||||
|
||||
// Set up validators
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
|
@ -117,6 +118,16 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
if (formatters == null || formatters.Count == 0)
|
||||
{
|
||||
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;
|
||||
|
|
@ -136,7 +147,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
{
|
||||
// No formatter supports this.
|
||||
Logger.NoFormatter(formatterContext);
|
||||
|
||||
|
||||
context.HttpContext.Response.StatusCode = StatusCodes.Status406NotAcceptable;
|
||||
return TaskCache.CompletedTask;
|
||||
}
|
||||
|
|
@ -189,7 +200,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
if (acceptableMediaTypes.Count == 0)
|
||||
{
|
||||
// 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();
|
||||
|
||||
selectFormatterWithoutRegardingAcceptHeader = true;
|
||||
|
|
@ -399,7 +410,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Selects the <see cref="IOutputFormatter"/> to write the response based on the content type values
|
||||
/// present in <paramref name="sortedAcceptableContentTypes"/> and <paramref name="possibleOutputContentTypes"/>.
|
||||
|
|
@ -423,19 +434,19 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
IList<MediaTypeSegmentWithQuality> sortedAcceptableContentTypes,
|
||||
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);
|
||||
for (var j = 0; j < possibleOutputContentTypes.Count; j++)
|
||||
for (var j = 0; j < possibleOutputContentTypes.Count; 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];
|
||||
formatterContext.ContentType = new StringSegment(possibleOutputContentTypes[j]);
|
||||
if (formatter.CanWriteResult(formatterContext))
|
||||
if (formatter.CanWriteResult(formatterContext))
|
||||
{
|
||||
return formatter;
|
||||
}
|
||||
|
|
@ -443,7 +454,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Mvc.Core;
|
||||
using Microsoft.AspNetCore.Mvc.Formatters;
|
||||
using Microsoft.AspNetCore.Mvc.Internal;
|
||||
|
||||
|
|
@ -48,6 +49,14 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
|||
if (context.BindingInfo.BindingSource != null &&
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,52 +1,25 @@
|
|||
// 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.
|
||||
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Mvc.Core;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Mvc.Internal;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||
{
|
||||
public class EmptyModelMetadataProvider : DefaultModelMetadataProvider
|
||||
{
|
||||
public EmptyModelMetadataProvider()
|
||||
: base(new DefaultCompositeMetadataDetailsProvider(new IMetadataDetailsProvider[]
|
||||
{
|
||||
new MessageOnlyBindingProvider()
|
||||
}))
|
||||
: base(
|
||||
new DefaultCompositeMetadataDetailsProvider(new List<IMetadataDetailsProvider>()),
|
||||
new OptionsAccessor())
|
||||
{
|
||||
}
|
||||
|
||||
private class MessageOnlyBindingProvider : IBindingMetadataProvider
|
||||
private class OptionsAccessor : IOptions<MvcOptions>
|
||||
{
|
||||
private readonly ModelBindingMessageProvider _messageProvider = CreateMessageProvider();
|
||||
|
||||
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,
|
||||
};
|
||||
}
|
||||
public MvcOptions Value { get; } = new MvcOptions();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -17,6 +17,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
|||
private readonly ICompositeMetadataDetailsProvider _detailsProvider;
|
||||
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 ModelMetadata _elementMetadata;
|
||||
private bool? _isBindingRequired;
|
||||
|
|
@ -36,6 +40,22 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
|||
IModelMetadataProvider provider,
|
||||
ICompositeMetadataDetailsProvider detailsProvider,
|
||||
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)
|
||||
{
|
||||
if (provider == null)
|
||||
|
|
@ -53,9 +73,15 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
|||
throw new ArgumentNullException(nameof(details));
|
||||
}
|
||||
|
||||
if (modelBindingMessageProvider == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(modelBindingMessageProvider));
|
||||
}
|
||||
|
||||
_provider = provider;
|
||||
_detailsProvider = detailsProvider;
|
||||
_details = details;
|
||||
_modelBindingMessageProvider = modelBindingMessageProvider;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -82,6 +108,11 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
|||
if (_details.BindingMetadata == null)
|
||||
{
|
||||
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);
|
||||
_details.BindingMetadata = context.BindingMetadata;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ using System.Collections.Concurrent;
|
|||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using Microsoft.Extensions.Internal;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
||||
{
|
||||
|
|
@ -23,11 +24,35 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
|||
/// </summary>
|
||||
/// <param name="detailsProvider">The <see cref="ICompositeMetadataDetailsProvider"/>.</param>
|
||||
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;
|
||||
ModelBindingMessageProvider = modelBindingMessageProvider;
|
||||
|
||||
_cacheEntryFactory = CreateCacheEntry;
|
||||
|
||||
_metadataCacheEntryForObjectType = GetMetadataCacheEntryForObjectType();
|
||||
}
|
||||
|
||||
|
|
@ -36,6 +61,12 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
|||
/// </summary>
|
||||
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 />
|
||||
public virtual IEnumerable<ModelMetadata> GetMetadataForProperties(Type modelType)
|
||||
{
|
||||
|
|
@ -78,6 +109,16 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.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)
|
||||
{
|
||||
ModelMetadataCacheEntry cacheEntry;
|
||||
|
|
@ -123,7 +164,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
|||
/// </remarks>
|
||||
protected virtual ModelMetadata CreateModelMetadata(DefaultMetadataDetails entry)
|
||||
{
|
||||
return new DefaultModelMetadata(this, DetailsProvider, entry);
|
||||
return new DefaultModelMetadata(this, DetailsProvider, entry, ModelBindingMessageProvider);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Mvc.Core;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
||||
{
|
||||
|
|
@ -23,6 +24,13 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
|||
/// </summary>
|
||||
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>
|
||||
|
|
|
|||
|
|
@ -46,12 +46,20 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
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;
|
||||
if (TryGetCachedBinder(context.Metadata, context.CacheToken, out binder))
|
||||
{
|
||||
return binder;
|
||||
}
|
||||
|
||||
|
||||
// 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
|
||||
// 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);
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
var value = _resourceManager.GetString(name);
|
||||
|
|
|
|||
|
|
@ -1,17 +1,17 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
|
||||
Example:
|
||||
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</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>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
|
||||
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
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
|
||||
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
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
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
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
|
|
@ -343,4 +343,13 @@
|
|||
<data name="CouldNotCreateIModelBinder" xml:space="preserve">
|
||||
<value>Could not create a model binder for model object of type '{0}'.</value>
|
||||
</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>
|
||||
|
|
@ -116,6 +116,15 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
|||
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);
|
||||
if (!fileInfo.Exists)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -18,7 +18,11 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
|||
public DefaultRazorViewEngineFileProviderAccessor(IOptions<RazorViewEngineOptions> optionsAccessor)
|
||||
{
|
||||
var fileProviders = optionsAccessor.Value.FileProviders;
|
||||
if (fileProviders.Count == 1)
|
||||
if (fileProviders.Count == 0)
|
||||
{
|
||||
FileProvider = new NullFileProvider();
|
||||
}
|
||||
else if (fileProviders.Count == 1)
|
||||
{
|
||||
FileProvider = fileProviders[0];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -494,6 +494,22 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
|||
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)
|
||||
{
|
||||
var value = _resourceManager.GetString(name);
|
||||
|
|
|
|||
|
|
@ -1,17 +1,17 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
|
||||
Example:
|
||||
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</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>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
|
||||
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
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
|
||||
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
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
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
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
|
|
@ -209,4 +209,7 @@
|
|||
<data name="RazorPage_NestingAttributeWritingScopesNotSupported" xml:space="preserve">
|
||||
<value>Nesting of TagHelper attribute writing scopes is not supported.</value>
|
||||
</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>
|
||||
|
|
@ -890,6 +890,22 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
|||
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)
|
||||
{
|
||||
var value = _resourceManager.GetString(name);
|
||||
|
|
|
|||
|
|
@ -1,17 +1,17 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
|
||||
Example:
|
||||
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</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>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
|
||||
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
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
|
||||
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
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
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
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
|
|
@ -283,4 +283,7 @@
|
|||
<data name="CreateModelExpression_NullModelMetadata" xml:space="preserve">
|
||||
<value>The {0} was unable to provide metadata for expression '{1}'.</value>
|
||||
</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>
|
||||
|
|
@ -37,6 +37,14 @@ namespace Microsoft.AspNetCore.Mvc.ViewEngines
|
|||
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.
|
||||
IEnumerable<string> searchedLocations = null;
|
||||
List<string> searchedList = null;
|
||||
|
|
@ -77,6 +85,14 @@ namespace Microsoft.AspNetCore.Mvc.ViewEngines
|
|||
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.
|
||||
IEnumerable<string> searchedLocations = 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.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using Microsoft.AspNetCore.Mvc.Internal;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Xunit;
|
||||
|
||||
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 dictionary = new ModelStateDictionary();
|
||||
|
||||
var messageProvider = new ModelBindingMessageProvider
|
||||
{
|
||||
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 bindingMetadataProvider = new DefaultBindingMetadataProvider();
|
||||
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));
|
||||
|
||||
// Act
|
||||
|
|
@ -939,20 +932,13 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
var dictionary = new ModelStateDictionary();
|
||||
dictionary.SetModelValue("key", new string[] { "some value" }, "some value");
|
||||
|
||||
var messageProvider = new ModelBindingMessageProvider
|
||||
{
|
||||
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 bindingMetadataProvider = new DefaultBindingMetadataProvider();
|
||||
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));
|
||||
|
||||
// Act
|
||||
|
|
@ -1303,5 +1289,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
// Assert
|
||||
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]);
|
||||
}
|
||||
|
||||
[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]
|
||||
[InlineData(new[] { "application/*" }, "application/*")]
|
||||
[InlineData(new[] { "application/xml", "application/*", "application/json" }, "application/*")]
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
// 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.
|
||||
|
||||
using Microsoft.AspNetCore.Mvc.Core;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Binders;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
|
||||
|
|
@ -25,7 +24,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
ModelMetadataIdentity.ForType(typeof(string)),
|
||||
new ModelAttributes(attributes));
|
||||
|
||||
var provider = new DefaultBindingMetadataProvider(CreateMessageProvider());
|
||||
var provider = new DefaultBindingMetadataProvider();
|
||||
|
||||
// Act
|
||||
provider.CreateBindingMetadata(context);
|
||||
|
|
@ -49,7 +48,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
ModelMetadataIdentity.ForType(typeof(string)),
|
||||
new ModelAttributes(attributes));
|
||||
|
||||
var provider = new DefaultBindingMetadataProvider(CreateMessageProvider());
|
||||
var provider = new DefaultBindingMetadataProvider();
|
||||
|
||||
// Act
|
||||
provider.CreateBindingMetadata(context);
|
||||
|
|
@ -72,7 +71,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
ModelMetadataIdentity.ForType(typeof(string)),
|
||||
new ModelAttributes(attributes));
|
||||
|
||||
var provider = new DefaultBindingMetadataProvider(CreateMessageProvider());
|
||||
var provider = new DefaultBindingMetadataProvider();
|
||||
|
||||
// Act
|
||||
provider.CreateBindingMetadata(context);
|
||||
|
|
@ -96,7 +95,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
ModelMetadataIdentity.ForType(typeof(string)),
|
||||
new ModelAttributes(attributes));
|
||||
|
||||
var provider = new DefaultBindingMetadataProvider(CreateMessageProvider());
|
||||
var provider = new DefaultBindingMetadataProvider();
|
||||
|
||||
// Act
|
||||
provider.CreateBindingMetadata(context);
|
||||
|
|
@ -119,7 +118,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
ModelMetadataIdentity.ForType(typeof(string)),
|
||||
new ModelAttributes(attributes));
|
||||
|
||||
var provider = new DefaultBindingMetadataProvider(CreateMessageProvider());
|
||||
var provider = new DefaultBindingMetadataProvider();
|
||||
|
||||
// Act
|
||||
provider.CreateBindingMetadata(context);
|
||||
|
|
@ -143,7 +142,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
ModelMetadataIdentity.ForType(typeof(string)),
|
||||
new ModelAttributes(attributes));
|
||||
|
||||
var provider = new DefaultBindingMetadataProvider(CreateMessageProvider());
|
||||
var provider = new DefaultBindingMetadataProvider();
|
||||
|
||||
// Act
|
||||
provider.CreateBindingMetadata(context);
|
||||
|
|
@ -165,7 +164,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
ModelMetadataIdentity.ForProperty(typeof(int), "Length", typeof(string)),
|
||||
new ModelAttributes(propertyAttributes, typeAttributes: new object[0]));
|
||||
|
||||
var provider = new DefaultBindingMetadataProvider(CreateMessageProvider());
|
||||
var provider = new DefaultBindingMetadataProvider();
|
||||
|
||||
// Act
|
||||
provider.CreateBindingMetadata(context);
|
||||
|
|
@ -188,7 +187,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
ModelMetadataIdentity.ForProperty(typeof(int), "Length", typeof(string)),
|
||||
new ModelAttributes(propertyAttributes, typeAttributes: new object[0]));
|
||||
|
||||
var provider = new DefaultBindingMetadataProvider(CreateMessageProvider());
|
||||
var provider = new DefaultBindingMetadataProvider();
|
||||
|
||||
// Act
|
||||
provider.CreateBindingMetadata(context);
|
||||
|
|
@ -211,7 +210,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
ModelMetadataIdentity.ForProperty(typeof(int), "Length", typeof(string)),
|
||||
new ModelAttributes(propertyAttributes, typeAttributes: new object[0]));
|
||||
|
||||
var provider = new DefaultBindingMetadataProvider(CreateMessageProvider());
|
||||
var provider = new DefaultBindingMetadataProvider();
|
||||
|
||||
// Act
|
||||
provider.CreateBindingMetadata(context);
|
||||
|
|
@ -234,7 +233,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
ModelMetadataIdentity.ForProperty(typeof(int), "Length", typeof(string)),
|
||||
new ModelAttributes(propertyAttributes, typeAttributes: new object[0]));
|
||||
|
||||
var provider = new DefaultBindingMetadataProvider(CreateMessageProvider());
|
||||
var provider = new DefaultBindingMetadataProvider();
|
||||
|
||||
// Act
|
||||
provider.CreateBindingMetadata(context);
|
||||
|
|
@ -257,7 +256,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
ModelMetadataIdentity.ForProperty(typeof(int), "Length", typeof(string)),
|
||||
new ModelAttributes(propertyAttributes, typeAttributes: new object[0]));
|
||||
|
||||
var provider = new DefaultBindingMetadataProvider(CreateMessageProvider());
|
||||
var provider = new DefaultBindingMetadataProvider();
|
||||
|
||||
// Act
|
||||
provider.CreateBindingMetadata(context);
|
||||
|
|
@ -283,7 +282,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
ModelMetadataIdentity.ForProperty(typeof(int), "Length", typeof(string)),
|
||||
new ModelAttributes(propertyAttributes, typeAttributes: new object[0]));
|
||||
|
||||
var provider = new DefaultBindingMetadataProvider(CreateMessageProvider());
|
||||
var provider = new DefaultBindingMetadataProvider();
|
||||
|
||||
// Act
|
||||
provider.CreateBindingMetadata(context);
|
||||
|
|
@ -301,7 +300,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
ModelMetadataIdentity.ForProperty(typeof(string), "Property", typeof(BindRequiredOnClass)),
|
||||
new ModelAttributes(propertyAttributes: new object[0], typeAttributes: new object[0]));
|
||||
|
||||
var provider = new DefaultBindingMetadataProvider(CreateMessageProvider());
|
||||
var provider = new DefaultBindingMetadataProvider();
|
||||
|
||||
// Act
|
||||
provider.CreateBindingMetadata(context);
|
||||
|
|
@ -319,7 +318,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
ModelMetadataIdentity.ForProperty(typeof(string), "Property", typeof(BindNeverOnClass)),
|
||||
new ModelAttributes(propertyAttributes: new object[0], typeAttributes: new object[0]));
|
||||
|
||||
var provider = new DefaultBindingMetadataProvider(CreateMessageProvider());
|
||||
var provider = new DefaultBindingMetadataProvider();
|
||||
|
||||
// Act
|
||||
provider.CreateBindingMetadata(context);
|
||||
|
|
@ -337,7 +336,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
ModelMetadataIdentity.ForProperty(typeof(string), "Property", typeof(InheritedBindNeverOnClass)),
|
||||
new ModelAttributes(propertyAttributes: new object[0], typeAttributes: new object[0]));
|
||||
|
||||
var provider = new DefaultBindingMetadataProvider(CreateMessageProvider());
|
||||
var provider = new DefaultBindingMetadataProvider();
|
||||
|
||||
// Act
|
||||
provider.CreateBindingMetadata(context);
|
||||
|
|
@ -360,7 +359,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
ModelMetadataIdentity.ForProperty(typeof(string), "Property", typeof(BindNeverOnClass)),
|
||||
new ModelAttributes(propertyAttributes, typeAttributes: new object[0]));
|
||||
|
||||
var provider = new DefaultBindingMetadataProvider(CreateMessageProvider());
|
||||
var provider = new DefaultBindingMetadataProvider();
|
||||
|
||||
// Act
|
||||
provider.CreateBindingMetadata(context);
|
||||
|
|
@ -383,7 +382,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
ModelMetadataIdentity.ForProperty(typeof(string), "Property", typeof(BindNeverOnClass)),
|
||||
new ModelAttributes(propertyAttributes, typeAttributes: new object[0]));
|
||||
|
||||
var provider = new DefaultBindingMetadataProvider(CreateMessageProvider());
|
||||
var provider = new DefaultBindingMetadataProvider();
|
||||
|
||||
// Act
|
||||
provider.CreateBindingMetadata(context);
|
||||
|
|
@ -406,7 +405,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
ModelMetadataIdentity.ForProperty(typeof(string), "Property", typeof(InheritedBindNeverOnClass)),
|
||||
new ModelAttributes(propertyAttributes, typeAttributes: new object[0]));
|
||||
|
||||
var provider = new DefaultBindingMetadataProvider(CreateMessageProvider());
|
||||
var provider = new DefaultBindingMetadataProvider();
|
||||
|
||||
// Act
|
||||
provider.CreateBindingMetadata(context);
|
||||
|
|
@ -429,7 +428,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
ModelMetadataIdentity.ForProperty(typeof(string), "Property", typeof(BindRequiredOnClass)),
|
||||
new ModelAttributes(propertyAttributes, typeAttributes: new object[0]));
|
||||
|
||||
var provider = new DefaultBindingMetadataProvider(CreateMessageProvider());
|
||||
var provider = new DefaultBindingMetadataProvider();
|
||||
|
||||
// Act
|
||||
provider.CreateBindingMetadata(context);
|
||||
|
|
@ -448,7 +447,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
ModelMetadataIdentity.ForProperty(typeof(string), "Property", typeof(BindRequiredOverridesInheritedBindNever)),
|
||||
new ModelAttributes(propertyAttributes: new object[0], typeAttributes: new object[0]));
|
||||
|
||||
var provider = new DefaultBindingMetadataProvider(CreateMessageProvider());
|
||||
var provider = new DefaultBindingMetadataProvider();
|
||||
|
||||
// Act
|
||||
provider.CreateBindingMetadata(context);
|
||||
|
|
@ -477,7 +476,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
context.BindingMetadata.IsBindingAllowed = initialValue;
|
||||
context.BindingMetadata.IsBindingRequired = initialValue;
|
||||
|
||||
var provider = new DefaultBindingMetadataProvider(CreateMessageProvider());
|
||||
var provider = new DefaultBindingMetadataProvider();
|
||||
|
||||
// Act
|
||||
provider.CreateBindingMetadata(context);
|
||||
|
|
@ -508,7 +507,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
context.BindingMetadata.IsBindingAllowed = initialValue;
|
||||
context.BindingMetadata.IsBindingRequired = initialValue;
|
||||
|
||||
var provider = new DefaultBindingMetadataProvider(CreateMessageProvider());
|
||||
var provider = new DefaultBindingMetadataProvider();
|
||||
|
||||
// Act
|
||||
provider.CreateBindingMetadata(context);
|
||||
|
|
@ -518,20 +517,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
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]
|
||||
private class BindNeverOnClass
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
// 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.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc.Formatters;
|
||||
using Xunit;
|
||||
|
||||
|
|
@ -24,7 +26,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
|||
|
||||
[Theory]
|
||||
[MemberData(nameof(NonBodyBindingSources))]
|
||||
public void Create_WhenBindingSourceIsNotFromBody_ReturnsNull(BindingSource source)
|
||||
public void GetBinder_WhenBindingSourceIsNotFromBody_ReturnsNull(BindingSource source)
|
||||
{
|
||||
// Arrange
|
||||
var provider = CreateProvider();
|
||||
|
|
@ -39,12 +41,25 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
|||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Create_WhenBindingSourceIsFromBody_ReturnsBinder()
|
||||
public void GetBinder_WhenNoInputFormatters_Throws()
|
||||
{
|
||||
// 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 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));
|
||||
context.BindingInfo.BindingSource = BindingSource.Body;
|
||||
|
||||
|
|
@ -55,9 +70,11 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
|||
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
|
||||
|
|
@ -66,5 +83,18 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
|||
|
||||
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.Linq;
|
||||
using System.Reflection;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
||||
|
|
@ -173,7 +172,9 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
|||
|
||||
private static DefaultModelMetadataProvider CreateProvider()
|
||||
{
|
||||
return new DefaultModelMetadataProvider(new EmptyCompositeMetadataDetailsProvider());
|
||||
return new DefaultModelMetadataProvider(
|
||||
new EmptyCompositeMetadataDetailsProvider(),
|
||||
new TestOptionsManager<MvcOptions>());
|
||||
}
|
||||
|
||||
[Model("OnType")]
|
||||
|
|
|
|||
|
|
@ -2,10 +2,8 @@
|
|||
// 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.Internal;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Binders;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Internal;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
|
@ -14,24 +12,41 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
{
|
||||
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]
|
||||
public void CreateBinder_Throws_WhenBinderNotCreated()
|
||||
{
|
||||
// Arrange
|
||||
var metadataProvider = new TestModelMetadataProvider();
|
||||
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()
|
||||
{
|
||||
Metadata = metadataProvider.GetMetadataForType(typeof(string)),
|
||||
};
|
||||
|
||||
// Act
|
||||
// Act & Assert
|
||||
var exception = Assert.Throws<InvalidOperationException>(() => factory.CreateBinder(context));
|
||||
|
||||
// Assert
|
||||
Assert.Equal(
|
||||
$"Could not create a model binder for model object of type '{typeof(string).FullName}'.",
|
||||
exception.Message);
|
||||
|
|
|
|||
|
|
@ -1046,31 +1046,17 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
|
|||
private readonly object[] _attributes;
|
||||
|
||||
public AttributeInjectModelMetadataProvider(object[] attributes)
|
||||
: base(new DefaultCompositeMetadataDetailsProvider(new IMetadataDetailsProvider[]
|
||||
{
|
||||
new DefaultBindingMetadataProvider(CreateMessageProvider()),
|
||||
new DataAnnotationsMetadataProvider(),
|
||||
}))
|
||||
: base(
|
||||
new DefaultCompositeMetadataDetailsProvider(new IMetadataDetailsProvider[]
|
||||
{
|
||||
new DefaultBindingMetadataProvider(),
|
||||
new DataAnnotationsMetadataProvider(),
|
||||
}),
|
||||
new TestOptionsManager<MvcOptions>())
|
||||
{
|
||||
_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)
|
||||
{
|
||||
var entry = base.CreateTypeDetails(key);
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ using System.Threading;
|
|||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Diagnostics;
|
||||
using Microsoft.AspNetCore.Mvc.Razor.Compilation;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
|
|
@ -445,6 +446,23 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
|||
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]
|
||||
public void GetOrAdd_CachesCompilationExceptions()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
|||
public class DefaultRazorViewEngineFileProviderAccessorTest
|
||||
{
|
||||
[Fact]
|
||||
public void FileProvider_ReturnsInstanceIfExactlyOneFileProviderIsSpecified()
|
||||
public void FileProvider_ReturnsInstance_IfExactlyOneFileProviderIsRegistered()
|
||||
{
|
||||
// Arrange
|
||||
var fileProvider = new TestFileProvider();
|
||||
|
|
@ -29,7 +29,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void FileProvider_ReturnsCompositeFileProviderIfNoInstancesAreRegistered()
|
||||
public void FileProvider_ReturnsNullFileProvider_IfNoInstancesAreRegistered()
|
||||
{
|
||||
// Arrange
|
||||
var options = new RazorViewEngineOptions();
|
||||
|
|
@ -41,11 +41,11 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
|||
var actual = fileProviderAccessor.FileProvider;
|
||||
|
||||
// Assert
|
||||
Assert.IsType<CompositeFileProvider>(actual);
|
||||
Assert.IsType<NullFileProvider>(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FileProvider_ReturnsCompositeFileProviderIfMoreThanOneInstanceIsRegistered()
|
||||
public void FileProvider_ReturnsCompositeFileProvider_IfMoreThanOneInstanceIsRegistered()
|
||||
{
|
||||
// Arrange
|
||||
var options = new RazorViewEngineOptions();
|
||||
|
|
|
|||
|
|
@ -18,21 +18,21 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
{
|
||||
var detailsProviders = new IMetadataDetailsProvider[]
|
||||
{
|
||||
new DefaultBindingMetadataProvider(CreateMessageProvider()),
|
||||
new DefaultBindingMetadataProvider(),
|
||||
new DefaultValidationMetadataProvider(),
|
||||
new DataAnnotationsMetadataProvider(),
|
||||
new DataMemberRequiredBindingMetadataProvider(),
|
||||
};
|
||||
|
||||
var compositeDetailsProvider = new DefaultCompositeMetadataDetailsProvider(detailsProviders);
|
||||
return new DefaultModelMetadataProvider(compositeDetailsProvider);
|
||||
return new DefaultModelMetadataProvider(compositeDetailsProvider, new TestOptionsManager<MvcOptions>());
|
||||
}
|
||||
|
||||
public static IModelMetadataProvider CreateDefaultProvider(IList<IMetadataDetailsProvider> providers)
|
||||
{
|
||||
var detailsProviders = new List<IMetadataDetailsProvider>()
|
||||
{
|
||||
new DefaultBindingMetadataProvider(CreateMessageProvider()),
|
||||
new DefaultBindingMetadataProvider(),
|
||||
new DefaultValidationMetadataProvider(),
|
||||
new DataAnnotationsMetadataProvider(),
|
||||
new DataMemberRequiredBindingMetadataProvider(),
|
||||
|
|
@ -41,7 +41,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
detailsProviders.AddRange(providers);
|
||||
|
||||
var compositeDetailsProvider = new DefaultCompositeMetadataDetailsProvider(detailsProviders);
|
||||
return new DefaultModelMetadataProvider(compositeDetailsProvider);
|
||||
return new DefaultModelMetadataProvider(compositeDetailsProvider, new TestOptionsManager<MvcOptions>());
|
||||
}
|
||||
|
||||
public static IModelMetadataProvider CreateProvider(IList<IMetadataDetailsProvider> providers)
|
||||
|
|
@ -53,7 +53,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
}
|
||||
|
||||
var compositeDetailsProvider = new DefaultCompositeMetadataDetailsProvider(detailsProviders);
|
||||
return new DefaultModelMetadataProvider(compositeDetailsProvider);
|
||||
return new DefaultModelMetadataProvider(compositeDetailsProvider, new TestOptionsManager<MvcOptions>());
|
||||
}
|
||||
|
||||
private readonly TestModelMetadataDetailsProvider _detailsProvider;
|
||||
|
|
@ -64,13 +64,15 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
}
|
||||
|
||||
private TestModelMetadataProvider(TestModelMetadataDetailsProvider detailsProvider)
|
||||
: base(new DefaultCompositeMetadataDetailsProvider(new IMetadataDetailsProvider[]
|
||||
{
|
||||
new DefaultBindingMetadataProvider(CreateMessageProvider()),
|
||||
new DefaultValidationMetadataProvider(),
|
||||
new DataAnnotationsMetadataProvider(),
|
||||
detailsProvider
|
||||
}))
|
||||
: base(
|
||||
new DefaultCompositeMetadataDetailsProvider(new IMetadataDetailsProvider[]
|
||||
{
|
||||
new DefaultBindingMetadataProvider(),
|
||||
new DefaultValidationMetadataProvider(),
|
||||
new DataAnnotationsMetadataProvider(),
|
||||
detailsProvider
|
||||
}),
|
||||
new TestOptionsManager<MvcOptions>())
|
||||
{
|
||||
_detailsProvider = detailsProvider;
|
||||
}
|
||||
|
|
@ -106,20 +108,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
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 :
|
||||
IBindingMetadataProvider,
|
||||
IDisplayMetadataProvider,
|
||||
|
|
|
|||
|
|
@ -32,20 +32,20 @@ namespace Microsoft.AspNetCore.Mvc.ViewEngines
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void FindView_IsMainPage_ReturnsNotFoundResult_WhenNoViewEnginesAreRegistered()
|
||||
public void FindView_IsMainPage_Throws_WhenNoViewEnginesAreRegistered()
|
||||
{
|
||||
// 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 actionContext = GetActionContext();
|
||||
var optionsAccessor = new TestOptionsManager<MvcViewOptions>();
|
||||
var compositeViewEngine = new CompositeViewEngine(optionsAccessor);
|
||||
|
||||
// Act
|
||||
var result = compositeViewEngine.FindView(actionContext, viewName, isMainPage: true);
|
||||
|
||||
// Assert
|
||||
Assert.False(result.Success);
|
||||
Assert.Empty(result.SearchedLocations);
|
||||
// Act & Assert
|
||||
var exception = Assert.Throws<InvalidOperationException>(
|
||||
() => compositeViewEngine.FindView(actionContext, viewName, isMainPage: true));
|
||||
Assert.Equal(expected, exception.Message);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -165,16 +165,16 @@ namespace Microsoft.AspNetCore.Mvc.ViewEngines
|
|||
public void GetView_ReturnsNotFoundResult_WhenNoViewEnginesAreRegistered(bool isMainPage)
|
||||
{
|
||||
// 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 optionsAccessor = new TestOptionsManager<MvcViewOptions>();
|
||||
var compositeViewEngine = new CompositeViewEngine(optionsAccessor);
|
||||
|
||||
// Act
|
||||
var result = compositeViewEngine.GetView("~/Index.html", viewName, isMainPage);
|
||||
|
||||
// Assert
|
||||
Assert.False(result.Success);
|
||||
Assert.Empty(result.SearchedLocations);
|
||||
// Act & Assert
|
||||
var exception = Assert.Throws<InvalidOperationException>(
|
||||
() => compositeViewEngine.GetView("~/Index.html", viewName, isMainPage));
|
||||
Assert.Equal(expected, exception.Message);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -305,16 +305,16 @@ namespace Microsoft.AspNetCore.Mvc.ViewEngines
|
|||
public void FindView_ReturnsNotFoundResult_WhenNoViewEnginesAreRegistered()
|
||||
{
|
||||
// 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 optionsAccessor = new TestOptionsManager<MvcViewOptions>();
|
||||
var compositeViewEngine = new CompositeViewEngine(optionsAccessor);
|
||||
|
||||
// Act
|
||||
var result = compositeViewEngine.FindView(GetActionContext(), viewName, isMainPage: false);
|
||||
|
||||
// Assert
|
||||
Assert.False(result.Success);
|
||||
Assert.Empty(result.SearchedLocations);
|
||||
// Act & AssertS
|
||||
var exception = Assert.Throws<InvalidOperationException>(
|
||||
() => compositeViewEngine.FindView(GetActionContext(), viewName, isMainPage: false));
|
||||
Assert.Equal(expected, exception.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
|
|||
Loading…
Reference in New Issue