Update to CR feedback

This commit is contained in:
Yishai Galatzer 2014-02-10 13:29:46 -08:00
parent a725e4c9b5
commit 400240673b
13 changed files with 102 additions and 293 deletions

View File

@ -1,8 +1,5 @@
using Microsoft.AspNet.Mvc;
<<<<<<< HEAD
using MvcSample.Models;
=======
>>>>>>> Support per process caching of controller discovery
namespace MvcSample
{

View File

@ -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<MvcHandler>(mvcServices.Services);
builder.Run(async context =>
{

View File

@ -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<IControllerFactory, DefaultControllerFactory>();
AddAndRegisterForFinalization<IActionInvokerFactory, ActionInvokerFactory>();
AddAndRegisterForFinalization<IActionResultHelper, ActionResultHelper>();
AddAndRegisterForFinalization<IActionResultFactory, ActionResultFactory>();
AddAndRegisterForFinalization<IRouteContextProvider, ControllerActionBasedRouteContextProvider>();
AddAndRegisterForFinalization<IActionInvokerProvider, ActionInvokerProvider>();
Add<IControllerFactory, DefaultControllerFactory>();
Add<IActionInvokerFactory, ActionInvokerFactory>();
Add<IActionResultHelper, ActionResultHelper>();
Add<IActionResultFactory, ActionResultFactory>();
Add<IRouteContextProvider, ControllerActionBasedRouteContextProvider>();
Add<IActionInvokerProvider, ActionInvokerProvider>();
// need singleton support here.
// AddAndRegisterForFinalization<SkipAssemblies, DefaultSkipAssemblies>();
AddInstanceAndRegisterForFinalization<ControllerCache>(new DefaultControllerCache(new DefaultSkipAssemblies()));
AddInstanceAndRegisterForFinalization<IFileSystem>(new PhysicalFileSystem(appRoot));
AddInstanceAndRegisterForFinalization<IMvcRazorHost>(new MvcRazorHost("Microsoft.AspNet.Mvc.Razor.RazorView<dynamic>"));
// need a design for immutable caches at startup
var provider = new DefaultControllerDescriptorProvider(new AppDomainControllerAssemblyProvider());
provider.FinalizeSetup();
AddInstance<IControllerDescriptorProvider>(provider);
AddInstance<IFileSystem>(new PhysicalFileSystem(appRoot));
AddInstance<IMvcRazorHost>(new MvcRazorHost("Microsoft.AspNet.Mvc.Razor.RazorView<dynamic>"));
#if NET45
AddAndRegisterForFinalization<ICompilationService, CscBasedCompilationService>();
Add<ICompilationService, CscBasedCompilationService>();
#endif
AddAndRegisterForFinalization<IRazorCompilationService, RazorCompilationService>();
AddAndRegisterForFinalization<IVirtualPathViewFactory, VirtualPathViewFactory>();
AddAndRegisterForFinalization<IViewEngine, RazorViewEngine>();
Add<IRazorCompilationService, RazorCompilationService>();
Add<IVirtualPathViewFactory, VirtualPathViewFactory>();
Add<IViewEngine, RazorViewEngine>();
}
public void AddAndRegisterForFinalization<T, U>() where U : T
private void Add<T, U>() where U : T
{
Services.Add<T, U>();
#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<T>(object instance)
private void AddInstance<T>(object instance)
{
Services.AddInstance<T>(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<IFinalizeSetup>())
{
service.FinalizeSetup();
}
}
}
}
_typesToFinalize = null;
}
}
}
}

View File

@ -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<Assembly> 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;
}
}
}

View File

@ -0,0 +1,10 @@
using System.Collections.Generic;
using System.Reflection;
namespace Microsoft.AspNet.Mvc
{
public interface ControllerAssemblyProvider
{
IEnumerable<Assembly> Assemblies { get; }
}
}

View File

@ -1,8 +0,0 @@
using System.Collections.Generic;
namespace Microsoft.AspNet.Mvc
{
public abstract class ControllerCache
{
public abstract IEnumerable<ControllerDescriptor> GetController(string controllerName);
}
}

View File

@ -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<string, IEnumerable<ControllerDescriptor>> 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<ControllerDescriptor> GetController(string controllerName)
public IEnumerable<ControllerDescriptor> 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<ControllerDescriptor>();
}
public Dictionary<string, IEnumerable<ControllerDescriptor>> ScanAppDomain()
{
var dictionary = new Dictionary<string, IEnumerable<ControllerDescriptor>>(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);
}
}
}

View File

@ -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)
{
}
}
}

View File

@ -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<string> _hash = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
public DefaultSkipAssemblies(IEnumerable<string> 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<string> 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;
}
}
}

View File

@ -0,0 +1,9 @@
using System.Collections.Generic;
namespace Microsoft.AspNet.Mvc
{
public interface IControllerDescriptorProvider
{
IEnumerable<ControllerDescriptor> GetControllers(string controllerName);
}
}

View File

@ -1,7 +0,0 @@
namespace Microsoft.AspNet.Mvc
{
public interface IFinalizeSetup
{
void FinalizeSetup();
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}
}