Add AdditionalAssemblies to Router
Adds the ability to specify multiple assemblies to the Router component. Prior to preview 8, the router would search all dependencies of `AppAssembly` for routable components. We made an intentional change to stop that. However, we haven't yet give users a way to specify multiple assemblies if their components are split across assemblies.
This commit is contained in:
parent
5678f84d60
commit
8035ef0a27
|
|
@ -7,11 +7,13 @@
|
|||
<Compile Include="Microsoft.AspNetCore.Components.netstandard2.0.cs" />
|
||||
<Reference Include="Microsoft.Extensions.Logging.Abstractions" />
|
||||
<Reference Include="Microsoft.Extensions.DependencyInjection.Abstractions" />
|
||||
<Reference Include="Microsoft.Extensions.HashCodeCombiner.Sources" />
|
||||
<Reference Include="System.Buffers" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp3.0'">
|
||||
<Compile Include="Microsoft.AspNetCore.Components.netcoreapp3.0.cs" />
|
||||
<Reference Include="Microsoft.Extensions.Logging.Abstractions" />
|
||||
<Reference Include="Microsoft.Extensions.DependencyInjection.Abstractions" />
|
||||
<Reference Include="Microsoft.Extensions.HashCodeCombiner.Sources" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -532,6 +532,8 @@ namespace Microsoft.AspNetCore.Components.Routing
|
|||
{
|
||||
public Router() { }
|
||||
[Microsoft.AspNetCore.Components.ParameterAttribute]
|
||||
public System.Collections.Generic.IEnumerable<System.Reflection.Assembly> AdditionalAssemblies { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
|
||||
[Microsoft.AspNetCore.Components.ParameterAttribute]
|
||||
public System.Reflection.Assembly AppAssembly { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
|
||||
[Microsoft.AspNetCore.Components.ParameterAttribute]
|
||||
public Microsoft.AspNetCore.Components.RenderFragment<Microsoft.AspNetCore.Components.RouteData> Found { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
|
||||
|
|
|
|||
|
|
@ -532,6 +532,8 @@ namespace Microsoft.AspNetCore.Components.Routing
|
|||
{
|
||||
public Router() { }
|
||||
[Microsoft.AspNetCore.Components.ParameterAttribute]
|
||||
public System.Collections.Generic.IEnumerable<System.Reflection.Assembly> AdditionalAssemblies { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
|
||||
[Microsoft.AspNetCore.Components.ParameterAttribute]
|
||||
public System.Reflection.Assembly AppAssembly { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
|
||||
[Microsoft.AspNetCore.Components.ParameterAttribute]
|
||||
public Microsoft.AspNetCore.Components.RenderFragment<Microsoft.AspNetCore.Components.RouteData> Found { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard2.0;netcoreapp3.0</TargetFrameworks>
|
||||
|
|
@ -16,6 +16,7 @@
|
|||
<ItemGroup>
|
||||
<Reference Include="Microsoft.Extensions.Logging.Abstractions" />
|
||||
<Reference Include="Microsoft.Extensions.DependencyInjection.Abstractions" />
|
||||
<Reference Include="Microsoft.Extensions.HashCodeCombiner.Sources" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)'=='netstandard2.0'">
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Microsoft.AspNetCore.Components.Routing;
|
||||
using Microsoft.Extensions.Internal;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components
|
||||
{
|
||||
|
|
@ -15,20 +16,21 @@ namespace Microsoft.AspNetCore.Components
|
|||
/// </summary>
|
||||
internal static class RouteTableFactory
|
||||
{
|
||||
private static readonly ConcurrentDictionary<Assembly, RouteTable> Cache =
|
||||
new ConcurrentDictionary<Assembly, RouteTable>();
|
||||
private static readonly ConcurrentDictionary<Key, RouteTable> Cache =
|
||||
new ConcurrentDictionary<Key, RouteTable>();
|
||||
public static readonly IComparer<RouteEntry> RoutePrecedence = Comparer<RouteEntry>.Create(RouteComparison);
|
||||
|
||||
public static RouteTable Create(Assembly appAssembly)
|
||||
public static RouteTable Create(IEnumerable<Assembly> assemblies)
|
||||
{
|
||||
if (Cache.TryGetValue(appAssembly, out var resolvedComponents))
|
||||
var key = new Key(assemblies.OrderBy(a => a.FullName).ToArray());
|
||||
if (Cache.TryGetValue(key, out var resolvedComponents))
|
||||
{
|
||||
return resolvedComponents;
|
||||
}
|
||||
|
||||
var componentTypes = appAssembly.ExportedTypes.Where(t => typeof(IComponent).IsAssignableFrom(t));
|
||||
var componentTypes = key.Assemblies.SelectMany(a => a.ExportedTypes.Where(t => typeof(IComponent).IsAssignableFrom(t)));
|
||||
var routeTable = Create(componentTypes);
|
||||
Cache.TryAdd(appAssembly, routeTable);
|
||||
Cache.TryAdd(key, routeTable);
|
||||
return routeTable;
|
||||
}
|
||||
|
||||
|
|
@ -160,5 +162,61 @@ namespace Microsoft.AspNetCore.Components
|
|||
");
|
||||
}
|
||||
}
|
||||
|
||||
private readonly struct Key : IEquatable<Key>
|
||||
{
|
||||
public readonly Assembly[] Assemblies;
|
||||
|
||||
public Key(Assembly[] assemblies)
|
||||
{
|
||||
Assemblies = assemblies;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is Key other ? base.Equals(other) : false;
|
||||
}
|
||||
|
||||
public bool Equals(Key other)
|
||||
{
|
||||
if (Assemblies == null && other.Assemblies == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if (Assemblies == null ^ other.Assemblies == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else if (Assemblies.Length != other.Assemblies.Length)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (var i = 0; i < Assemblies.Length; i++)
|
||||
{
|
||||
if (!Assemblies[i].Equals(other.Assemblies[i]))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
var hash = new HashCodeCombiner();
|
||||
|
||||
if (Assemblies != null)
|
||||
{
|
||||
for (var i = 0; i < Assemblies.Length; i++)
|
||||
{
|
||||
hash.Add(Assemblies[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Components.Rendering;
|
||||
|
|
@ -37,6 +38,12 @@ namespace Microsoft.AspNetCore.Components.Routing
|
|||
/// </summary>
|
||||
[Parameter] public Assembly AppAssembly { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a collection of additional assemblies that should be searched for components
|
||||
/// that can match URIs.
|
||||
/// </summary>
|
||||
[Parameter] public IEnumerable<Assembly> AdditionalAssemblies { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the content to display when no match is found for the requested route.
|
||||
/// </summary>
|
||||
|
|
@ -64,6 +71,11 @@ namespace Microsoft.AspNetCore.Components.Routing
|
|||
{
|
||||
parameters.SetParameterProperties(this);
|
||||
|
||||
if (AppAssembly == null)
|
||||
{
|
||||
throw new InvalidOperationException($"The {nameof(Router)} component requires a value for the parameter {nameof(AppAssembly)}.");
|
||||
}
|
||||
|
||||
// Found content is mandatory, because even though we could use something like <RouteView ...> as a
|
||||
// reasonable default, if it's not declared explicitly in the template then people will have no way
|
||||
// to discover how to customize this (e.g., to add authorization).
|
||||
|
|
@ -79,7 +91,9 @@ namespace Microsoft.AspNetCore.Components.Routing
|
|||
throw new InvalidOperationException($"The {nameof(Router)} component requires a value for the parameter {nameof(NotFound)}.");
|
||||
}
|
||||
|
||||
Routes = RouteTableFactory.Create(AppAssembly);
|
||||
|
||||
var assemblies = AdditionalAssemblies == null ? new[] { AppAssembly } : new[] { AppAssembly }.Concat(AdditionalAssemblies);
|
||||
Routes = RouteTableFactory.Create(assemblies);
|
||||
Refresh(isNavigationIntercepted: false);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,45 @@ namespace Microsoft.AspNetCore.Components.Test.Routing
|
|||
{
|
||||
public class RouteTableFactoryTests
|
||||
{
|
||||
[Fact]
|
||||
public void CanCacheRouteTable()
|
||||
{
|
||||
// Arrange
|
||||
var routes1 = RouteTableFactory.Create(new[] { GetType().Assembly, });
|
||||
|
||||
// Act
|
||||
var routes2 = RouteTableFactory.Create(new[] { GetType().Assembly, });
|
||||
|
||||
// Assert
|
||||
Assert.Same(routes1, routes2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanCacheRouteTableWithDifferentAssembliesAndOrder()
|
||||
{
|
||||
// Arrange
|
||||
var routes1 = RouteTableFactory.Create(new[] { typeof(object).Assembly, GetType().Assembly, });
|
||||
|
||||
// Act
|
||||
var routes2 = RouteTableFactory.Create(new[] { GetType().Assembly, typeof(object).Assembly, });
|
||||
|
||||
// Assert
|
||||
Assert.Same(routes1, routes2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DoesNotCacheRouteTableForDifferentAssemblies()
|
||||
{
|
||||
// Arrange
|
||||
var routes1 = RouteTableFactory.Create(new[] { GetType().Assembly, });
|
||||
|
||||
// Act
|
||||
var routes2 = RouteTableFactory.Create(new[] { GetType().Assembly, typeof(object).Assembly, });
|
||||
|
||||
// Assert
|
||||
Assert.NotSame(routes1, routes2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanDiscoverRoute()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -418,6 +418,15 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
Assert.Equal("Oops, that component wasn't found!", app.FindElement(By.Id("test-info")).Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RoutingToComponentOutsideMainAppWorksWithAdditionalAssemblySpecified()
|
||||
{
|
||||
SetUrlViaPushState("/routeablecomponentfrompackage.html");
|
||||
|
||||
var app = MountTestComponent<TestRouterWithAdditionalAssembly>();
|
||||
Assert.Contains("This component, including the CSS and image required to produce its", app.FindElement(By.CssSelector("div.special-style")).Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ResetsScrollPositionWhenPerformingInternalNavigation_LinkClick()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -61,6 +61,7 @@
|
|||
<option value="BasicTestApp.ReorderingFocusComponent">Reordering focus retention</option>
|
||||
<option value="BasicTestApp.RouterTest.NavigationManagerComponent">NavigationManager Test</option>
|
||||
<option value="BasicTestApp.RouterTest.TestRouter">Router</option>
|
||||
<option value="BasicTestApp.RouterTest.TestRouterWithAdditionalAssembly">Router with additional assembly</option>
|
||||
<option value="BasicTestApp.SvgComponent">SVG</option>
|
||||
<option value="BasicTestApp.SvgWithChildComponent">SVG with child component</option>
|
||||
<option value="BasicTestApp.TextOnlyComponent">Plain text</option>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
@using Microsoft.AspNetCore.Components.Routing
|
||||
<Router AppAssembly="@typeof(BasicTestApp.Program).Assembly" AdditionalAssemblies="@(new[] { typeof(TestContentPackage.RouteableComponentFromPackage).Assembly, })">
|
||||
<Found Context="routeData">
|
||||
<RouteView RouteData="@routeData" />
|
||||
</Found>
|
||||
<NotFound>
|
||||
<LayoutView Layout="@typeof(RouterTestLayout)">
|
||||
<div id="test-info">Oops, that component wasn't found!</div>
|
||||
</LayoutView>
|
||||
</NotFound>
|
||||
</Router>
|
||||
Loading…
Reference in New Issue