Remove IAssemblyProvider and update DNX to work with application parts.

* Remove IAssemblyProvider.
* Remove DefaultAssemblyProvider in favor of DefaultAssemblyPartDiscoveryProvider.
* Update AddMvcDnx to add the list of DNX discovered assemblies to the list of application parts.
This commit is contained in:
jacalvar 2016-03-31 11:04:07 -07:00
parent 5246125cb7
commit f638c051fa
9 changed files with 141 additions and 288 deletions

View File

@ -74,10 +74,10 @@ namespace Microsoft.Extensions.DependencyInjection
return manager;
}
var assemblies = new DefaultAssemblyProvider(environment).CandidateAssemblies;
foreach (var assembly in assemblies)
var parts = DefaultAssemblyPartDiscoveryProvider.DiscoverAssemblyParts(environment.ApplicationName);
foreach (var part in parts)
{
manager.ApplicationParts.Add(new AssemblyPart(assembly));
manager.ApplicationParts.Add(part);
}
}
@ -132,7 +132,6 @@ namespace Microsoft.Extensions.DependencyInjection
// Action Discovery
//
// These are consumed only when creating action descriptors, then they can be de-allocated
services.TryAddTransient<IAssemblyProvider, DefaultAssemblyProvider>();
services.TryAddEnumerable(
ServiceDescriptor.Transient<IApplicationModelProvider, DefaultApplicationModelProvider>());

View File

@ -1,125 +0,0 @@
// 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.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyModel;
namespace Microsoft.AspNetCore.Mvc.Infrastructure
{
/// <summary>
/// An <see cref="IAssemblyProvider"/> that uses <see cref="DependencyContext"/> to discover assemblies that may
/// contain Mvc specific types such as controllers, and view components.
/// </summary>
public class DefaultAssemblyProvider : IAssemblyProvider
{
private const string NativeImageSufix = ".ni";
private readonly Assembly _entryAssembly;
private readonly DependencyContext _dependencyContext;
/// <summary>
/// Initializes a new instance of <see cref="DefaultAssemblyProvider"/>.
/// </summary>
/// <param name="environment">The <see cref="IHostingEnvironment"/>.</param>
public DefaultAssemblyProvider(IHostingEnvironment environment)
: this(
Assembly.Load(new AssemblyName(environment.ApplicationName)),
DependencyContext.Load(Assembly.Load(new AssemblyName(environment.ApplicationName))))
{
}
// Internal for unit testing.
internal DefaultAssemblyProvider(Assembly entryAssembly, DependencyContext dependencyContext)
{
_entryAssembly = entryAssembly;
_dependencyContext = dependencyContext;
}
/// <summary>
/// Gets the set of assembly names that are used as root for discovery of
/// MVC controllers, view components and views.
/// </summary>
// DefaultControllerTypeProvider uses CandidateAssemblies to determine if the base type of a POCO controller
// lives in an assembly that references MVC. CandidateAssemblies excludes all assemblies from the
// ReferenceAssemblies set. Consequently adding WebApiCompatShim to this set would cause the ApiController to
// fail this test.
protected virtual HashSet<string> ReferenceAssemblies { get; } = new HashSet<string>(StringComparer.Ordinal)
{
"Microsoft.AspNetCore.Mvc",
"Microsoft.AspNetCore.Mvc.Abstractions",
"Microsoft.AspNetCore.Mvc.ApiExplorer",
"Microsoft.AspNetCore.Mvc.Core",
"Microsoft.AspNetCore.Mvc.Cors",
"Microsoft.AspNetCore.Mvc.DataAnnotations",
"Microsoft.AspNetCore.Mvc.Formatters.Json",
"Microsoft.AspNetCore.Mvc.Formatters.Xml",
"Microsoft.AspNetCore.Mvc.Localization",
"Microsoft.AspNetCore.Mvc.Razor",
"Microsoft.AspNetCore.Mvc.Razor.Host",
"Microsoft.AspNetCore.Mvc.TagHelpers",
"Microsoft.AspNetCore.Mvc.ViewFeatures"
};
/// <inheritdoc />
public IEnumerable<Assembly> CandidateAssemblies
{
get
{
if (_dependencyContext == null)
{
// Use the entry assembly as the sole candidate.
return new[] { _entryAssembly };
}
return GetCandidateLibraries()
.SelectMany(library => library.RuntimeAssemblyGroups.GetDefaultGroup().AssetPaths)
.Select(Load)
.Where(assembly => assembly != null);
}
}
/// <summary>
/// Returns a list of libraries that references the assemblies in <see cref="ReferenceAssemblies"/>.
/// By default it returns all assemblies that reference any of the primary MVC assemblies
/// while ignoring MVC assemblies.
/// </summary>
/// <returns>A set of <see cref="Library"/>.</returns>
// Internal for unit testing
protected internal virtual IEnumerable<RuntimeLibrary> GetCandidateLibraries()
{
if (ReferenceAssemblies == null)
{
return Enumerable.Empty<RuntimeLibrary>();
}
return _dependencyContext.RuntimeLibraries.Where(IsCandidateLibrary);
}
private static Assembly Load(string assetPath)
{
var name = Path.GetFileNameWithoutExtension(assetPath);
if (name != null)
{
if (name.EndsWith(NativeImageSufix, StringComparison.OrdinalIgnoreCase))
{
name = name.Substring(0, name.Length - NativeImageSufix.Length);
}
return Assembly.Load(new AssemblyName(name));
}
return null;
}
private bool IsCandidateLibrary(RuntimeLibrary library)
{
Debug.Assert(ReferenceAssemblies != null);
return library.Dependencies.Any(dependency => ReferenceAssemblies.Contains(dependency.Name));
}
}
}

