![](/img/trans.png)
[英]How can I load the same Blazor page hitting same url with different parameters and show the parameter to UI in Blazor?
[英]How can I load a Blazor page at runtime?
我在 RCL 中创建了一些 Razor 组件,我想在运行时加载和显示它们。 我知道我可以使用反射加载程序集,但是我希望页面自动更新菜单并显示正确的组件。 这应该可以通过简单地将 DLL 放到指定目录中来完成。
到目前为止,我已经确定每个页面都应该有自己的类来实现一个接口,以确保每个页面都有必要的信息。 我想出的界面是
public interface IDynamicComponent
{
IDictionary<string,string> Parameters { get; }
string Name { get; }
string Page { get; }
Type Component { get;}
MenuItem MenuData { get; }
}
我可以使用以下方法将其加载到内存中:
public IEnumerable<Type> LoadComponents(string path)
{
var components = new List<Type>();
var assemblies = LoadAssemblies(path);
foreach (var asm in assemblies)
{
var types = GetTypesWithInterface(asm);
foreach (var typ in types) components.Add(typ);
}
Components = components;
}
private IEnumerable<Type> GetTypesWithInterface(Assembly asm)
{
var it = typeof(IDynamicComponent);
return GetLoadableTypes(asm).Where(it.IsAssignableFrom).ToList();
}
private IEnumerable<Type> GetLoadableTypes(Assembly assembly)
{
if (assembly == null) throw new ArgumentNullException("assembly");
try
{
return assembly.GetTypes();
}
catch (ReflectionTypeLoadException e)
{
return e.Types.Where(t => t != null);
}
}
但是如何更新 UI(页面和导航菜单)以反映这些组件?
这是我前几天试图完成的事情,并认为如果其他人遇到类似问题,我会在这里发布以供其他人记录。 我解决这个问题的第一步是创建一个新的 .net 标准 2.0 项目并添加以下项目:
一个 IComponentService 接口,允许简单的 DI 注入,并且在需要时可能有不同的实现
public interface IComponentService
{
void LoadComponents(string path);
IDynamicComponent GetComponentByName(string name);
IDynamicComponent GetComponentByPage(string name);
IEnumerable<Type> Components { get; }
IEnumerable<MenuItem> GetMenuItems(bool getHiddenItems = false);
}
IComponentService 的实现,主要用于加载组件/页面并跟踪它们。
public class ComponentService : IComponentService
{
public IEnumerable<Type> Components { get; private set; }
public void LoadComponents(string path)
{
var components = new List<Type>();
var assemblies = LoadAssemblies(path);
foreach (var asm in assemblies)
{
var types = GetTypesWithInterface(asm);
foreach (var typ in types) components.Add(typ);
}
Components = components;
}
public IEnumerable<MenuItem> GetMenuItems(bool getHiddenItems = false)
{
var components = Components.Select(x => (IDynamicComponent) Activator.CreateInstance(x));
if (!getHiddenItems)
components = components.Where(x => x.MenuData.Display);
return components.Select(x=>x.MenuData);
}
public IDynamicComponent GetComponentByName(string name)
{
return Components.Select(x => (IDynamicComponent) Activator.CreateInstance(x))
.SingleOrDefault(x => x.Name == name);
}
public IDynamicComponent GetComponentByPage(string name)
{
return Components.Select(x => (IDynamicComponent) Activator.CreateInstance(x))
.SingleOrDefault(x => x.Page == name);
}
private IEnumerable<Assembly> LoadAssemblies(string path)
{
return Directory.GetFiles(path, "*.dll").Select(dll => Assembly.LoadFile(dll)).ToList();
}
private IEnumerable<Type> GetTypesWithInterface(Assembly asm)
{
var it = typeof(IDynamicComponent);
return GetLoadableTypes(asm).Where(it.IsAssignableFrom).ToList();
}
private IEnumerable<Type> GetLoadableTypes(Assembly assembly)
{
if (assembly == null) throw new ArgumentNullException("assembly");
try
{
return assembly.GetTypes();
}
catch (ReflectionTypeLoadException e)
{
return e.Types.Where(t => t != null);
}
}
}
IDynamicComponent 接口,您要加载的每个页面都应该有一个实现
public interface IDynamicComponent
{
IDictionary<string,string> Parameters { get; }
string Name { get; }
string Page { get; }
Type Component { get;}
MenuItem MenuData { get; }
}
还有一个简单的 MenuItem 类,它将包含导航菜单的信息
public class MenuItem
{
public bool Display { get; set; }
public string Text { get; set; }
public string Page { get; set; }
public string Icon { get; set; }
public string CSS { get; set; }
}
设置组件
下一步是设置页面。 我首先使用内置演示 WeatherForecast 并将所有相关文件作为RCL移动到一个单独的项目中。 在此之后,我修改了 .razor 文件以不注入 WeatherForecastService 而是实例化它的新副本,如下所示:
@code {
[Parameter]
public string Name { get; set; }
private WeatherForecast[] forecasts;
private WeatherForecastService WeatherForecastService;
protected override async Task OnInitializedAsync()
{
WeatherForecastService = new WeatherForecastService();
forecasts = await WeatherForecastService.GetForecastAsync(DateTime.Now);
}
}
接下来,我创建了一个名为 MyComponent 的类并将其添加到包含 WeatherForecast 的项目中
public class MyComponent : IDynamicComponent
{
public bool DisplayInMenu => true;
public IDictionary<string,string> Parameters => new Dictionary<string,string>
{
{"Name","My Weather Forecast"}
};
public string Name => "Weather Forecast";
public string Page => "Forecast";
public Type Component => typeof(Component2);
public MenuItem MenuData => new MenuItem
{
Display = true,
Page = Page,
CSS = String.Empty,
Text = "Data",
Icon = "oi oi-list-rich"
};
}
请务必注意,参数字典包含一个名为“名称”的条目,它是 WeatherForecast 页面的参数名称。 这允许我们在运行时更改和注入不同的参数。 “页面”属性是为页面创建 url(例如 /Forecast /Counter 等)
修复基础项目
设置组件和包含 componentservice 的另一个项目后,我必须修改基础 Blazor 项目以利用这些更改。
首先,我通过将以下代码添加到 Startup.cs 文件中的 ConfigureServices 方法,将 IComponentService 添加到 DI 容器
services.AddSingleton<IComponentService>(_ =>
{
var service = new ComponentService();
service.LoadComponents(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location));
return service;
});
接下来我创建了一个简单的扩展方法,可以使用RenderFragment 构建器将 MenuItem 转换为组件
public static RenderFragment GenerateMenuItem(this MenuItem item)
{
RenderFragment fragment = builder =>
{
builder.OpenElement(3, "li");
builder.AddAttribute(4,"class","nav-item px-3");
builder.OpenComponent<NavLink>(4);
builder.AddAttribute(6,"class","nav-link");
builder.AddAttribute(7, "href", $"/{item.Page}");
builder.AddAttribute(8, "Match", NavLinkMatch.All);
builder.AddAttribute(9, "ChildContent", (RenderFragment)((builder2) => {
builder2.AddMarkupContent(10, $"<span class=\"{item.Icon}\" aria-hidden=\"true\"></span>");
builder2.AddContent(11, item.Text);
}));
builder.CloseComponent();
builder.CloseElement();
};
return fragment;
}
下一阶段是通过从 MenuItems 生成渲染片段并显示它们来修改导航菜单以加载所有组件。 在 NavMenu.razor 中,我编辑了文件以匹配以下内容:
@using Component.Common
@inject IComponentService ComponentService
<div class="top-row pl-4 navbar navbar-dark">
<a class="navbar-brand" href="">BlazorComponentHotloadDemo</a>
<button class="navbar-toggler" @onclick="ToggleNavMenu">
<span class="navbar-toggler-icon"></span>
</button>
</div>
<div class="@NavMenuCssClass" @onclick="ToggleNavMenu">
<ul class="nav flex-column">
<li class="nav-item px-3">
<NavLink class="nav-link" href="" Match="NavLinkMatch.All">
<span class="oi oi-home" aria-hidden="true"></span> Home
</NavLink>
</li>
@if (menuItems != null)
{
foreach (var fragment in menuItems)
@fragment;
}
</ul>
</div>
@code {
IEnumerable<RenderFragment> menuItems;
private bool collapseNavMenu = true;
private string NavMenuCssClass => collapseNavMenu ? "collapse" : null;
private void ToggleNavMenu()
{
collapseNavMenu = !collapseNavMenu;
}
protected override void OnInitialized()
{
var items = ComponentService.GetMenuItems();
var menulist = new List<RenderFragment>();
foreach (var item in items)
{
menulist.Add(item.GenerateMenuItem());
}
menuItems = menulist;
base.OnInitialized();
}
}
对于最后一步,我在 Pages 目录中创建了一个名为 ComponentPage 的新页面来显示新页面。 这是通过使用 RenderFragment 构建器来完成的。 我们打开页面并添加任何参数,然后在页面上显示结果。
@page "/{componentName}"
@using Component.Common
@inject IComponentService ComponentService
@dynamicComonent()
@code{
[Parameter]
public string componentName { get; set; }
RenderFragment dynamicComonent() => builder =>
{
var component = ComponentService.GetComponentByPage(componentName);
builder.OpenComponent(0,component.Component);
for (int i = 0; i < component.Parameters.Count; i++)
{
var attribute = component.Parameters.ElementAt(i);
builder.AddAttribute(i+1,attribute.Key,attribute.Value);
}
builder.CloseComponent();
};
}
结果是能够在运行时加载完整页面并修改导航菜单。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.