diff --git a/Mvc.NoFun.sln b/Mvc.NoFun.sln index 2164ee973e..33d7b08eff 100644 --- a/Mvc.NoFun.sln +++ b/Mvc.NoFun.sln @@ -74,6 +74,8 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Mvc.Extens EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Mvc.Extensions.Test", "test\Microsoft.AspNet.Mvc.Extensions.Test\Microsoft.AspNet.Mvc.Extensions.Test.xproj", "{5DF6EFA5-865E-450B-BF83-DE9CE88EB77C}" EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "MvcMinimalSample.Web", "samples\MvcMinimalSample.Web\MvcMinimalSample.Web.xproj", "{F21E225B-190B-4DAA-8B0A-05986D231F56}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -419,6 +421,18 @@ Global {5DF6EFA5-865E-450B-BF83-DE9CE88EB77C}.Release|Mixed Platforms.Build.0 = Release|Any CPU {5DF6EFA5-865E-450B-BF83-DE9CE88EB77C}.Release|x86.ActiveCfg = Release|Any CPU {5DF6EFA5-865E-450B-BF83-DE9CE88EB77C}.Release|x86.Build.0 = Release|Any CPU + {F21E225B-190B-4DAA-8B0A-05986D231F56}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F21E225B-190B-4DAA-8B0A-05986D231F56}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F21E225B-190B-4DAA-8B0A-05986D231F56}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {F21E225B-190B-4DAA-8B0A-05986D231F56}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {F21E225B-190B-4DAA-8B0A-05986D231F56}.Debug|x86.ActiveCfg = Debug|Any CPU + {F21E225B-190B-4DAA-8B0A-05986D231F56}.Debug|x86.Build.0 = Debug|Any CPU + {F21E225B-190B-4DAA-8B0A-05986D231F56}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F21E225B-190B-4DAA-8B0A-05986D231F56}.Release|Any CPU.Build.0 = Release|Any CPU + {F21E225B-190B-4DAA-8B0A-05986D231F56}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {F21E225B-190B-4DAA-8B0A-05986D231F56}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {F21E225B-190B-4DAA-8B0A-05986D231F56}.Release|x86.ActiveCfg = Release|Any CPU + {F21E225B-190B-4DAA-8B0A-05986D231F56}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -454,5 +468,6 @@ Global {4C2AD8AB-8AC0-46C4-80C6-C5577C7255F6} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} {B2CA101A-87E6-4DD2-9BB2-28DA68EF1A94} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E} {5DF6EFA5-865E-450B-BF83-DE9CE88EB77C} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} + {F21E225B-190B-4DAA-8B0A-05986D231F56} = {DAAE4C74-D06F-4874-A166-33305D2643CE} EndGlobalSection EndGlobal diff --git a/samples/MvcMinimalSample.Web/HomeController.cs b/samples/MvcMinimalSample.Web/HomeController.cs new file mode 100644 index 0000000000..a78cf8c985 --- /dev/null +++ b/samples/MvcMinimalSample.Web/HomeController.cs @@ -0,0 +1,18 @@ +// 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. + +namespace MvcMinimalSample.Web +{ + public class HomeController + { + public string Index() + { + return "Hi from MVC"; + } + + public string GetUser(int id) + { + return $"User: {id}"; + } + } +} diff --git a/samples/MvcMinimalSample.Web/MvcMinimalSample.Web.xproj b/samples/MvcMinimalSample.Web/MvcMinimalSample.Web.xproj new file mode 100644 index 0000000000..e9cdd21b1e --- /dev/null +++ b/samples/MvcMinimalSample.Web/MvcMinimalSample.Web.xproj @@ -0,0 +1,19 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + f21e225b-190b-4daa-8b0a-05986d231f56 + MvcMinimalSample.Web + ..\..\artifacts\obj\$(MSBuildProjectName) + ..\..\artifacts\bin\$(MSBuildProjectName)\ + + + 2.0 + 4976 + + + \ No newline at end of file diff --git a/samples/MvcMinimalSample.Web/Startup.cs b/samples/MvcMinimalSample.Web/Startup.cs new file mode 100644 index 0000000000..bd4908a517 --- /dev/null +++ b/samples/MvcMinimalSample.Web/Startup.cs @@ -0,0 +1,21 @@ +// 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.AspNet.Builder; +using Microsoft.Framework.DependencyInjection; + +namespace MvcMinimalSample.Web +{ + public class Startup + { + public void ConfigureServices(IServiceCollection services) + { + services.AddMinimalMvc(); + } + + public void Configure(IApplicationBuilder app) + { + app.UseMvcWithDefaultRoute(); + } + } +} diff --git a/samples/MvcMinimalSample.Web/project.json b/samples/MvcMinimalSample.Web/project.json new file mode 100644 index 0000000000..2c2be227ac --- /dev/null +++ b/samples/MvcMinimalSample.Web/project.json @@ -0,0 +1,32 @@ +{ + "webroot": "wwwroot", + "version": "1.0.0-*", + + "dependencies": { + "Microsoft.AspNet.Mvc.Core": "6.0.0-*", + "Microsoft.AspNet.Hosting": "1.0.0-*", + "Microsoft.AspNet.Server.WebListener": "1.0.0-*" + }, + + "commands": { + "web": "Microsoft.AspNet.Hosting --server Microsoft.AspNet.Server.WebListener --server.urls http://localhost:5000" + }, + + "frameworks": { + "dnx451": { }, + "dnxcore50": { } + }, + + "publishExclude": [ + "node_modules", + "bower_components", + "**.xproj", + "**.user", + "**.vspscc" + ], + "exclude": [ + "wwwroot", + "node_modules", + "bower_components" + ] +} diff --git a/samples/MvcMinimalSample.Web/wwwroot/Index.html b/samples/MvcMinimalSample.Web/wwwroot/Index.html new file mode 100644 index 0000000000..7659725483 --- /dev/null +++ b/samples/MvcMinimalSample.Web/wwwroot/Index.html @@ -0,0 +1,10 @@ + + + + + Minimal MVC Sample + + +