View File

@ -1,21 +0,0 @@
// 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.Collections.Generic;
using System.Reflection;
namespace Microsoft.AspNetCore.Mvc.Infrastructure
{
/// <summary>
/// Specifies the contract for discovering assemblies that may contain Mvc specific types such as controllers,
/// view components and precompiled views.
/// </summary>
public interface IAssemblyProvider
{
/// <summary>
/// Gets the sequence of candidate <see cref="Assembly"/> instances that the application
/// uses for discovery of Mvc specific types.
/// </summary>
IEnumerable<Assembly> CandidateAssemblies { get; }
}
}

View File

@ -1,21 +0,0 @@
// 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.Collections.Generic;
using System.Reflection;
namespace Microsoft.AspNetCore.Mvc.Infrastructure
{
/// <summary>
/// A <see cref="IAssemblyProvider"/> with a fixed set of candidate assemblies.
/// </summary>
public class StaticAssemblyProvider : IAssemblyProvider
{
/// <summary>
/// Gets the list of candidate assemblies.
/// </summary>
public IList<Assembly> CandidateAssemblies { get; } = new List<Assembly>();
IEnumerable<Assembly> IAssemblyProvider.CandidateAssemblies => CandidateAssemblies;
}
}

View File

@ -0,0 +1,95 @@
// 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.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using Microsoft.AspNetCore.Mvc.ApplicationParts;
using Microsoft.Extensions.DependencyModel;
namespace Microsoft.AspNetCore.Mvc.Internal
{
// Discovers assemblies that are part of the MVC application using the DependencyContext.
public static class DefaultAssemblyPartDiscoveryProvider
{
private const string NativeImageSufix = ".ni";
internal static HashSet<string> ReferenceAssemblies { get; } = new HashSet<string>(StringComparer.Ordinal)
{
"Microsoft.AspNetCore.Mvc",
"Microsoft.AspNetCore.Mvc.Abstractions",
"Microsoft.AspNetCore.Mvc.ApiExplorer",
"Microsoft.AspNetCore.Mvc.Core",
"Microsoft.AspNetCore.Mvc.Cors",
"Microsoft.AspNetCore.Mvc.DataAnnotations",
"Microsoft.AspNetCore.Mvc.Formatters.Json",
"Microsoft.AspNetCore.Mvc.Formatters.Xml",
"Microsoft.AspNetCore.Mvc.Localization",
"Microsoft.AspNetCore.Mvc.Razor",
"Microsoft.AspNetCore.Mvc.Razor.Host",
"Microsoft.AspNetCore.Mvc.TagHelpers",
"Microsoft.AspNetCore.Mvc.ViewFeatures"
};
public static IEnumerable<ApplicationPart> DiscoverAssemblyParts(string entryPointAssemblyName)
{
var entryAssembly = Assembly.Load(new AssemblyName(entryPointAssemblyName));
var context = DependencyContext.Load(Assembly.Load(new AssemblyName(entryPointAssemblyName)));
return GetCandidateAssemblies(entryAssembly, context).Select(p => new AssemblyPart(p));
}
internal static IEnumerable<Assembly> GetCandidateAssemblies(Assembly entryAssembly, DependencyContext dependencyContext)
{
if (dependencyContext == null)
{
// Use the entry assembly as the sole candidate.
return new[] { entryAssembly };
}
return GetCandidateLibraries(dependencyContext)
.SelectMany(library => library.RuntimeAssemblyGroups.GetDefaultGroup().AssetPaths)
.Select(Load)
.Where(assembly => assembly != null);
}
// Returns a list of libraries that references the assemblies in <see cref="ReferenceAssemblies"/>.
// By default it returns all assemblies that reference any of the primary MVC assemblies
// while ignoring MVC assemblies.
// Internal for unit testing
internal static IEnumerable<RuntimeLibrary> GetCandidateLibraries(DependencyContext dependencyContext)
{
if (ReferenceAssemblies == null)
{
return Enumerable.Empty<RuntimeLibrary>();
}
return dependencyContext.RuntimeLibraries.Where(IsCandidateLibrary);
}
private static Assembly Load(string assetPath)
{
var name = Path.GetFileNameWithoutExtension(assetPath);
if (name != null)
{
if (name.EndsWith(NativeImageSufix, StringComparison.OrdinalIgnoreCase))
{
name = name.Substring(0, name.Length - NativeImageSufix.Length);
}
return Assembly.Load(new AssemblyName(name));
}
return null;
}
private static bool IsCandidateLibrary(RuntimeLibrary library)
{
Debug.Assert(ReferenceAssemblies != null);
return library.Dependencies.Any(dependency => ReferenceAssemblies.Contains(dependency.Name));
}
}
}

