diff --git a/samples/MvcSample.Web/Filters/InspectResultPageAttribute.cs b/samples/MvcSample.Web/Filters/InspectResultPageAttribute.cs index 6d91c843d0..9ff209fb95 100644 --- a/samples/MvcSample.Web/Filters/InspectResultPageAttribute.cs +++ b/samples/MvcSample.Web/Filters/InspectResultPageAttribute.cs @@ -1,28 +1,37 @@ using System; -using System.Threading.Tasks; using Microsoft.AspNet.Mvc; -using Microsoft.AspNet.Mvc.Filters; using MvcSample.Web.Models; namespace MvcSample.Web.Filters { - public class InspectResultPageAttribute : ActionFilterAttribute + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)] + public class InspectResultPageAttribute : Attribute, IFilterFactory { - public override async Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next) + public IFilter CreateInstance(IServiceProvider serviceProvider) { - var viewResult = context.Result as ViewResult; + return new InspectResultPageFilter(); + } - if (viewResult != null) + private class InspectResultPageFilter : IResultFilter + { + public void OnResultExecuting(ResultExecutingContext context) { - var user = viewResult.ViewData.Model as User; + var viewResult = context.Result as ViewResult; - if (user != null) + if (viewResult != null) { - user.Name += "**" + user.Name + "**"; + var user = viewResult.ViewData.Model as User; + + if (user != null) + { + user.Name += "**" + user.Name + "**"; + } } } - await next(); + public void OnResultExecuted(ResultExecutedContext context) + { + } } } } diff --git a/samples/MvcSample.Web/Filters/UserNameProvider.cs b/samples/MvcSample.Web/Filters/UserNameProvider.cs index 00d94fada4..9809b5d9c9 100644 --- a/samples/MvcSample.Web/Filters/UserNameProvider.cs +++ b/samples/MvcSample.Web/Filters/UserNameProvider.cs @@ -4,8 +4,12 @@ namespace MvcSample.Web.Filters { public class UserNameProvider : IActionFilter { - private static readonly string[] _userNames = new[] { "Jon", "David", "Goliath" }; - private static int _index; + private readonly UserNameService _nameService; + + public UserNameProvider(UserNameService nameService) + { + _nameService = nameService; + } public void OnActionExecuting(ActionExecutingContext context) { @@ -17,7 +21,7 @@ namespace MvcSample.Web.Filters if (string.IsNullOrWhiteSpace(userName)) { - context.ActionArguments["userName"] = _userNames[(_index++)%3]; + context.ActionArguments["userName"] = _nameService.GetName(); } } diff --git a/samples/MvcSample.Web/Filters/UserNameService.cs b/samples/MvcSample.Web/Filters/UserNameService.cs new file mode 100644 index 0000000000..7480bc1a91 --- /dev/null +++ b/samples/MvcSample.Web/Filters/UserNameService.cs @@ -0,0 +1,15 @@ +using Microsoft.AspNet.Mvc; + +namespace MvcSample.Web.Filters +{ + public class UserNameService + { + private static readonly string[] _userNames = new[] { "Jon", "David", "Goliath" }; + private static int _index; + + public string GetName() + { + return _userNames[_index++ % 3]; + } + } +} diff --git a/samples/MvcSample.Web/MvcSample.Web.kproj b/samples/MvcSample.Web/MvcSample.Web.kproj index 99aa4a241b..ddbfaf1c7f 100644 --- a/samples/MvcSample.Web/MvcSample.Web.kproj +++ b/samples/MvcSample.Web/MvcSample.Web.kproj @@ -50,6 +50,7 @@ + diff --git a/samples/MvcSample.Web/Startup.cs b/samples/MvcSample.Web/Startup.cs index 358c8cee2d..207b720f7d 100644 --- a/samples/MvcSample.Web/Startup.cs +++ b/samples/MvcSample.Web/Startup.cs @@ -2,6 +2,7 @@ using Microsoft.AspNet.Abstractions; using Microsoft.AspNet.Mvc; using Microsoft.AspNet.Routing; +using MvcSample.Web.Filters; namespace MvcSample.Web { @@ -13,6 +14,7 @@ namespace MvcSample.Web { services.AddMvc(); services.AddSingleton(); + services.AddSingleton(); }); app.UseMvc(routes => diff --git a/src/Microsoft.AspNet.Mvc.Core/Filters/DefaultFilterProvider.cs b/src/Microsoft.AspNet.Mvc.Core/Filters/DefaultFilterProvider.cs index e63eb41fab..53e123dd92 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Filters/DefaultFilterProvider.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Filters/DefaultFilterProvider.cs @@ -1,6 +1,7 @@ using System; using System.Diagnostics.Contracts; using Microsoft.AspNet.DependencyInjection; +using Microsoft.AspNet.Mvc.Core; namespace Microsoft.AspNet.Mvc.Filters { @@ -46,45 +47,23 @@ namespace Microsoft.AspNet.Mvc.Filters var filter = filterItem.Descriptor.Filter; - var serviceFilterSignature = filter as IServiceFilter; - if (serviceFilterSignature != null) + var filterFactory = filter as IFilterFactory; + if (filterFactory == null) { - var serviceFilter = ServiceProvider.GetService(serviceFilterSignature.ServiceType) as IFilter; - - if (serviceFilter == null) - { - throw new InvalidOperationException("Service filter must be of type IFilter"); - } - - filterItem.Filter = serviceFilter; + filterItem.Filter = filter; } else { - var typeFilterSignature = filter as ITypeFilter; - if (typeFilterSignature != null) + filterItem.Filter = filterFactory.CreateInstance(ServiceProvider); + + if (filterItem.Filter == null) { - if (typeFilterSignature.ImplementationType == null) - { - throw new InvalidOperationException("Type filter must provide a type to instantiate"); - } - - if (!typeof (IFilter).IsAssignableFrom(typeFilterSignature.ImplementationType)) - { - throw new InvalidOperationException("Type filter must implement IFilter"); - } - - var typeFilter = (IFilter)_typeActivator.CreateInstance( - ServiceProvider, - typeFilterSignature.ImplementationType, - typeFilterSignature.Arguments); - - ApplyFilterToContainer(typeFilter, filter); - filterItem.Filter = typeFilter; - } - else - { - filterItem.Filter = filter; + throw new InvalidOperationException(Resources.FormatTypeMethodMustReturnNotNullValue( + "CreateInstance", + typeof(IFilterFactory).Name)); } + + ApplyFilterToContainer(filterItem.Filter, filterFactory); } } diff --git a/src/Microsoft.AspNet.Mvc.Core/Filters/IFilterFactory.cs b/src/Microsoft.AspNet.Mvc.Core/Filters/IFilterFactory.cs new file mode 100644 index 0000000000..bf7307bc5d --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Core/Filters/IFilterFactory.cs @@ -0,0 +1,9 @@ +using System; + +namespace Microsoft.AspNet.Mvc +{ + public interface IFilterFactory : IFilter + { + IFilter CreateInstance([NotNull] IServiceProvider serviceProvider); + } +} diff --git a/src/Microsoft.AspNet.Mvc.Core/Filters/IServiceFilter.cs b/src/Microsoft.AspNet.Mvc.Core/Filters/IServiceFilter.cs deleted file mode 100644 index a5763f452b..0000000000 --- a/src/Microsoft.AspNet.Mvc.Core/Filters/IServiceFilter.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System; - -namespace Microsoft.AspNet.Mvc -{ - public interface IServiceFilter : IFilter - { - Type ServiceType { get; } - } -} diff --git a/src/Microsoft.AspNet.Mvc.Core/Filters/ITypeFilter.cs b/src/Microsoft.AspNet.Mvc.Core/Filters/ITypeFilter.cs deleted file mode 100644 index eccb2ed6df..0000000000 --- a/src/Microsoft.AspNet.Mvc.Core/Filters/ITypeFilter.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System; - -namespace Microsoft.AspNet.Mvc -{ - public interface ITypeFilter : IFilter - { - object[] Arguments { get; } - - Type ImplementationType { get; } - } -} diff --git a/src/Microsoft.AspNet.Mvc.Core/Filters/ServiceFilterAttribute.cs b/src/Microsoft.AspNet.Mvc.Core/Filters/ServiceFilterAttribute.cs index e5ed6ae706..e64405c74a 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Filters/ServiceFilterAttribute.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Filters/ServiceFilterAttribute.cs @@ -1,13 +1,14 @@ using System; using System.Diagnostics; +using Microsoft.AspNet.Mvc.Core; namespace Microsoft.AspNet.Mvc { [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)] [DebuggerDisplay("ServiceFilter: Type={ServiceType} Order={Order}")] - public class ServiceFilterAttribute : Attribute, IServiceFilter + public class ServiceFilterAttribute : Attribute, IFilterFactory, IOrderedFilter { - public ServiceFilterAttribute(Type type) + public ServiceFilterAttribute([NotNull] Type type) { ServiceType = type; } @@ -15,5 +16,20 @@ namespace Microsoft.AspNet.Mvc public Type ServiceType { get; private set; } public int Order { get; set; } + + public IFilter CreateInstance([NotNull] IServiceProvider serviceProvider) + { + var service = serviceProvider.GetService(ServiceType); + + var filter = service as IFilter; + if (filter == null) + { + throw new InvalidOperationException(Resources.FormatFilterFactoryAttribute_TypeMustImplementIFilter( + typeof(ServiceFilterAttribute).Name, + typeof(IFilter).Name)); + } + + return filter; + } } } diff --git a/src/Microsoft.AspNet.Mvc.Core/Filters/TypeFilterAttribute.cs b/src/Microsoft.AspNet.Mvc.Core/Filters/TypeFilterAttribute.cs index a7fc9bc12d..22bc8696dc 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Filters/TypeFilterAttribute.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Filters/TypeFilterAttribute.cs @@ -1,13 +1,15 @@ using System; using System.Diagnostics; +using Microsoft.AspNet.DependencyInjection; +using Microsoft.AspNet.Mvc.Core; namespace Microsoft.AspNet.Mvc { [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)] [DebuggerDisplay("TypeFilter: Type={ImplementationType} Order={Order}")] - public class TypeFilterAttribute : Attribute, ITypeFilter, IOrderedFilter + public class TypeFilterAttribute : Attribute, IFilterFactory, IOrderedFilter { - public TypeFilterAttribute(Type type) + public TypeFilterAttribute([NotNull] Type type) { ImplementationType = type; } @@ -17,5 +19,21 @@ namespace Microsoft.AspNet.Mvc public Type ImplementationType { get; private set; } public int Order { get; set; } + + public IFilter CreateInstance([NotNull] IServiceProvider serviceProvider) + { + var activator = serviceProvider.GetService(); + var obj = activator.CreateInstance(serviceProvider, ImplementationType, Arguments ?? new object[0]); + + var filter = obj as IFilter; + if (filter == null) + { + throw new InvalidOperationException(Resources.FormatFilterFactoryAttribute_TypeMustImplementIFilter( + typeof(TypeFilterAttribute).Name, + typeof(IFilter).Name)); + } + + return filter; + } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/Microsoft.AspNet.Mvc.Core.kproj b/src/Microsoft.AspNet.Mvc.Core/Microsoft.AspNet.Mvc.Core.kproj index 7390e2a347..6ac42e8f14 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Microsoft.AspNet.Mvc.Core.kproj +++ b/src/Microsoft.AspNet.Mvc.Core/Microsoft.AspNet.Mvc.Core.kproj @@ -103,8 +103,7 @@ - - + diff --git a/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs b/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs index c291a3c46e..685a10cae8 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs @@ -10,86 +10,6 @@ namespace Microsoft.AspNet.Mvc.Core private static readonly ResourceManager _resourceManager = new ResourceManager("Microsoft.AspNet.Mvc.Core.Resources", typeof(Resources).GetTypeInfo().Assembly); - /// - /// The provided identity of type '{0}' is marked IsAuthenticated = true but does not have a value for Name. By default, the anti-forgery system requires that all authenticated identities have a unique Name. If it is not possible to provide a unique Name for this identity, consider extending IAdditionalDataProvider by overriding the DefaultAdditionalDataProvider or a custom type that can provide some form of unique identifier for the current user. - /// - internal static string TokenValidator_AuthenticatedUserWithoutUsername - { - get { return GetString("TokenValidator_AuthenticatedUserWithoutUsername"); } - } - - /// - /// The provided identity of type '{0}' is marked IsAuthenticated = true but does not have a value for Name. By default, the anti-forgery system requires that all authenticated identities have a unique Name. If it is not possible to provide a unique Name for this identity, consider extending IAdditionalDataProvider by overriding the DefaultAdditionalDataProvider or a custom type that can provide some form of unique identifier for the current user. - /// - internal static string FormatTokenValidator_AuthenticatedUserWithoutUsername(object p0) - { - return string.Format(CultureInfo.CurrentCulture, GetString("TokenValidator_AuthenticatedUserWithoutUsername"), p0); - } - - /// - /// A claim of type '{0}' was not present on the provided ClaimsIdentity. - /// - internal static string ClaimUidExtractor_ClaimNotPresent - { - get { return GetString("ClaimUidExtractor_ClaimNotPresent"); } - } - - /// - /// A claim of type '{0}' was not present on the provided ClaimsIdentity. - /// - internal static string FormatClaimUidExtractor_ClaimNotPresent(object p0) - { - return string.Format(CultureInfo.CurrentCulture, GetString("ClaimUidExtractor_ClaimNotPresent"), p0); - } - - /// - /// A claim of type 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier' or 'http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider' was not present on the provided ClaimsIdentity. To enable anti-forgery token support with claims-based authentication, please verify that the configured claims provider is providing both of these claims on the ClaimsIdentity instances it generates. If the configured claims provider instead uses a different claim type as a unique identifier, it can be configured by setting the static property AntiForgeryConfig.UniqueClaimTypeIdentifier. - /// - internal static string ClaimUidExtractor_DefaultClaimsNotPresent - { - get { return GetString("ClaimUidExtractor_DefaultClaimsNotPresent"); } - } - - /// - /// A claim of type 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier' or 'http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider' was not present on the provided ClaimsIdentity. To enable anti-forgery token support with claims-based authentication, please verify that the configured claims provider is providing both of these claims on the ClaimsIdentity instances it generates. If the configured claims provider instead uses a different claim type as a unique identifier, it can be configured by setting the static property AntiForgeryConfig.UniqueClaimTypeIdentifier. - /// - internal static string FormatClaimUidExtractor_DefaultClaimsNotPresent() - { - return GetString("ClaimUidExtractor_DefaultClaimsNotPresent"); - } - - /// - /// The method '{0}' on type '{1}' returned an instance of '{2}'. Make sure to call Unwrap on the returned value to avoid unobserved faulted Task. - /// - internal static string ActionExecutor_WrappedTaskInstance - { - get { return GetString("ActionExecutor_WrappedTaskInstance"); } - } - - /// - /// The method '{0}' on type '{1}' returned an instance of '{2}'. Make sure to call Unwrap on the returned value to avoid unobserved faulted Task. - /// - internal static string FormatActionExecutor_WrappedTaskInstance(object p0, object p1, object p2) - { - return string.Format(CultureInfo.CurrentCulture, GetString("ActionExecutor_WrappedTaskInstance"), p0, p1, p2); - } - - /// - /// The method '{0}' on type '{1}' returned a Task instance even though it is not an asynchronous method. - /// - internal static string ActionExecutor_UnexpectedTaskInstance - { - get { return GetString("ActionExecutor_UnexpectedTaskInstance"); } - } - - /// - /// The method '{0}' on type '{1}' returned a Task instance even though it is not an asynchronous method. - /// - internal static string FormatActionExecutor_UnexpectedTaskInstance(object p0, object p1) - { - return string.Format(CultureInfo.CurrentCulture, GetString("ActionExecutor_UnexpectedTaskInstance"), p0, p1); - } - /// /// The provided anti-forgery token failed a custom data check. /// @@ -234,6 +154,70 @@ namespace Microsoft.AspNet.Mvc.Core return GetString("AntiForgeryWorker_RequireSSL"); } + /// + /// The method '{0}' on type '{1}' returned an instance of '{2}'. Make sure to call Unwrap on the returned value to avoid unobserved faulted Task. + /// + internal static string ActionExecutor_WrappedTaskInstance + { + get { return GetString("ActionExecutor_WrappedTaskInstance"); } + } + + /// + /// The method '{0}' on type '{1}' returned an instance of '{2}'. Make sure to call Unwrap on the returned value to avoid unobserved faulted Task. + /// + internal static string FormatActionExecutor_WrappedTaskInstance(object p0, object p1, object p2) + { + return string.Format(CultureInfo.CurrentCulture, GetString("ActionExecutor_WrappedTaskInstance"), p0, p1, p2); + } + + /// + /// The method '{0}' on type '{1}' returned a Task instance even though it is not an asynchronous method. + /// + internal static string ActionExecutor_UnexpectedTaskInstance + { + get { return GetString("ActionExecutor_UnexpectedTaskInstance"); } + } + + /// + /// The method '{0}' on type '{1}' returned a Task instance even though it is not an asynchronous method. + /// + internal static string FormatActionExecutor_UnexpectedTaskInstance(object p0, object p1) + { + return string.Format(CultureInfo.CurrentCulture, GetString("ActionExecutor_UnexpectedTaskInstance"), p0, p1); + } + + /// + /// A claim of type '{0}' was not present on the provided ClaimsIdentity. + /// + internal static string ClaimUidExtractor_ClaimNotPresent + { + get { return GetString("ClaimUidExtractor_ClaimNotPresent"); } + } + + /// + /// A claim of type '{0}' was not present on the provided ClaimsIdentity. + /// + internal static string FormatClaimUidExtractor_ClaimNotPresent(object p0) + { + return string.Format(CultureInfo.CurrentCulture, GetString("ClaimUidExtractor_ClaimNotPresent"), p0); + } + + /// + /// The provided identity of type '{0}' is marked IsAuthenticated = true but does not have a value for Name. By default, the anti-forgery system requires that all authenticated identities have a unique Name. If it is not possible to provide a unique Name for this identity, consider extending IAdditionalDataProvider by overriding the DefaultAdditionalDataProvider or a custom type that can provide some form of unique identifier for the current user. + /// + internal static string TokenValidator_AuthenticatedUserWithoutUsername + { + get { return GetString("TokenValidator_AuthenticatedUserWithoutUsername"); } + } + + /// + /// The provided identity of type '{0}' is marked IsAuthenticated = true but does not have a value for Name. By default, the anti-forgery system requires that all authenticated identities have a unique Name. If it is not possible to provide a unique Name for this identity, consider extending IAdditionalDataProvider by overriding the DefaultAdditionalDataProvider or a custom type that can provide some form of unique identifier for the current user. + /// + internal static string FormatTokenValidator_AuthenticatedUserWithoutUsername(object p0) + { + return string.Format(CultureInfo.CurrentCulture, GetString("TokenValidator_AuthenticatedUserWithoutUsername"), p0); + } + /// /// The class ReflectedActionFilterEndPoint only supports ReflectedActionDescriptors. /// @@ -475,19 +459,19 @@ namespace Microsoft.AspNet.Mvc.Core } /// - /// The '{0}' must return a non-null '{1}'. + /// The '{0}' method of type '{1}' cannot return a null value. /// - internal static string MethodMustReturnNotNullValue + internal static string TypeMethodMustReturnNotNullValue { - get { return GetString("MethodMustReturnNotNullValue"); } + get { return GetString("TypeMethodMustReturnNotNullValue"); } } /// - /// The '{0}' must return a non-null '{1}'. + /// The '{0}' method of type '{1}' cannot return a null value. /// - internal static string FormatMethodMustReturnNotNullValue(object p0, object p1) + internal static string FormatTypeMethodMustReturnNotNullValue(object p0, object p1) { - return string.Format(CultureInfo.CurrentCulture, GetString("MethodMustReturnNotNullValue"), p0, p1); + return string.Format(CultureInfo.CurrentCulture, GetString("TypeMethodMustReturnNotNullValue"), p0, p1); } /// @@ -922,6 +906,21 @@ namespace Microsoft.AspNet.Mvc.Core return GetString("HtmlHelper_TextAreaParameterOutOfRange"); } + /// The type provided to '{0}' must implement '{1}'. + /// + internal static string FilterFactoryAttribute_TypeMustImplementIFilter + { + get { return GetString("FilterFactoryAttribute_TypeMustImplementIFilter"); } + } + + /// + /// The type provided to '{0}' must implement '{1}'. + /// + internal static string FormatFilterFactoryAttribute_TypeMustImplementIFilter(object p0, object p1) + { + return string.Format(CultureInfo.CurrentCulture, GetString("FilterFactoryAttribute_TypeMustImplementIFilter"), p0, p1); + } + private static string GetString(string name, params string[] formatterNames) { var value = _resourceManager.GetString(name); diff --git a/src/Microsoft.AspNet.Mvc.Core/Resources.resx b/src/Microsoft.AspNet.Mvc.Core/Resources.resx index 92a8fe7ec4..55b2f2eeb3 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Resources.resx +++ b/src/Microsoft.AspNet.Mvc.Core/Resources.resx @@ -150,9 +150,6 @@ The method '{0}' on type '{1}' returned a Task instance even though it is not an asynchronous method. - A claim of type '{0}' was not present on the provided ClaimsIdentity. @@ -204,8 +201,8 @@ The '{0}' property of '{1}' must not be null. - - The '{0}' must return a non-null '{1}'. + + The '{0}' method of type '{1}' cannot return a null value. The supplied route values are ambiguous and can select multiple sets of actions. @@ -288,4 +285,7 @@ The value must be greater than or equal to zero. - \ No newline at end of file + + The type provided to '{0}' must implement '{1}'. + + diff --git a/src/Microsoft.AspNet.Mvc.Razor.Host/Properties/Resources.Designer.cs b/src/Microsoft.AspNet.Mvc.Razor.Host/Properties/Resources.Designer.cs index 97d038e5a3..7d4618123b 100644 --- a/src/Microsoft.AspNet.Mvc.Razor.Host/Properties/Resources.Designer.cs +++ b/src/Microsoft.AspNet.Mvc.Razor.Host/Properties/Resources.Designer.cs @@ -63,7 +63,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Host var value = _resourceManager.GetString(name); System.Diagnostics.Debug.Assert(value != null); - + if (formatterNames != null) { for (var i = 0; i < formatterNames.Length; i++) diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Properties/Resources.Designer.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Properties/Resources.Designer.cs index a127d68320..35e49f5cc1 100644 --- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Properties/Resources.Designer.cs +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Properties/Resources.Designer.cs @@ -31,7 +31,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test var value = _resourceManager.GetString(name); System.Diagnostics.Debug.Assert(value != null); - + if (formatterNames != null) { for (var i = 0; i < formatterNames.Length; i++)