diff --git a/samples/MvcSample/HomeController.cs b/samples/MvcSample/HomeController.cs index 96ca422e06..db8481ef11 100644 --- a/samples/MvcSample/HomeController.cs +++ b/samples/MvcSample/HomeController.cs @@ -1,8 +1,5 @@ using Microsoft.AspNet.Mvc; -<<<<<<< HEAD using MvcSample.Models; -======= ->>>>>>> Support per process caching of controller discovery namespace MvcSample { diff --git a/samples/MvcSample/Startup.cs b/samples/MvcSample/Startup.cs index 341bdda3cd..c92183b422 100644 --- a/samples/MvcSample/Startup.cs +++ b/samples/MvcSample/Startup.cs @@ -27,9 +27,7 @@ namespace MvcSample var mvcServices = new MvcServices(appRoot); - mvcServices.Finalize(); - - var handler = (MvcHandler)(ActivatorUtilities.CreateInstance(mvcServices.Services, typeof(MvcHandler))); + var handler = ActivatorUtilities.CreateInstance(mvcServices.Services); builder.Run(async context => { diff --git a/src/Microsoft.AspNet.Mvc.Startup/MvcServices.cs b/src/Microsoft.AspNet.Mvc.Startup/MvcServices.cs index 9fa0d2e7e9..01e69cdef5 100644 --- a/src/Microsoft.AspNet.Mvc.Startup/MvcServices.cs +++ b/src/Microsoft.AspNet.Mvc.Startup/MvcServices.cs @@ -1,8 +1,5 @@ using System; -using System.Collections; using System.Collections.Generic; -using System.Linq; -using System.Reflection; using Microsoft.AspNet.DependencyInjection; using Microsoft.AspNet.FileSystems; using Microsoft.AspNet.Mvc.Razor; @@ -21,92 +18,38 @@ namespace Microsoft.AspNet.Mvc.Startup { Services = new ServiceProvider(); - AddAndRegisterForFinalization(); - AddAndRegisterForFinalization(); - AddAndRegisterForFinalization(); - AddAndRegisterForFinalization(); - AddAndRegisterForFinalization(); - AddAndRegisterForFinalization(); + Add(); + Add(); + Add(); + Add(); + Add(); + Add(); // need singleton support here. - // AddAndRegisterForFinalization(); - AddInstanceAndRegisterForFinalization(new DefaultControllerCache(new DefaultSkipAssemblies())); - AddInstanceAndRegisterForFinalization(new PhysicalFileSystem(appRoot)); - AddInstanceAndRegisterForFinalization(new MvcRazorHost("Microsoft.AspNet.Mvc.Razor.RazorView")); + // need a design for immutable caches at startup + var provider = new DefaultControllerDescriptorProvider(new AppDomainControllerAssemblyProvider()); + provider.FinalizeSetup(); + + AddInstance(provider); + AddInstance(new PhysicalFileSystem(appRoot)); + AddInstance(new MvcRazorHost("Microsoft.AspNet.Mvc.Razor.RazorView")); #if NET45 - AddAndRegisterForFinalization(); + Add(); #endif - AddAndRegisterForFinalization(); - AddAndRegisterForFinalization(); - AddAndRegisterForFinalization(); + Add(); + Add(); + Add(); } - public void AddAndRegisterForFinalization() where U : T + private void Add() where U : T { Services.Add(); -#if NET45 - if (typeof(IFinalizeSetup).IsAssignableFrom(typeof(U))) -#else - if (typeof(IFinalizeSetup).GetTypeInfo().IsAssignableFrom(typeof(U).GetTypeInfo())) -#endif - { - _typesToFinalize.Add(typeof(T)); - } } - public void AddInstanceAndRegisterForFinalization(object instance) + private void AddInstance(object instance) { Services.AddInstance(instance); - - if ((instance as IFinalizeSetup) != null) - { - _typesToFinalize.Add(typeof(T)); - } - } - - public void Finalize() - { - if (_typesToFinalize == null) - { - return; - } - - // We want to lock around here so finalization happens just once. - // This is not a code intended to be used during request, so the lock is just a safety precaution. - lock (_lock) - { - if (_typesToFinalize == null) - { - return; - } - - foreach (var markerType in _typesToFinalize) - { - var services = this.Services.GetService(markerType); - - var serviceToFinalize = services as IFinalizeSetup; - - if (serviceToFinalize != null) - { - serviceToFinalize.FinalizeSetup(); - } - else - { - var setOfServices = services as IEnumerable; - - if (setOfServices != null) - { - foreach (var service in setOfServices.OfType()) - { - service.FinalizeSetup(); - } - } - } - } - - _typesToFinalize = null; - } } } } diff --git a/src/Microsoft.AspNet.Mvc/AppDomainControllerAssemblyProvider.cs b/src/Microsoft.AspNet.Mvc/AppDomainControllerAssemblyProvider.cs new file mode 100644 index 0000000000..d80c9e246a --- /dev/null +++ b/src/Microsoft.AspNet.Mvc/AppDomainControllerAssemblyProvider.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace Microsoft.AspNet.Mvc +{ + public class AppDomainControllerAssemblyProvider : ControllerAssemblyProvider + { + public IEnumerable Assemblies + { + get + { + foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies().Where(AllowAssembly)) + { + yield return assembly; + } + } + } + + private bool AllowAssembly(Assembly assembly) + { + // consider mechanisms to filter assemblies upfront, so scanning cost is minimized and startup improved. + // 1 - Does assembly reference the WebFx assembly (directly or indirectly). - Down side, object only controller not supported. + // 2 - Remove well known assemblies (maintenance and composability cost) + return true; + } + } +} diff --git a/src/Microsoft.AspNet.Mvc/ControllerAssemblyProvider.cs b/src/Microsoft.AspNet.Mvc/ControllerAssemblyProvider.cs new file mode 100644 index 0000000000..7cbf5469b7 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc/ControllerAssemblyProvider.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; +using System.Reflection; + +namespace Microsoft.AspNet.Mvc +{ + public interface ControllerAssemblyProvider + { + IEnumerable Assemblies { get; } + } +} diff --git a/src/Microsoft.AspNet.Mvc/ControllerCache.cs b/src/Microsoft.AspNet.Mvc/ControllerCache.cs deleted file mode 100644 index a348f452a9..0000000000 --- a/src/Microsoft.AspNet.Mvc/ControllerCache.cs +++ /dev/null @@ -1,8 +0,0 @@ -using System.Collections.Generic; -namespace Microsoft.AspNet.Mvc -{ - public abstract class ControllerCache - { - public abstract IEnumerable GetController(string controllerName); - } -} diff --git a/src/Microsoft.AspNet.Mvc/DefaultControllerCache.cs b/src/Microsoft.AspNet.Mvc/DefaultControllerDescriptorProvider.cs similarity index 66% rename from src/Microsoft.AspNet.Mvc/DefaultControllerCache.cs rename to src/Microsoft.AspNet.Mvc/DefaultControllerDescriptorProvider.cs index df01542db5..14ac3c021e 100644 --- a/src/Microsoft.AspNet.Mvc/DefaultControllerCache.cs +++ b/src/Microsoft.AspNet.Mvc/DefaultControllerDescriptorProvider.cs @@ -5,24 +5,34 @@ using System.Reflection; namespace Microsoft.AspNet.Mvc { - public class DefaultControllerCache : ControllerCache, IFinalizeSetup + public class DefaultControllerDescriptorProvider : IControllerDescriptorProvider { - private readonly SkipAssemblies _skipAssemblies; + private readonly ControllerAssemblyProvider _controllerAssemblyProvider; public IReadOnlyDictionary> Controllers { get; protected set; } - public virtual void FinalizeSetup() + public void FinalizeSetup() { Controllers = ScanAppDomain(); } - public DefaultControllerCache(SkipAssemblies skipAssemblies) + public DefaultControllerDescriptorProvider(ControllerAssemblyProvider controllerAssemblyProvider) { - _skipAssemblies = skipAssemblies ?? new SkipNoAssemblies(); + if (controllerAssemblyProvider == null) + { + throw new ArgumentNullException("controllerAssemblyProvider"); + } + + _controllerAssemblyProvider = controllerAssemblyProvider; } - public override IEnumerable GetController(string controllerName) + public IEnumerable GetControllers(string controllerName) { + if (!controllerName.EndsWith("Controller", StringComparison.OrdinalIgnoreCase)) + { + controllerName += "Controller"; + } + if (Controllers == null) { throw new InvalidOperationException("Finalizing the setup must happen prior to accessing controllers"); @@ -35,14 +45,14 @@ namespace Microsoft.AspNet.Mvc return descriptors; } - return null; + return Enumerable.Empty(); } public Dictionary> ScanAppDomain() { var dictionary = new Dictionary>(StringComparer.Ordinal); - foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies().Where(AllowAssembly)) + foreach (var assembly in _controllerAssemblyProvider.Assemblies) { foreach (var type in assembly.DefinedTypes.Where(IsController).Select(info => info.AsType())) { @@ -70,17 +80,12 @@ namespace Microsoft.AspNet.Mvc } bool validController = typeInfo.IsClass && - !typeInfo.IsAbstract && - !typeInfo.ContainsGenericParameters; + !typeInfo.IsAbstract && + !typeInfo.ContainsGenericParameters; validController = validController && typeInfo.Name.EndsWith("Controller", StringComparison.OrdinalIgnoreCase); return validController; } - - private bool AllowAssembly(Assembly assembly) - { - return !_skipAssemblies.Skip(assembly, SkipAssemblies.ControllerDiscoveryScope); - } } } diff --git a/src/Microsoft.AspNet.Mvc/DefaultControllerFactory.cs b/src/Microsoft.AspNet.Mvc/DefaultControllerFactory.cs index 16a248a63e..591b489712 100644 --- a/src/Microsoft.AspNet.Mvc/DefaultControllerFactory.cs +++ b/src/Microsoft.AspNet.Mvc/DefaultControllerFactory.cs @@ -9,35 +9,33 @@ namespace Microsoft.AspNet.Mvc public class DefaultControllerFactory : IControllerFactory { private readonly IServiceProvider _serviceProvider; - private readonly ControllerCache _controllerCache; + private readonly IControllerDescriptorProvider _controllerDescriptorProvider; - public DefaultControllerFactory(IServiceProvider serviceProvider, ControllerCache cache) + public DefaultControllerFactory(IServiceProvider serviceProvider, IControllerDescriptorProvider controllerDescriptorProvider) { _serviceProvider = serviceProvider; - _controllerCache = cache; + _controllerDescriptorProvider = controllerDescriptorProvider; } public object CreateController(HttpContext context, string controllerName) - { - if (!controllerName.EndsWith("Controller", StringComparison.OrdinalIgnoreCase)) - { - controllerName += "Controller"; - } - - var controllers = _controllerCache.GetController(controllerName); + { + var controllers = _controllerDescriptorProvider.GetControllers(controllerName); if (controllers != null) { try { - var type = controllers.Single().ControllerType; + var descriptor = controllers.SingleOrDefault(); - try - { - return ActivatorUtilities.CreateInstance(_serviceProvider, type); - } - catch (ReflectionTypeLoadException) + if (descriptor != null) { + try + { + return ActivatorUtilities.CreateInstance(_serviceProvider, descriptor.ControllerType); + } + catch (ReflectionTypeLoadException) + { + } } } catch (InvalidOperationException) @@ -51,7 +49,6 @@ namespace Microsoft.AspNet.Mvc public void ReleaseController(object controller) { - } } } diff --git a/src/Microsoft.AspNet.Mvc/DefaultSkipAssemblies.cs b/src/Microsoft.AspNet.Mvc/DefaultSkipAssemblies.cs deleted file mode 100644 index 65c31ff05f..0000000000 --- a/src/Microsoft.AspNet.Mvc/DefaultSkipAssemblies.cs +++ /dev/null @@ -1,141 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Reflection; - -namespace Microsoft.AspNet.Mvc -{ - public class DefaultSkipAssemblies : SkipAssemblies - { - private HashSet _hash = new HashSet(StringComparer.OrdinalIgnoreCase); - - public DefaultSkipAssemblies(IEnumerable assemblyNames) - { - InitializeHash(assemblyNames); - } - - public DefaultSkipAssemblies() - { -#if NET45 - InitializeHash(@" -klr.net45.managed -Microsoft.Net.Runtime.Interfaces -klr.host -System -System.Core -System.Configuration -System.Xml -Microsoft.Net.ApplicationHost -Microsoft.Net.Runtime -Newtonsoft.Json -System.Numerics -System.ComponentModel.DataAnnotations -System.Runtime.Serialization -System.Xml.Linq -System.Data -Microsoft.CodeAnalysis -System.Collections.Immutable -System.Runtime -Microsoft.CodeAnalysis.CSharp -System.IO.Compression -Microsoft.AspNet.FileSystems -Microsoft.AspNet.Abstractions -Microsoft.AspNet.DependencyInjection -Microsoft.AspNet.Razor -Newtonsoft.Json -System.Linq -System.Collections -System.Runtime.Extensions -System.Threading -System.Reflection.Metadata.Ecma335 -Microsoft.AspNet.Mvc.ModelBinding -Microsoft.AspNet.Mvc.Rendering -Microsoft.AspNet.Mvc -Microsoft.AspNet.Mvc.Razor.Host -Microsoft.AspNet.Mvc.Razor -Microsoft.AspNet.Mvc.Startup -Owin -Microsoft.Owin -Microsoft.Owin.Diagnostics -Microsoft.Owin.Hosting -Microsoft.Owin.Host.HttpListener -Microsoft.AspNet.AppBuilderSupport -Anonymously Hosted DynamicMethods Assembly -Microsoft.AspNet.PipelineCore -Microsoft.AspNet.FeatureModel -mscorlib -klr.net45.managed -Microsoft.Net.Runtime.Interfaces -klr.host -System -System.Core -System.Configuration -System.Xml -Microsoft.Net.ApplicationHost -Microsoft.Net.Runtime -Newtonsoft.Json -System.Numerics -System.ComponentModel.DataAnnotations -System.Runtime.Serialization -System.Xml.Linq -System.Data -Microsoft.CodeAnalysis -System.Collections.Immutable -System.Runtime -Microsoft.CodeAnalysis.CSharp -System.IO.Compression -Microsoft.AspNet.FileSystems -Microsoft.AspNet.Abstractions -Microsoft.AspNet.DependencyInjection -Microsoft.AspNet.Razor -Newtonsoft.Json -System.Linq -System.Collections -System.Runtime.Extensions -System.Threading -System.Reflection.Metadata.Ecma335 -Microsoft.AspNet.Mvc.ModelBinding -Microsoft.AspNet.Mvc.Rendering -Microsoft.AspNet.Mvc -Microsoft.AspNet.Mvc.Razor.Host -Microsoft.AspNet.Mvc.Razor -Microsoft.AspNet.Mvc.Startup -Owin -Microsoft.Owin -Microsoft.Owin.Diag".Split(new char[] { '\r', '\n'}, StringSplitOptions.RemoveEmptyEntries)); -#else -#endif - } - - private void InitializeHash(IEnumerable assemblyNames) - { - if (assemblyNames == null) - { - throw new ArgumentNullException("assemblyNames"); - } - - foreach (var assemblyName in assemblyNames) - { - if (!string.IsNullOrWhiteSpace(assemblyName)) - { - _hash.Add(assemblyName); - } - } - } - - public override bool Skip(Assembly assembly, string scope) - { - if (scope == null || - !string.Equals(scope, SkipAssemblies.ControllerDiscoveryScope, StringComparison.Ordinal)) - { - return false; - } - - string name = assembly.GetName().Name; - - bool contains = _hash.Contains(name); - - return contains; - } - } -} diff --git a/src/Microsoft.AspNet.Mvc/IControllerDescriptorProvider.cs b/src/Microsoft.AspNet.Mvc/IControllerDescriptorProvider.cs new file mode 100644 index 0000000000..3139e14c56 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc/IControllerDescriptorProvider.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; + +namespace Microsoft.AspNet.Mvc +{ + public interface IControllerDescriptorProvider + { + IEnumerable GetControllers(string controllerName); + } +} diff --git a/src/Microsoft.AspNet.Mvc/IFinalizeSetup.cs b/src/Microsoft.AspNet.Mvc/IFinalizeSetup.cs deleted file mode 100644 index d67e78f8df..0000000000 --- a/src/Microsoft.AspNet.Mvc/IFinalizeSetup.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Microsoft.AspNet.Mvc -{ - public interface IFinalizeSetup - { - void FinalizeSetup(); - } -} diff --git a/src/Microsoft.AspNet.Mvc/SkipAssemblies.cs b/src/Microsoft.AspNet.Mvc/SkipAssemblies.cs deleted file mode 100644 index 7bd2b7d27f..0000000000 --- a/src/Microsoft.AspNet.Mvc/SkipAssemblies.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System.Reflection; - -namespace Microsoft.AspNet.Mvc -{ - public abstract class SkipAssemblies - { - public static readonly string ControllerDiscoveryScope = "DCS"; - - public abstract bool Skip(Assembly assembly, string scope); - } -} diff --git a/src/Microsoft.AspNet.Mvc/SkipNoAssemblies.cs b/src/Microsoft.AspNet.Mvc/SkipNoAssemblies.cs deleted file mode 100644 index bc30222f1e..0000000000 --- a/src/Microsoft.AspNet.Mvc/SkipNoAssemblies.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Reflection; - -namespace Microsoft.AspNet.Mvc -{ - public class SkipNoAssemblies : SkipAssemblies - { - public override bool Skip(Assembly assembly, string scope) - { - return false; - } - } -}