View File

@ -10,7 +10,7 @@ using Microsoft.Extensions.PlatformAbstractions;
namespace Microsoft.AspNetCore.Mvc.Infrastructure
{
public class DnxAssemblyProvider : IAssemblyProvider
public class DnxAssemblyProvider
{
private readonly ILibraryManager _libraryManager;

View File

@ -1,6 +1,8 @@
// 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.Linq;
using Microsoft.AspNetCore.Mvc.ApplicationParts;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Mvc.Razor.Compilation;
using Microsoft.AspNetCore.Mvc.Razor.Internal;
@ -20,9 +22,15 @@ namespace Microsoft.Extensions.DependencyInjection
{
if (DnxPlatformServices.Default.LibraryManager != null)
{
var partManager = GetApplicationPartManager(services);
var provider = new DnxAssemblyProvider(DnxPlatformServices.Default.LibraryManager);
foreach (var assembly in provider.CandidateAssemblies)
{
partManager.ApplicationParts.Add(new AssemblyPart(assembly));
}
// Add IAssemblyProvider services
services.AddSingleton(DnxPlatformServices.Default.LibraryManager);
services.AddTransient<IAssemblyProvider, DnxAssemblyProvider>();
// Add compilation services
services.AddSingleton(CompilationServices.Default.LibraryExporter);
@ -31,5 +39,23 @@ namespace Microsoft.Extensions.DependencyInjection
return services;
}
private static ApplicationPartManager GetApplicationPartManager(IServiceCollection services)
{
var manager = GetServiceFromCollection<ApplicationPartManager>(services);
if (manager == null)
{
manager = new ApplicationPartManager();
}
return manager;
}
private static T GetServiceFromCollection<T>(IServiceCollection services)
{
return (T)services
.FirstOrDefault(d => d.ServiceType == typeof(T))
?.ImplementationInstance;
}
}
}

View File

@ -1,18 +1,18 @@
// Copyright (c) .NET Foundation. All rights reserved.
// 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 System.Reflection;
using Microsoft.Extensions.DependencyModel;
using Xunit;
namespace Microsoft.AspNetCore.Mvc.Infrastructure
namespace Microsoft.AspNetCore.Mvc.Internal
{
public class DefaultAssemblyProviderTests
public class DefaultAssemblyPartDiscoveryProviderTests
{
private static readonly Assembly CurrentAssembly = typeof(DefaultAssemblyProviderTests).GetTypeInfo().Assembly;
private static readonly Assembly CurrentAssembly =
typeof(DefaultAssemblyPartDiscoveryProviderTests).GetTypeInfo().Assembly;
[Fact]
public void GetCandidateLibraries_IgnoresMvcAssemblies()
@ -31,10 +31,9 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure
expected,
},
Enumerable.Empty<RuntimeFallbacks>());
var provider = new DefaultAssemblyProvider(CurrentAssembly, dependencyContext);
// Act
var candidates = provider.GetCandidateLibraries();
var candidates = DefaultAssemblyPartDiscoveryProvider.GetCandidateLibraries(dependencyContext);
// Assert
Assert.Equal(new[] { expected }, candidates);
@ -43,11 +42,8 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure
[Fact]
public void CandidateAssemblies_ReturnsEntryAssemblyIfDependencyContextIsNull()
{
// Arrange
var provider = new DefaultAssemblyProvider(CurrentAssembly, dependencyContext: null);
// Act
var candidates = provider.CandidateAssemblies;
// Arrange & Act
var candidates = DefaultAssemblyPartDiscoveryProvider.GetCandidateAssemblies(CurrentAssembly, dependencyContext: null);
// Assert
Assert.Equal(new[] { CurrentAssembly }, candidates);
@ -69,82 +65,21 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure
GetLibrary("Baz", "Microsoft.AspNetCore.Mvc.Abstractions"),
},
Enumerable.Empty<RuntimeFallbacks>());
var provider = new DefaultAssemblyProvider(CurrentAssembly, dependencyContext);
// Act
var candidates = provider.GetCandidateLibraries();
var candidates = DefaultAssemblyPartDiscoveryProvider.GetCandidateLibraries(dependencyContext);
// Assert
Assert.Equal(new[] { "Foo", "Bar", "Baz" }, candidates.Select(a => a.Name));
}
[Fact]
public void GetCandidateLibraries_ReturnsLibrariesReferencingOverriddenAssemblies()
{
// Arrange
var dependencyContext = new DependencyContext(
new TargetInfo("framework", "runtime", "signature", isPortable: true),
CompilationOptions.Default,
new CompilationLibrary[0],
new[]
{
GetLibrary("Foo", "CustomMvc.Modules"),
GetLibrary("Bar", "CustomMvc.Application.Loader"),
GetLibrary("Baz", "Microsoft.AspNetCore.Mvc.Abstractions"),
},
Enumerable.Empty<RuntimeFallbacks>());
var referenceAssemblies = new HashSet<string>
{
"CustomMvc.Modules",
"CustomMvc.Application.Loader"
};
var assemblyProvider = new OverridenAssemblyProvider(
CurrentAssembly,
dependencyContext,
referenceAssemblies);
// Act
var candidates = assemblyProvider.GetCandidateLibraries();
// Assert
Assert.Equal(new[] { "Foo", "Bar" }, candidates.Select(a => a.Name));
}
[Fact]
public void GetCandidateLibraries_ReturnsEmptySequenceWhenReferenceAssembliesIsNull()
{
// Arrange
var dependencyContext = new DependencyContext(
new TargetInfo("framework", "runtime", "signature", isPortable: true),
CompilationOptions.Default,
new CompilationLibrary[0],
new[]
{
GetLibrary("Foo", "CustomMvc.Modules"),
GetLibrary("Bar", "CustomMvc.Application.Loader"),
GetLibrary("Baz", "Microsoft.AspNetCore.Mvc.Abstractions"),
},
Enumerable.Empty<RuntimeFallbacks>());
var assemblyProvider = new OverridenAssemblyProvider(
CurrentAssembly,
dependencyContext,
referenceAssemblies: null);
// Act
var candidates = assemblyProvider.GetCandidateLibraries();
// Assert
Assert.Empty(candidates);
}
// This test verifies DefaultAssemblyProvider.ReferenceAssemblies reflects the actual loadable assemblies
// This test verifies DefaultAssemblyPartDiscoveryProvider.ReferenceAssemblies reflects the actual loadable assemblies
// of the libraries that Microsoft.AspNetCore.Mvc dependes on.
// If we add or remove dependencies, this test should be changed together.
[Fact]
public void ReferenceAssemblies_ReturnsLoadableReferenceAssemblies()
{
// Arrange
var provider = new TestableAssemblyProvider(CurrentAssembly, dependencyContext: null);
var excludeAssemblies = new string[]
{
"Microsoft.AspNetCore.Mvc.WebApiCompatShim",
@ -170,7 +105,9 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure
.OrderBy(p => p, StringComparer.Ordinal);
// Act
var referenceAssemblies = provider.ReferenceAssemblies.OrderBy(p => p, StringComparer.Ordinal);
var referenceAssemblies = DefaultAssemblyPartDiscoveryProvider
.ReferenceAssemblies
.OrderBy(p => p, StringComparer.Ordinal);
// Assert
Assert.Equal(expected, referenceAssemblies);
@ -191,31 +128,5 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure
dependencies: dependencies.ToArray(),
serviceable: true);
}
private class OverridenAssemblyProvider : DefaultAssemblyProvider
{
public OverridenAssemblyProvider(
Assembly entryAssembly,
DependencyContext dependencyContext,
HashSet<string> referenceAssemblies)
: base(entryAssembly, dependencyContext)
{
ReferenceAssemblies = referenceAssemblies;
}
protected override HashSet<string> ReferenceAssemblies { get; }
}
private class TestableAssemblyProvider : DefaultAssemblyProvider
{
public TestableAssemblyProvider(
Assembly entryAssembly,
DependencyContext dependencyContext)
: base(entryAssembly, dependencyContext)
{
}
public new HashSet<string> ReferenceAssemblies => base.ReferenceAssemblies;
}
}
}
}

View File

@ -66,11 +66,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
{
var startupAssembly = typeof(TStartup).GetTypeInfo().Assembly;
// Inject a custom assembly provider. Overrides AddMvc() because that uses TryAdd().
var assemblyProvider = new StaticAssemblyProvider();
assemblyProvider.CandidateAssemblies.Add(startupAssembly);
services.AddSingleton<IAssemblyProvider>(assemblyProvider);
// Inject a custom application part manager. Overrides AddMvcCore() because that uses TryAdd().
var manager = new ApplicationPartManager();
manager.ApplicationParts.Add(new AssemblyPart(startupAssembly));
@ -79,12 +75,5 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
services.AddSingleton(manager);
}
private class StaticAssemblyProvider : IAssemblyProvider
{
public IList<Assembly> CandidateAssemblies { get; } = new List<Assembly>();
IEnumerable<Assembly> IAssemblyProvider.CandidateAssemblies => CandidateAssemblies;
}
}
}