diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultBindingMetadataProvider.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultBindingMetadataProvider.cs
index be82a8f14a..8ed75a30ef 100644
--- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultBindingMetadataProvider.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultBindingMetadataProvider.cs
@@ -15,19 +15,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
///
public class DefaultBindingMetadataProvider : IBindingMetadataProvider
{
- private readonly ModelBindingMessageProvider _messageProvider;
-
- public DefaultBindingMetadataProvider(ModelBindingMessageProvider messageProvider)
- {
- if (messageProvider == null)
- {
- throw new ArgumentNullException(nameof(messageProvider));
- }
-
- _messageProvider = messageProvider;
- }
-
- ///
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().ToArray();
if (propertyFilterProviders.Length == 0)
diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreMvcOptionsSetup.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreMvcOptionsSetup.cs
index 0246e9a223..74fa4b9914 100644
--- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreMvcOptionsSetup.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreMvcOptionsSetup.cs
@@ -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
diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ObjectResultExecutor.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/ObjectResultExecutor.cs
index cb0306c18a..ff06f6d483 100644
--- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ObjectResultExecutor.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/ObjectResultExecutor.cs
@@ -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;
}
-
+
///
/// Selects the to write the response based on the content type values
/// present in and .
@@ -423,19 +434,19 @@ namespace Microsoft.AspNetCore.Mvc.Internal
IList 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;
}
diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/BodyModelBinderProvider.cs b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/BodyModelBinderProvider.cs
index 0658cc71d3..d3f16b1088 100644
--- a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/BodyModelBinderProvider.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/BodyModelBinderProvider.cs
@@ -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);
}
diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/EmptyModelMetadataProvider.cs b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/EmptyModelMetadataProvider.cs
index 454df38355..122d760af8 100644
--- a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/EmptyModelMetadataProvider.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/EmptyModelMetadataProvider.cs
@@ -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()),
+ new OptionsAccessor())
{
}
- private class MessageOnlyBindingProvider : IBindingMetadataProvider
+ private class OptionsAccessor : IOptions
{
- 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();
}
}
}
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/DefaultModelMetadata.cs b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/DefaultModelMetadata.cs
index 8dc54ddea4..b99d128441 100644
--- a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/DefaultModelMetadata.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/DefaultModelMetadata.cs
@@ -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
protected ICompositeMetadataDetailsProvider DetailsProvider { get; }
+ ///
+ /// Gets the .
+ ///
+ /// Same as in all production scenarios.
+ protected ModelBindingMessageProvider ModelBindingMessageProvider { get; }
+
///
public virtual IEnumerable GetMetadataForProperties(Type modelType)
{
@@ -78,6 +109,16 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
return cacheEntry.Metadata;
}
+ private static ModelBindingMessageProvider GetMessageProvider(IOptions 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
///
protected virtual ModelMetadata CreateModelMetadata(DefaultMetadataDetails entry)
{
- return new DefaultModelMetadata(this, DetailsProvider, entry);
+ return new DefaultModelMetadata(this, DetailsProvider, entry, ModelBindingMessageProvider);
}
///
diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/ModelBindingMessageProvider.cs b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/ModelBindingMessageProvider.cs
index 4f1b67f65c..950e059241 100644
--- a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/ModelBindingMessageProvider.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/ModelBindingMessageProvider.cs
@@ -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
///
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;
}
///
diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/ModelBinderFactory.cs b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/ModelBinderFactory.cs
index fc72488a76..f2f74099a6 100644
--- a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/ModelBinderFactory.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/ModelBinderFactory.cs
@@ -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
diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Properties/Resources.Designer.cs b/src/Microsoft.AspNetCore.Mvc.Core/Properties/Resources.Designer.cs
index bb38364f51..418c37e1bf 100644
--- a/src/Microsoft.AspNetCore.Mvc.Core/Properties/Resources.Designer.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Core/Properties/Resources.Designer.cs
@@ -1162,6 +1162,54 @@ namespace Microsoft.AspNetCore.Mvc.Core
return string.Format(CultureInfo.CurrentCulture, GetString("CouldNotCreateIModelBinder"), p0);
}
+ ///
+ /// '{0}.{1}' must not be empty. At least one '{2}' is required to bind from the body.
+ ///
+ internal static string InputFormattersAreRequired
+ {
+ get { return GetString("InputFormattersAreRequired"); }
+ }
+
+ ///
+ /// '{0}.{1}' must not be empty. At least one '{2}' is required to bind from the body.
+ ///
+ internal static string FormatInputFormattersAreRequired(object p0, object p1, object p2)
+ {
+ return string.Format(CultureInfo.CurrentCulture, GetString("InputFormattersAreRequired"), p0, p1, p2);
+ }
+
+ ///
+ /// '{0}.{1}' must not be empty. At least one '{2}' is required to model bind.
+ ///
+ internal static string ModelBinderProvidersAreRequired
+ {
+ get { return GetString("ModelBinderProvidersAreRequired"); }
+ }
+
+ ///
+ /// '{0}.{1}' must not be empty. At least one '{2}' is required to model bind.
+ ///
+ internal static string FormatModelBinderProvidersAreRequired(object p0, object p1, object p2)
+ {
+ return string.Format(CultureInfo.CurrentCulture, GetString("ModelBinderProvidersAreRequired"), p0, p1, p2);
+ }
+
+ ///
+ /// '{0}.{1}' must not be empty. At least one '{2}' is required to format a response.
+ ///
+ internal static string OutputFormattersAreRequired
+ {
+ get { return GetString("OutputFormattersAreRequired"); }
+ }
+
+ ///
+ /// '{0}.{1}' must not be empty. At least one '{2}' is required to format a response.
+ ///
+ 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);
diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Resources.resx b/src/Microsoft.AspNetCore.Mvc.Core/Resources.resx
index d5ce00f4ef..732bba5387 100644
--- a/src/Microsoft.AspNetCore.Mvc.Core/Resources.resx
+++ b/src/Microsoft.AspNetCore.Mvc.Core/Resources.resx
@@ -1,17 +1,17 @@
-
@@ -343,4 +343,13 @@
Could not create a model binder for model object of type '{0}'.
+
+ '{0}.{1}' must not be empty. At least one '{2}' is required to bind from the body.
+
+
+ '{0}.{1}' must not be empty. At least one '{2}' is required to model bind.
+
+
+ '{0}.{1}' must not be empty. At least one '{2}' is required to format a response.
+
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/CompilerCache.cs b/src/Microsoft.AspNetCore.Mvc.Razor/Internal/CompilerCache.cs
index 15b11c1846..575f1e5728 100644
--- a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/CompilerCache.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Razor/Internal/CompilerCache.cs
@@ -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)
{
diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultRazorViewEngineFileProviderAccessor.cs b/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultRazorViewEngineFileProviderAccessor.cs
index e1fc5f6bf7..5823a4b39c 100644
--- a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultRazorViewEngineFileProviderAccessor.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultRazorViewEngineFileProviderAccessor.cs
@@ -18,7 +18,11 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
public DefaultRazorViewEngineFileProviderAccessor(IOptions 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];
}
diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Properties/Resources.Designer.cs b/src/Microsoft.AspNetCore.Mvc.Razor/Properties/Resources.Designer.cs
index fe96da1030..9552b4a630 100644
--- a/src/Microsoft.AspNetCore.Mvc.Razor/Properties/Resources.Designer.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Razor/Properties/Resources.Designer.cs
@@ -494,6 +494,22 @@ namespace Microsoft.AspNetCore.Mvc.Razor
return GetString("RazorPage_NestingAttributeWritingScopesNotSupported");
}
+ ///
+ /// '{0}.{1}' must not be empty. At least one '{2}' is required to locate a view for rendering.
+ ///
+ internal static string FileProvidersAreRequired
+ {
+ get { return GetString("FileProvidersAreRequired"); }
+ }
+
+ ///
+ /// '{0}.{1}' must not be empty. At least one '{2}' is required to locate a view for rendering.
+ ///
+ 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);
diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Resources.resx b/src/Microsoft.AspNetCore.Mvc.Razor/Resources.resx
index 5d8203d3da..6e402095f5 100644
--- a/src/Microsoft.AspNetCore.Mvc.Razor/Resources.resx
+++ b/src/Microsoft.AspNetCore.Mvc.Razor/Resources.resx
@@ -1,17 +1,17 @@
-
@@ -209,4 +209,7 @@
Nesting of TagHelper attribute writing scopes is not supported.
+
+ '{0}.{1}' must not be empty. At least one '{2}' is required to locate a view for rendering.
+
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Properties/Resources.Designer.cs b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Properties/Resources.Designer.cs
index a3ae42d54f..a2db38f704 100644
--- a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Properties/Resources.Designer.cs
+++ b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Properties/Resources.Designer.cs
@@ -890,6 +890,22 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
return string.Format(CultureInfo.CurrentCulture, GetString("CreateModelExpression_NullModelMetadata"), p0, p1);
}
+ ///
+ /// '{0}.{1}' must not be empty. At least one '{2}' is required to locate a view for rendering.
+ ///
+ internal static string ViewEnginesAreRequired
+ {
+ get { return GetString("ViewEnginesAreRequired"); }
+ }
+
+ ///
+ /// '{0}.{1}' must not be empty. At least one '{2}' is required to locate a view for rendering.
+ ///
+ 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);
diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Resources.resx b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Resources.resx
index 61c168de0c..55a2345b48 100644
--- a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Resources.resx
+++ b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Resources.resx
@@ -1,17 +1,17 @@
-
@@ -283,4 +283,7 @@
The {0} was unable to provide metadata for expression '{1}'.
+
+ '{0}.{1}' must not be empty. At least one '{2}' is required to locate a view for rendering.
+
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewEngines/CompositeViewEngine.cs b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewEngines/CompositeViewEngine.cs
index c492c88cea..d0478b2aeb 100644
--- a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewEngines/CompositeViewEngine.cs
+++ b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewEngines/CompositeViewEngine.cs
@@ -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 searchedLocations = null;
List 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 searchedLocations = null;
List searchedList = null;
diff --git a/test/Microsoft.AspNetCore.Mvc.Abstractions.Test/ModelBinding/ModelStateDictionaryTest.cs b/test/Microsoft.AspNetCore.Mvc.Abstractions.Test/ModelBinding/ModelStateDictionaryTest.cs
index 231f444240..3611cee6ed 100644
--- a/test/Microsoft.AspNetCore.Mvc.Abstractions.Test/ModelBinding/ModelStateDictionaryTest.cs
+++ b/test/Microsoft.AspNetCore.Mvc.Abstractions.Test/ModelBinding/ModelStateDictionaryTest.cs
@@ -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
+ {
+ public MvcOptions Value { get; } = new MvcOptions();
+ }
}
}
\ No newline at end of file
diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/ObjectResultExecutorTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/ObjectResultExecutorTest.cs
index 55912a2483..78e3fa1e38 100644
--- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/ObjectResultExecutorTest.cs
+++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/ObjectResultExecutorTest.cs
@@ -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(
+ () => executor.ExecuteAsync(actionContext, result));
+ Assert.Equal(expected, exception.Message);
+ }
+
[Theory]
[InlineData(new[] { "application/*" }, "application/*")]
[InlineData(new[] { "application/xml", "application/*", "application/json" }, "application/*")]
diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/DefaultBindingMetadataProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/DefaultBindingMetadataProviderTest.cs
index 85f8fca32c..9e553ea05d 100644
--- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/DefaultBindingMetadataProviderTest.cs
+++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/DefaultBindingMetadataProviderTest.cs
@@ -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
{
diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/BodyModelBinderProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/BodyModelBinderProviderTest.cs
index 7a15456b11..ff64a9bf4d 100644
--- a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/BodyModelBinderProviderTest.cs
+++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/BodyModelBinderProviderTest.cs
@@ -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(() => 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(result);
}
- private static BodyModelBinderProvider CreateProvider()
+ private static BodyModelBinderProvider CreateProvider(params IInputFormatter[] formatters)
{
- return new BodyModelBinderProvider(new List(), new TestHttpRequestStreamReaderFactory());
+ return new BodyModelBinderProvider(
+ new List(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 ReadAsync(InputFormatterContext context)
+ {
+ throw new NotImplementedException();
+ }
+ }
}
}
diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Metadata/DefaultModelMetadataProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Metadata/DefaultModelMetadataProviderTest.cs
index 66fda668b3..700be54129 100644
--- a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Metadata/DefaultModelMetadataProviderTest.cs
+++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Metadata/DefaultModelMetadataProviderTest.cs
@@ -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());
}
[Model("OnType")]
diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/ModelBinderFactoryTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/ModelBinderFactoryTest.cs
index 4524b49fb5..8b95b45a52 100644
--- a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/ModelBinderFactoryTest.cs
+++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/ModelBinderFactoryTest.cs
@@ -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();
+ var factory = new ModelBinderFactory(metadataProvider, options);
+ var context = new ModelBinderFactoryContext()
+ {
+ Metadata = metadataProvider.GetMetadataForType(typeof(string)),
+ };
+
+ // Act & Assert
+ var exception = Assert.Throws(() => factory.CreateBinder(context));
+ Assert.Equal(expected, exception.Message);
+ }
+
[Fact]
public void CreateBinder_Throws_WhenBinderNotCreated()
{
// Arrange
var metadataProvider = new TestModelMetadataProvider();
var options = new TestOptionsManager();
- 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(() => factory.CreateBinder(context));
-
- // Assert
Assert.Equal(
$"Could not create a model binder for model object of type '{typeof(string).FullName}'.",
exception.Message);
diff --git a/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/ModelMetadataProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/ModelMetadataProviderTest.cs
index f50bf402c7..ed343b3be0 100644
--- a/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/ModelMetadataProviderTest.cs
+++ b/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/ModelMetadataProviderTest.cs
@@ -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())
{
_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);
diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/CompilerCacheTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/CompilerCacheTest.cs
index e7e4b5ddf2..cf525a6443 100644
--- a/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/CompilerCacheTest.cs
+++ b/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/CompilerCacheTest.cs
@@ -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(
+ () => cache.GetOrAdd(ViewPath, _ => { throw new InvalidTimeZoneException(); }));
+ Assert.Equal(expected, exception.Message);
+ }
+
[Fact]
public void GetOrAdd_CachesCompilationExceptions()
{
diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/DefaultRazorViewEngineFileProviderAccessorTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/DefaultRazorViewEngineFileProviderAccessorTest.cs
index 52ac2610c4..7f64ceb9f8 100644
--- a/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/DefaultRazorViewEngineFileProviderAccessorTest.cs
+++ b/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/DefaultRazorViewEngineFileProviderAccessorTest.cs
@@ -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(actual);
+ Assert.IsType(actual);
}
[Fact]
- public void FileProvider_ReturnsCompositeFileProviderIfMoreThanOneInstanceIsRegistered()
+ public void FileProvider_ReturnsCompositeFileProvider_IfMoreThanOneInstanceIsRegistered()
{
// Arrange
var options = new RazorViewEngineOptions();
diff --git a/test/Microsoft.AspNetCore.Mvc.TestCommon/TestModelMetadataProvider.cs b/test/Microsoft.AspNetCore.Mvc.TestCommon/TestModelMetadataProvider.cs
index d40cd366a6..a6b3f89719 100644
--- a/test/Microsoft.AspNetCore.Mvc.TestCommon/TestModelMetadataProvider.cs
+++ b/test/Microsoft.AspNetCore.Mvc.TestCommon/TestModelMetadataProvider.cs
@@ -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());
}
public static IModelMetadataProvider CreateDefaultProvider(IList providers)
{
var detailsProviders = new List()
{
- 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());
}
public static IModelMetadataProvider CreateProvider(IList 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());
}
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())
{
_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,
diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewEngines/CompositeViewEngineTest.cs b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewEngines/CompositeViewEngineTest.cs
index 036a8ed1d3..3696a61639 100644
--- a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewEngines/CompositeViewEngineTest.cs
+++ b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewEngines/CompositeViewEngineTest.cs
@@ -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();
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(
+ () => 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();
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(
+ () => 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();
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(
+ () => compositeViewEngine.FindView(GetActionContext(), viewName, isMainPage: false));
+ Assert.Equal(expected, exception.Message);
}
[Fact]