Hello, World!

+ + \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc/BuilderExtensions.cs b/src/Microsoft.AspNet.Mvc.Core/BuilderExtensions.cs similarity index 100% rename from src/Microsoft.AspNet.Mvc/BuilderExtensions.cs rename to src/Microsoft.AspNet.Mvc.Core/BuilderExtensions.cs diff --git a/src/Microsoft.AspNet.Mvc.Core/CoreMvcOptionsSetup.cs b/src/Microsoft.AspNet.Mvc.Core/CoreMvcOptionsSetup.cs new file mode 100644 index 0000000000..b4bf49631f --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Core/CoreMvcOptionsSetup.cs @@ -0,0 +1,67 @@ +// 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.AspNet.Mvc.ModelBinding; +using Microsoft.AspNet.Mvc.ModelBinding.Metadata; +using Microsoft.AspNet.Mvc.ModelBinding.Validation; +using Microsoft.Framework.OptionsModel; + +namespace Microsoft.AspNet.Mvc +{ + /// + /// Sets up default options for . + /// + public class CoreMvcOptionsSetup : ConfigureOptions + { + public CoreMvcOptionsSetup() + : base(ConfigureMvc) + { + Order = DefaultOrder.DefaultFrameworkSortOrder; + } + + public static void ConfigureMvc(MvcOptions options) + { + // Set up ModelBinding + options.ModelBinders.Add(new BinderTypeBasedModelBinder()); + options.ModelBinders.Add(new ServicesModelBinder()); + options.ModelBinders.Add(new BodyModelBinder()); + options.ModelBinders.Add(new HeaderModelBinder()); + options.ModelBinders.Add(new TypeConverterModelBinder()); + options.ModelBinders.Add(new TypeMatchModelBinder()); + options.ModelBinders.Add(new CancellationTokenModelBinder()); + options.ModelBinders.Add(new ByteArrayModelBinder()); + options.ModelBinders.Add(new FormFileModelBinder()); + options.ModelBinders.Add(new FormCollectionModelBinder()); + options.ModelBinders.Add(new GenericModelBinder()); + options.ModelBinders.Add(new MutableObjectModelBinder()); + options.ModelBinders.Add(new ComplexModelDtoModelBinder()); + + // Set up default output formatters. + options.OutputFormatters.Add(new HttpNoContentOutputFormatter()); + options.OutputFormatters.Add(new StringOutputFormatter()); + options.OutputFormatters.Add(new StreamOutputFormatter()); + + // Set up ValueProviders + options.ValueProviderFactories.Add(new RouteValueValueProviderFactory()); + options.ValueProviderFactories.Add(new QueryStringValueProviderFactory()); + options.ValueProviderFactories.Add(new FormValueProviderFactory()); + + // Set up metadata providers + options.ModelMetadataDetailsProviders.Add(new DefaultBindingMetadataProvider()); + options.ModelMetadataDetailsProviders.Add(new DefaultValidationMetadataProvider()); + + // Set up validators + options.ModelValidatorProviders.Add(new DefaultModelValidatorProvider()); + + // Add types to be excluded from Validation + options.ValidationExcludeFilters.Add(new SimpleTypesExcludeFilter()); + options.ValidationExcludeFilters.Add(typeof(Type)); + + // Any 'known' types that we bind should be marked as excluded from validation. + options.ValidationExcludeFilters.Add(typeof(System.Threading.CancellationToken)); + options.ValidationExcludeFilters.Add(typeof(Http.IFormFile)); + options.ValidationExcludeFilters.Add(typeof(Http.IFormCollection)); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/MvcCoreServiceCollectionExtensions.cs b/src/Microsoft.AspNet.Mvc.Core/MvcCoreServiceCollectionExtensions.cs new file mode 100644 index 0000000000..11544de30b --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Core/MvcCoreServiceCollectionExtensions.cs @@ -0,0 +1,161 @@ +// 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.Diagnostics; +using System.Linq; +using Microsoft.AspNet.Mvc; +using Microsoft.AspNet.Mvc.ActionConstraints; +using Microsoft.AspNet.Mvc.ApplicationModels; +using Microsoft.AspNet.Mvc.Core; +using Microsoft.AspNet.Mvc.Filters; +using Microsoft.AspNet.Mvc.Internal; +using Microsoft.AspNet.Mvc.ModelBinding; +using Microsoft.AspNet.Mvc.ModelBinding.Metadata; +using Microsoft.AspNet.Mvc.ModelBinding.Validation; +using Microsoft.AspNet.Mvc.Routing; +using Microsoft.AspNet.Routing; +using Microsoft.Framework.Internal; +using Microsoft.Framework.OptionsModel; + +namespace Microsoft.Framework.DependencyInjection +{ + public static class MvcCoreServiceCollectionExtensions + { + public static IServiceCollection AddMinimalMvc([NotNull] this IServiceCollection services) + { + ConfigureDefaultServices(services); + + AddMvcCoreServices(services); + + return services; + } + + /// + /// Configures a set of for the application. + /// + /// The services available in the application. + /// The which need to be configured. + public static void ConfigureMvc( + [NotNull] this IServiceCollection services, + [NotNull] Action setupAction) + { + services.Configure(setupAction); + } + + // To enable unit testing + internal static void AddMvcCoreServices(IServiceCollection services) + { + // Options + // + TryAddMultiRegistrationService( + services, + ServiceDescriptor.Transient, CoreMvcOptionsSetup>()); + + // Action Discovery + // + // These are consumed only when creating action descriptors, then they can be de-allocated + services.TryAdd(ServiceDescriptor.Transient()); + services.TryAdd(ServiceDescriptor.Transient()); ; + TryAddMultiRegistrationService( + services, + ServiceDescriptor.Transient()); + TryAddMultiRegistrationService( + services, + ServiceDescriptor.Transient()); + services.TryAdd(ServiceDescriptor + .Singleton()); + + // Action Selection + // + services.TryAdd(ServiceDescriptor.Singleton()); + // Performs caching + services.TryAdd(ServiceDescriptor + .Singleton()); + // This provider needs access to the per-request services, but might be used many times for a given + // request. + TryAddMultiRegistrationService( + services, + ServiceDescriptor.Transient()); + + // Action Invoker + // + // This has a cache, so it needs to be a singleton + services.TryAdd(ServiceDescriptor.Singleton()); + services.TryAdd(ServiceDescriptor.Transient()); + // This accesses per-request services + services.TryAdd(ServiceDescriptor.Transient()); + services.TryAdd(ServiceDescriptor + .Transient()); + TryAddMultiRegistrationService( + services, + ServiceDescriptor.Transient()); + TryAddMultiRegistrationService( + services, + ServiceDescriptor.Transient()); + TryAddMultiRegistrationService( + services, + ServiceDescriptor.Transient()); + + // ModelBinding, Validation and Formatting + // + // The DefaultModelMetadataProvider does significant caching and should be a singleton. + services.TryAdd(ServiceDescriptor.Singleton()); + services.TryAdd(ServiceDescriptor.Transient(serviceProvider => + { + var options = serviceProvider.GetRequiredService>().Options; + return new DefaultCompositeMetadataDetailsProvider(options.ModelMetadataDetailsProviders); + })); + services.TryAdd(ServiceDescriptor.Transient(serviceProvider => + { + var options = serviceProvider.GetRequiredService>().Options; + var modelMetadataProvider = serviceProvider.GetRequiredService(); + return new DefaultObjectValidator(options.ValidationExcludeFilters, modelMetadataProvider); + })); + + // Temp Data + // + services.TryAdd(ServiceDescriptor.Scoped()); + // This does caching so it should stay singleton + services.TryAdd(ServiceDescriptor.Singleton()); + + // Random Infrastructure + // + services.TryAdd(ServiceDescriptor.Transient()); + services.TryAdd((ServiceDescriptor.Singleton())); + services.TryAdd(ServiceDescriptor.Scoped(typeof(IScopedInstance<>), typeof(ScopedInstance<>))); + services.TryAdd(ServiceDescriptor.Scoped()); + } + + // Adds a service if the service type and implementation type hasn't been added yet. This is needed for + // services like IConfigureOptions or IApplicationModelProvider where you need the ability + // to register multiple implementation types for the same service type. + private static bool TryAddMultiRegistrationService(IServiceCollection services, ServiceDescriptor descriptor) + { + // This can't work when registering a factory or instance, you have to register a type. + // Additionally, if any existing registrations use a factory or instance, we can't check those, but we don't + // assert that because it might be added by user-code. + Debug.Assert(descriptor.ImplementationType != null); + + if (services.Any(d => + d.ServiceType == descriptor.ServiceType && + d.ImplementationType == descriptor.ImplementationType)) + { + return false; + } + + services.Add(descriptor); + return true; + } + + private static void ConfigureDefaultServices(IServiceCollection services) + { + services.AddOptions(); + services.AddRouting(); + services.AddCors(); + services.AddNotifier(); + services.Configure( + routeOptions => routeOptions.ConstraintMap.Add("exists", typeof(KnownRouteValueConstraint))); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc/MvcOptionsSetup.cs b/src/Microsoft.AspNet.Mvc/MvcOptionsSetup.cs index 1aa4ce26b9..67fb93912c 100644 --- a/src/Microsoft.AspNet.Mvc/MvcOptionsSetup.cs +++ b/src/Microsoft.AspNet.Mvc/MvcOptionsSetup.cs @@ -1,13 +1,10 @@ // 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.Xml.Linq; -using Microsoft.AspNet.Mvc.ModelBinding; using Microsoft.AspNet.Mvc.ModelBinding.Metadata; using Microsoft.AspNet.Mvc.ModelBinding.Validation; using Microsoft.Framework.OptionsModel; -using Microsoft.Net.Http.Headers; using Newtonsoft.Json.Linq; namespace Microsoft.AspNet.Mvc @@ -20,57 +17,18 @@ namespace Microsoft.AspNet.Mvc public MvcOptionsSetup() : base(ConfigureMvc) { - Order = DefaultOrder.DefaultFrameworkSortOrder; + Order = DefaultOrder.DefaultFrameworkSortOrder + 1; } public static void ConfigureMvc(MvcOptions options) { - // Set up ModelBinding - options.ModelBinders.Add(new BinderTypeBasedModelBinder()); - options.ModelBinders.Add(new ServicesModelBinder()); - options.ModelBinders.Add(new BodyModelBinder()); - options.ModelBinders.Add(new HeaderModelBinder()); - options.ModelBinders.Add(new TypeConverterModelBinder()); - options.ModelBinders.Add(new TypeMatchModelBinder()); - options.ModelBinders.Add(new CancellationTokenModelBinder()); - options.ModelBinders.Add(new ByteArrayModelBinder()); - options.ModelBinders.Add(new FormFileModelBinder()); - options.ModelBinders.Add(new FormCollectionModelBinder()); - options.ModelBinders.Add(new GenericModelBinder()); - options.ModelBinders.Add(new MutableObjectModelBinder()); - options.ModelBinders.Add(new ComplexModelDtoModelBinder()); - - // Set up default output formatters. - options.OutputFormatters.Add(new HttpNoContentOutputFormatter()); - options.OutputFormatters.Add(new StringOutputFormatter()); - options.OutputFormatters.Add(new StreamOutputFormatter()); - - // Set up ValueProviders - options.ValueProviderFactories.Add(new RouteValueValueProviderFactory()); - options.ValueProviderFactories.Add(new QueryStringValueProviderFactory()); - options.ValueProviderFactories.Add(new FormValueProviderFactory()); - - // Set up metadata providers - options.ModelMetadataDetailsProviders.Add(new DefaultBindingMetadataProvider()); - options.ModelMetadataDetailsProviders.Add(new DefaultValidationMetadataProvider()); options.ModelMetadataDetailsProviders.Add(new DataAnnotationsMetadataProvider()); options.ModelMetadataDetailsProviders.Add(new DataMemberRequiredBindingMetadataProvider()); - // Set up validators - options.ModelValidatorProviders.Add(new DefaultModelValidatorProvider()); options.ModelValidatorProviders.Add(new DataAnnotationsModelValidatorProvider()); - // Add types to be excluded from Validation - options.ValidationExcludeFilters.Add(new SimpleTypesExcludeFilter()); options.ValidationExcludeFilters.Add(typeof(XObject)); - options.ValidationExcludeFilters.Add(typeof(Type)); options.ValidationExcludeFilters.Add(typeof(JToken)); - - // Any 'known' types that we bind should be marked as excluded from validation. - options.ValidationExcludeFilters.Add(typeof(System.Threading.CancellationToken)); - options.ValidationExcludeFilters.Add(typeof(Http.IFormFile)); - options.ValidationExcludeFilters.Add(typeof(Http.IFormCollection)); - options.ValidationExcludeFilters.Add(typeFullName: "System.Xml.XmlNode"); } } diff --git a/src/Microsoft.AspNet.Mvc/MvcServiceCollectionExtensions.cs b/src/Microsoft.AspNet.Mvc/MvcServiceCollectionExtensions.cs index 117dc317b6..3ef0d3a0fc 100644 --- a/src/Microsoft.AspNet.Mvc/MvcServiceCollectionExtensions.cs +++ b/src/Microsoft.AspNet.Mvc/MvcServiceCollectionExtensions.cs @@ -7,22 +7,13 @@ using System.Diagnostics; using System.Linq; using System.Reflection; using Microsoft.AspNet.Mvc; -using Microsoft.AspNet.Mvc.ActionConstraints; using Microsoft.AspNet.Mvc.ApiExplorer; using Microsoft.AspNet.Mvc.ApplicationModels; -using Microsoft.AspNet.Mvc.Core; -using Microsoft.AspNet.Mvc.Filters; -using Microsoft.AspNet.Mvc.Internal; -using Microsoft.AspNet.Mvc.ModelBinding; -using Microsoft.AspNet.Mvc.ModelBinding.Metadata; -using Microsoft.AspNet.Mvc.ModelBinding.Validation; using Microsoft.AspNet.Mvc.Razor; using Microsoft.AspNet.Mvc.Razor.Compilation; using Microsoft.AspNet.Mvc.Razor.Directives; using Microsoft.AspNet.Mvc.Rendering; -using Microsoft.AspNet.Mvc.Routing; using Microsoft.AspNet.Mvc.ViewComponents; -using Microsoft.AspNet.Routing; using Microsoft.Framework.Caching.Memory; using Microsoft.Framework.Internal; using Microsoft.Framework.OptionsModel; @@ -33,6 +24,8 @@ namespace Microsoft.Framework.DependencyInjection { public static IServiceCollection AddMvc([NotNull] this IServiceCollection services) { + services.AddMinimalMvc(); + ConfigureDefaultServices(services); AddMvcServices(services); @@ -52,18 +45,6 @@ namespace Microsoft.Framework.DependencyInjection services.Configure(setupAction); } - /// - /// Configures a set of for the application. - /// - /// The services available in the application. - /// The which need to be configured. - public static void ConfigureMvc( - [NotNull] this IServiceCollection services, - [NotNull] Action setupAction) - { - services.Configure(setupAction); - } - /// /// Configures a set of for the application. /// @@ -182,86 +163,25 @@ namespace Microsoft.Framework.DependencyInjection ServiceDescriptor .Transient, RazorViewEngineOptionsSetup>()); - services.TryAdd(ServiceDescriptor.Transient()); - services.TryAdd((ServiceDescriptor.Singleton())); - services.TryAdd(ServiceDescriptor.Scoped(typeof(IScopedInstance<>), typeof(ScopedInstance<>))); - - // Core action discovery, filters and action execution. - - // This are consumed only when creating action descriptors, then they can be de-allocated - services.TryAdd(ServiceDescriptor.Transient()); - services.TryAdd(ServiceDescriptor.Transient()); ; - TryAddMultiRegistrationService( - services, - ServiceDescriptor.Transient()); + // Cors TryAddMultiRegistrationService( services, ServiceDescriptor.Transient()); + services.TryAdd(ServiceDescriptor.Transient()); + + // Auth TryAddMultiRegistrationService( services, ServiceDescriptor.Transient()); - // This has a cache, so it needs to be a singleton - services.TryAdd(ServiceDescriptor.Singleton()); - - services.TryAdd(ServiceDescriptor.Transient()); - - // This accesses per-request services - services.TryAdd(ServiceDescriptor.Transient()); - - // This provider needs access to the per-request services, but might be used many times for a given - // request. - TryAddMultiRegistrationService( - services, - ServiceDescriptor.Transient()); - - services.TryAdd(ServiceDescriptor - .Singleton()); - services.TryAdd(ServiceDescriptor.Singleton()); - services.TryAdd(ServiceDescriptor - .Transient()); - services.TryAdd(ServiceDescriptor.Transient(serviceProvider => - { - var options = serviceProvider.GetRequiredService>().Options; - var modelMetadataProvider = serviceProvider.GetRequiredService(); - return new DefaultObjectValidator(options.ValidationExcludeFilters, modelMetadataProvider); - })); - - TryAddMultiRegistrationService( - services, - ServiceDescriptor.Transient()); - - TryAddMultiRegistrationService( - services, - ServiceDescriptor.Transient()); - - services.TryAdd(ServiceDescriptor - .Singleton()); - - TryAddMultiRegistrationService( - services, - ServiceDescriptor.Transient()); - - TryAddMultiRegistrationService( - services, - ServiceDescriptor.Transient()); + // Support for activating ViewDataDictionary TryAddMultiRegistrationService( services, ServiceDescriptor .Transient()); + // Formatter Mappings services.TryAdd(ServiceDescriptor.Transient()); - services.TryAdd(ServiceDescriptor.Transient()); - - // Dataflow - ModelBinding, Validation and Formatting - // - // The DefaultModelMetadataProvider does significant caching and should be a singleton. - services.TryAdd(ServiceDescriptor.Singleton()); - services.TryAdd(ServiceDescriptor.Transient(serviceProvider => - { - var options = serviceProvider.GetRequiredService>().Options; - return new DefaultCompositeMetadataDetailsProvider(options.ModelMetadataDetailsProviders); - })); // JsonOutputFormatter should use the SerializerSettings on MvcOptions services.TryAdd(ServiceDescriptor.Singleton(serviceProvider => @@ -311,7 +231,6 @@ namespace Microsoft.Framework.DependencyInjection services.TryAdd(ServiceDescriptor.Transient()); services.TryAdd(ServiceDescriptor.Transient(typeof(IHtmlHelper<>), typeof(HtmlHelper<>))); services.TryAdd(ServiceDescriptor.Transient()); - services.TryAdd(ServiceDescriptor.Scoped()); // Only want one ITagHelperActivator so it can cache Type activation information. Types won't conflict. services.TryAdd(ServiceDescriptor.Singleton()); @@ -348,11 +267,6 @@ namespace Microsoft.Framework.DependencyInjection TryAddMultiRegistrationService( services, ServiceDescriptor.Transient()); - - // Temp Data - services.TryAdd(ServiceDescriptor.Scoped()); - // This does caching so it should stay singleton - services.TryAdd(ServiceDescriptor.Singleton()); } /// @@ -406,15 +320,10 @@ namespace Microsoft.Framework.DependencyInjection private static void ConfigureDefaultServices(IServiceCollection services) { - services.AddOptions(); services.AddDataProtection(); - services.AddRouting(); services.AddCors(); services.AddAuthorization(); services.AddWebEncoders(); - services.AddNotifier(); - services.Configure( - routeOptions => routeOptions.ConstraintMap.Add("exists", typeof(KnownRouteValueConstraint))); } } -} +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/MvcCoreServiceCollectionExtensionsTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/MvcCoreServiceCollectionExtensionsTest.cs new file mode 100644 index 0000000000..1d698e4de4 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.Core.Test/MvcCoreServiceCollectionExtensionsTest.cs @@ -0,0 +1,216 @@ +// 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.Linq; +using Microsoft.AspNet.Mvc.ActionConstraints; +using Microsoft.AspNet.Mvc.ApplicationModels; +using Microsoft.AspNet.Mvc.Core; +using Microsoft.AspNet.Mvc.Filters; +using Microsoft.Framework.DependencyInjection; +using Microsoft.Framework.OptionsModel; +using Moq; +using Xunit; + +namespace Microsoft.AspNet.Mvc +{ + public class MvcCoreServiceCollectionExtensionsTest + { + // Some MVC services can be registered multiple times, for example, 'IConfigureOptions' can + // be registered by calling 'ConfigureMvc(...)' before the call to 'AddMvc()' in which case the options + // configuration is run in the order they were registered. + // + // For these kind of multi registration service types, we want to make sure that MVC will still add its + // services if the implementation type is different. + [Fact] + public void MultiRegistrationServiceTypes_AreRegistered_MultipleTimes() + { + // Arrange + var services = new ServiceCollection(); + + // Register a mock implementation of each service, AddMvcServices should add another implemenetation. + foreach (var serviceType in MutliRegistrationServiceTypes) + { + var mockType = typeof(Mock<>).MakeGenericType(serviceType.Key); + services.Add(ServiceDescriptor.Transient(serviceType.Key, mockType)); + } + + // Act + MvcCoreServiceCollectionExtensions.AddMvcCoreServices(services); + + // Assert + foreach (var serviceType in MutliRegistrationServiceTypes) + { + AssertServiceCountEquals(services, serviceType.Key, serviceType.Value.Length + 1); + + foreach (var implementationType in serviceType.Value) + { + AssertContainsSingle(services, serviceType.Key, implementationType); + } + } + } + + [Fact] + public void SingleRegistrationServiceTypes_AreNotRegistered_MultipleTimes() + { + // Arrange + var services = new ServiceCollection(); + + // Register a mock implementation of each service, AddMvcServices should not replace it. + foreach (var serviceType in SingleRegistrationServiceTypes) + { + var mockType = typeof(Mock<>).MakeGenericType(serviceType); + services.Add(ServiceDescriptor.Transient(serviceType, mockType)); + } + + // Act + MvcCoreServiceCollectionExtensions.AddMvcCoreServices(services); + + // Assert + foreach (var singleRegistrationType in SingleRegistrationServiceTypes) + { + AssertServiceCountEquals(services, singleRegistrationType, 1); + } + } + + [Fact] + public void AddMvcServicesTwice_DoesNotAddDuplicates() + { + // Arrange + var services = new ServiceCollection(); + + // Act + MvcCoreServiceCollectionExtensions.AddMvcCoreServices(services); + MvcCoreServiceCollectionExtensions.AddMvcCoreServices(services); + + // Assert + var singleRegistrationServiceTypes = SingleRegistrationServiceTypes; + foreach (var service in services) + { + if (singleRegistrationServiceTypes.Contains(service.ServiceType)) + { + // 'single-registration' services should only have one implementation registered. + AssertServiceCountEquals(services, service.ServiceType, 1); + } + else + { + // 'multi-registration' services should only have one *instance* of each implementation registered. + AssertContainsSingle(services, service.ServiceType, service.ImplementationType); + } + } + } + + private IEnumerable SingleRegistrationServiceTypes + { + get + { + var services = new ServiceCollection(); + MvcCoreServiceCollectionExtensions.AddMvcCoreServices(services); + + var multiRegistrationServiceTypes = MutliRegistrationServiceTypes; + return services + .Where(sd => !multiRegistrationServiceTypes.Keys.Contains(sd.ServiceType)) + .Select(sd => sd.ServiceType); + } + } + + private Dictionary MutliRegistrationServiceTypes + { + get + { + return new Dictionary() + { + { + typeof(IConfigureOptions), + new Type[] + { + typeof(CoreMvcOptionsSetup), + } + }, + { + typeof(IActionConstraintProvider), + new Type[] + { + typeof(DefaultActionConstraintProvider), + } + }, + { + typeof(IActionDescriptorProvider), + new Type[] + { + typeof(ControllerActionDescriptorProvider), + } + }, + { + typeof(IActionInvokerProvider), + new Type[] + { + typeof(ControllerActionInvokerProvider), + } + }, + { + typeof(IFilterProvider), + new Type[] + { + typeof(DefaultFilterProvider), + } + }, + { + typeof(IControllerPropertyActivator), + new Type[] + { + typeof(DefaultControllerPropertyActivator), + } + }, + { + typeof(IApplicationModelProvider), + new Type[] + { + typeof(DefaultApplicationModelProvider), + } + }, + }; + } + } + + private void AssertServiceCountEquals( + IServiceCollection services, + Type serviceType, + int expectedServiceRegistrationCount) + { + var serviceDescriptors = services.Where(serviceDescriptor => serviceDescriptor.ServiceType == serviceType); + var actual = serviceDescriptors.Count(); + + Assert.True( + (expectedServiceRegistrationCount == actual), + $"Expected service type '{serviceType}' to be registered {expectedServiceRegistrationCount}" + + $" time(s) but was actually registered {actual} time(s)."); + } + + private void AssertContainsSingle( + IServiceCollection services, + Type serviceType, + Type implementationType) + { + var matches = services + .Where(sd => + sd.ServiceType == serviceType && + sd.ImplementationType == implementationType) + .ToArray(); + + if (matches.Length == 0) + { + Assert.True( + false, + $"Could not find an instance of {implementationType} registered as {serviceType}"); + } + else if (matches.Length > 1) + { + Assert.True( + false, + $"Found multiple instances of {implementationType} registered as {serviceType}"); + } + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.IntegrationTests/TestMvcOptions.cs b/test/Microsoft.AspNet.Mvc.IntegrationTests/TestMvcOptions.cs index 28e08e9328..c14efaa3c7 100644 --- a/test/Microsoft.AspNet.Mvc.IntegrationTests/TestMvcOptions.cs +++ b/test/Microsoft.AspNet.Mvc.IntegrationTests/TestMvcOptions.cs @@ -12,6 +12,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests public TestMvcOptions() { Options = new MvcOptions(); + CoreMvcOptionsSetup.ConfigureMvc(Options); MvcOptionsSetup.ConfigureMvc(Options); JsonMvcOptionsSetup.ConfigureMvc(Options, SerializerSettingsProvider.CreateSerializerSettings()); } diff --git a/test/Microsoft.AspNet.Mvc.Test/MvcOptionsSetupTest.cs b/test/Microsoft.AspNet.Mvc.Test/MvcOptionsSetupTest.cs index 7610687395..a8301cc0af 100644 --- a/test/Microsoft.AspNet.Mvc.Test/MvcOptionsSetupTest.cs +++ b/test/Microsoft.AspNet.Mvc.Test/MvcOptionsSetupTest.cs @@ -35,10 +35,12 @@ namespace Microsoft.AspNet.Mvc { // Arrange var mvcOptions = new MvcOptions(); - var setup = new MvcOptionsSetup(); + var setup1 = new CoreMvcOptionsSetup(); + var setup2 = new MvcOptionsSetup(); // Act - setup.Configure(mvcOptions); + setup1.Configure(mvcOptions); + setup2.Configure(mvcOptions); // Assert var i = 0; @@ -63,10 +65,12 @@ namespace Microsoft.AspNet.Mvc { // Arrange var mvcOptions = new MvcOptions(); - var setup = new MvcOptionsSetup(); + var setup1 = new CoreMvcOptionsSetup(); + var setup2 = new MvcOptionsSetup(); // Act - setup.Configure(mvcOptions); + setup1.Configure(mvcOptions); + setup2.Configure(mvcOptions); // Assert var valueProviders = mvcOptions.ValueProviderFactories; @@ -81,13 +85,15 @@ namespace Microsoft.AspNet.Mvc { // Arrange var mvcOptions = new MvcOptions(); - var setup1 = new MvcOptionsSetup(); - var setup2 = new JsonMvcOptionsSetup(new OptionsManager( + var setup1 = new CoreMvcOptionsSetup(); + var setup2 = new MvcOptionsSetup(); + var setup3 = new JsonMvcOptionsSetup(new OptionsManager( Enumerable.Empty>())); // Act setup1.Configure(mvcOptions); setup2.Configure(mvcOptions); + setup3.Configure(mvcOptions); // Assert Assert.Equal(4, mvcOptions.OutputFormatters.Count); @@ -102,13 +108,15 @@ namespace Microsoft.AspNet.Mvc { // Arrange var mvcOptions = new MvcOptions(); - var setup1 = new MvcOptionsSetup(); - var setup2 = new JsonMvcOptionsSetup(new OptionsManager( + var setup1 = new CoreMvcOptionsSetup(); + var setup2 = new MvcOptionsSetup(); + var setup3 = new JsonMvcOptionsSetup(new OptionsManager( Enumerable.Empty>())); // Act setup1.Configure(mvcOptions); setup2.Configure(mvcOptions); + setup3.Configure(mvcOptions); // Assert Assert.Equal(2, mvcOptions.InputFormatters.Count); @@ -121,10 +129,12 @@ namespace Microsoft.AspNet.Mvc { // Arrange var mvcOptions = new MvcOptions(); - var setup = new MvcOptionsSetup(); + var setup1 = new CoreMvcOptionsSetup(); + var setup2 = new MvcOptionsSetup(); // Act - setup.Configure(mvcOptions); + setup1.Configure(mvcOptions); + setup2.Configure(mvcOptions); // Assert Assert.Equal(2, mvcOptions.ModelValidatorProviders.Count); @@ -153,7 +163,7 @@ namespace Microsoft.AspNet.Mvc { // Arrange var mvcOptions = new MvcOptions(); - var setup = new MvcOptionsSetup(); + var setup = new CoreMvcOptionsSetup(); // Act setup.Configure(mvcOptions); @@ -167,10 +177,12 @@ namespace Microsoft.AspNet.Mvc { // Arrange var mvcOptions = new MvcOptions(); - var setup = new MvcOptionsSetup(); + var setup1 = new CoreMvcOptionsSetup(); + var setup2 = new MvcOptionsSetup(); // Act - setup.Configure(mvcOptions); + setup1.Configure(mvcOptions); + setup2.Configure(mvcOptions); // Assert Assert.Equal(8, mvcOptions.ValidationExcludeFilters.Count); @@ -178,21 +190,12 @@ namespace Microsoft.AspNet.Mvc // Verify if the delegates registered by default exclude the given types. Assert.IsType(typeof(SimpleTypesExcludeFilter), mvcOptions.ValidationExcludeFilters[i++]); - Assert.IsType(typeof(DefaultTypeBasedExcludeFilter), mvcOptions.ValidationExcludeFilters[i]); - var xObjectFilter - = Assert.IsType(mvcOptions.ValidationExcludeFilters[i++]); - Assert.Equal(xObjectFilter.ExcludedType, typeof(XObject)); Assert.IsType(typeof(DefaultTypeBasedExcludeFilter), mvcOptions.ValidationExcludeFilters[i]); var typeFilter = Assert.IsType(mvcOptions.ValidationExcludeFilters[i++]); Assert.Equal(typeFilter.ExcludedType, typeof(Type)); - Assert.IsType(typeof(DefaultTypeBasedExcludeFilter), mvcOptions.ValidationExcludeFilters[i]); - var jTokenFilter - = Assert.IsType(mvcOptions.ValidationExcludeFilters[i++]); - Assert.Equal(jTokenFilter.ExcludedType, typeof(JToken)); - Assert.IsType(typeof(DefaultTypeBasedExcludeFilter), mvcOptions.ValidationExcludeFilters[i]); var cancellationTokenFilter = Assert.IsType(mvcOptions.ValidationExcludeFilters[i++]); @@ -210,6 +213,16 @@ namespace Microsoft.AspNet.Mvc = Assert.IsType(mvcOptions.ValidationExcludeFilters[i++]); Assert.Equal(formCollectionFilter.ExcludedType, typeof(Http.IFormCollection)); + Assert.IsType(typeof(DefaultTypeBasedExcludeFilter), mvcOptions.ValidationExcludeFilters[i]); + var xObjectFilter + = Assert.IsType(mvcOptions.ValidationExcludeFilters[i++]); + Assert.Equal(xObjectFilter.ExcludedType, typeof(XObject)); + + Assert.IsType(typeof(DefaultTypeBasedExcludeFilter), mvcOptions.ValidationExcludeFilters[i]); + var jTokenFilter + = Assert.IsType(mvcOptions.ValidationExcludeFilters[i++]); + Assert.Equal(jTokenFilter.ExcludedType, typeof(JToken)); + Assert.IsType(typeof(DefaultTypeNameBasedExcludeFilter), mvcOptions.ValidationExcludeFilters[i]); var xmlNodeFilter = Assert.IsType(mvcOptions.ValidationExcludeFilters[i++]); diff --git a/test/Microsoft.AspNet.Mvc.Test/MvcServiceCollectionExtensionsTest.cs b/test/Microsoft.AspNet.Mvc.Test/MvcServiceCollectionExtensionsTest.cs index cdfc623e65..8fd50591ae 100644 --- a/test/Microsoft.AspNet.Mvc.Test/MvcServiceCollectionExtensionsTest.cs +++ b/test/Microsoft.AspNet.Mvc.Test/MvcServiceCollectionExtensionsTest.cs @@ -219,34 +219,6 @@ namespace Microsoft.AspNet.Mvc typeof(RazorViewEngineOptionsSetup), } }, - { - typeof(IActionConstraintProvider), - new Type[] - { - typeof(DefaultActionConstraintProvider), - } - }, - { - typeof(IActionDescriptorProvider), - new Type[] - { - typeof(ControllerActionDescriptorProvider), - } - }, - { - typeof(IActionInvokerProvider), - new Type[] - { - typeof(ControllerActionInvokerProvider), - } - }, - { - typeof(IFilterProvider), - new Type[] - { - typeof(DefaultFilterProvider), - } - }, { typeof(IApiDescriptionProvider), new Type[] @@ -258,7 +230,6 @@ namespace Microsoft.AspNet.Mvc typeof(IControllerPropertyActivator), new Type[] { - typeof(DefaultControllerPropertyActivator), typeof(ViewDataDictionaryControllerPropertyActivator), } }, @@ -266,7 +237,6 @@ namespace Microsoft.AspNet.Mvc typeof(IApplicationModelProvider), new Type[] { - typeof(DefaultApplicationModelProvider), typeof(CorsApplicationModelProvider), typeof(AuthorizationApplicationModelProvider), }