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" />
|
<Compile Include="Microsoft.AspNetCore.Components.netstandard2.0.cs" />
|
||||||
<Reference Include="Microsoft.Extensions.Logging.Abstractions" />
|
<Reference Include="Microsoft.Extensions.Logging.Abstractions" />
|
||||||
<Reference Include="Microsoft.Extensions.DependencyInjection.Abstractions" />
|
<Reference Include="Microsoft.Extensions.DependencyInjection.Abstractions" />
|
||||||
|
<Reference Include="Microsoft.Extensions.HashCodeCombiner.Sources" />
|
||||||
<Reference Include="System.Buffers" />
|
<Reference Include="System.Buffers" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp3.0'">
|
<ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp3.0'">
|
||||||
<Compile Include="Microsoft.AspNetCore.Components.netcoreapp3.0.cs" />
|
<Compile Include="Microsoft.AspNetCore.Components.netcoreapp3.0.cs" />
|
||||||
<Reference Include="Microsoft.Extensions.Logging.Abstractions" />
|
<Reference Include="Microsoft.Extensions.Logging.Abstractions" />
|
||||||
<Reference Include="Microsoft.Extensions.DependencyInjection.Abstractions" />
|
<Reference Include="Microsoft.Extensions.DependencyInjection.Abstractions" />
|
||||||
|
<Reference Include="Microsoft.Extensions.HashCodeCombiner.Sources" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
||||||
|
|
@ -532,6 +532,8 @@ namespace Microsoft.AspNetCore.Components.Routing
|
||||||
{
|
{
|
||||||
public Router() { }
|
public Router() { }
|
||||||
[Microsoft.AspNetCore.Components.ParameterAttribute]
|
[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 { } }
|
public System.Reflection.Assembly AppAssembly { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
|
||||||
[Microsoft.AspNetCore.Components.ParameterAttribute]
|
[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 { } }
|
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() { }
|
public Router() { }
|
||||||
[Microsoft.AspNetCore.Components.ParameterAttribute]
|
[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 { } }
|
public System.Reflection.Assembly AppAssembly { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
|
||||||
[Microsoft.AspNetCore.Components.ParameterAttribute]
|
[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 { } }
|
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>
|
<PropertyGroup>
|
||||||
<TargetFrameworks>netstandard2.0;netcoreapp3.0</TargetFrameworks>
|
<TargetFrameworks>netstandard2.0;netcoreapp3.0</TargetFrameworks>
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Reference Include="Microsoft.Extensions.Logging.Abstractions" />
|
<Reference Include="Microsoft.Extensions.Logging.Abstractions" />
|
||||||
<Reference Include="Microsoft.Extensions.DependencyInjection.Abstractions" />
|
<Reference Include="Microsoft.Extensions.DependencyInjection.Abstractions" />
|
||||||
|
<Reference Include="Microsoft.Extensions.HashCodeCombiner.Sources" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup Condition="'$(TargetFramework)'=='netstandard2.0'">
|
<ItemGroup Condition="'$(TargetFramework)'=='netstandard2.0'">
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using Microsoft.AspNetCore.Components.Routing;
|
using Microsoft.AspNetCore.Components.Routing;
|
||||||
|
using Microsoft.Extensions.Internal;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Components
|
namespace Microsoft.AspNetCore.Components
|
||||||
{
|
{
|
||||||
|
|
@ -15,20 +16,21 @@ namespace Microsoft.AspNetCore.Components
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static class RouteTableFactory
|
internal static class RouteTableFactory
|
||||||
{
|
{
|
||||||
private static readonly ConcurrentDictionary<Assembly, RouteTable> Cache =
|
private static readonly ConcurrentDictionary<Key, RouteTable> Cache =
|
||||||
new ConcurrentDictionary<Assembly, RouteTable>();
|
new ConcurrentDictionary<Key, RouteTable>();
|
||||||
public static readonly IComparer<RouteEntry> RoutePrecedence = Comparer<RouteEntry>.Create(RouteComparison);
|
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;
|
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);
|
var routeTable = Create(componentTypes);
|
||||||
Cache.TryAdd(appAssembly, routeTable);
|
Cache.TryAdd(key, routeTable);
|
||||||
return 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;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Components.Rendering;
|
using Microsoft.AspNetCore.Components.Rendering;
|
||||||
|
|
@ -37,6 +38,12 @@ namespace Microsoft.AspNetCore.Components.Routing
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Parameter] public Assembly AppAssembly { get; set; }
|
[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>
|
/// <summary>
|
||||||
/// Gets or sets the content to display when no match is found for the requested route.
|
/// Gets or sets the content to display when no match is found for the requested route.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -64,6 +71,11 @@ namespace Microsoft.AspNetCore.Components.Routing
|
||||||
{
|
{
|
||||||
parameters.SetParameterProperties(this);
|
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
|
// 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
|
// 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).
|
// 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)}.");
|
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);
|
Refresh(isNavigationIntercepted: false);
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,45 @@ namespace Microsoft.AspNetCore.Components.Test.Routing
|
||||||
{
|
{
|
||||||
public class RouteTableFactoryTests
|
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]
|
[Fact]
|
||||||
public void CanDiscoverRoute()
|
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);
|
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]
|
[Fact]
|
||||||
public void ResetsScrollPositionWhenPerformingInternalNavigation_LinkClick()
|
public void ResetsScrollPositionWhenPerformingInternalNavigation_LinkClick()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -61,6 +61,7 @@
|
||||||
<option value="BasicTestApp.ReorderingFocusComponent">Reordering focus retention</option>
|
<option value="BasicTestApp.ReorderingFocusComponent">Reordering focus retention</option>
|
||||||
<option value="BasicTestApp.RouterTest.NavigationManagerComponent">NavigationManager Test</option>
|
<option value="BasicTestApp.RouterTest.NavigationManagerComponent">NavigationManager Test</option>
|
||||||
<option value="BasicTestApp.RouterTest.TestRouter">Router</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.SvgComponent">SVG</option>
|
||||||
<option value="BasicTestApp.SvgWithChildComponent">SVG with child component</option>
|
<option value="BasicTestApp.SvgWithChildComponent">SVG with child component</option>
|
||||||
<option value="BasicTestApp.TextOnlyComponent">Plain text</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