I'm trying to deal with plugins using Razor Pages application.
Solution consists of 3 projects: one Razor Pages application and two Razor Class Libraries (RCL). Application must not refer RCL projects statically, they must be loaded as plugins:
There's nothing special inside pages. Feature pages just produce simple HTML. Index page builds a sort of menu.
Index page model:
public class IndexModel : PageModel
{
public IEnumerable<MenuItem> MenuItems { get; private set; }
public void OnGet()
{
MenuItems = new List<MenuItem>
{
new MenuItem { Route = "FeatureA", Title = "Feature A" },
new MenuItem { Route = "FeatureB", Title = "Feature B" }
};
}
}
Index page:
@page
@model IndexModel
@{
ViewData["Title"] = "Home page";
}
<div class="text-center">
<div class="navbar-collapse collapse d-sm-inline-flex flex-sm-row-reverse">
<ul class="navbar-nav flex-grow-1">
@foreach (var item in Model.MenuItems)
{
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-page="/@item.Route">@item.Title</a>
</li>
}
</ul>
</div>
</div>
When I run the app, there are menu items, but their href
s are empty:
<div class="text-center">
<div class="navbar-collapse collapse d-sm-inline-flex flex-sm-row-reverse">
<ul class="navbar-nav flex-grow-1">
<li class="nav-item">
<a class="nav-link text-dark" href="">Feature A</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" href="">Feature B</a>
</li>
</ul>
</div>
</div>
Of course, all assemblies (app and feature ones) are in the same directory.
Menu works in two following cases:
App.deps.json
with FeatureLib_A
and FeatureLib_B
as dependecies (just save deps file from first case, remove references, rebuild all, copy saved deps file).Also, I've tried to eagerly load RCL assemblies in Startup
class.
Assemblies are being loaded, but Index
page behaves the same.
Is there any way to tell ASP infrastructure to use RCL assemblies without modifying deps file? What am I missing?
Razor Pages allow for self-contained .cshtml Razor pages on disk to be served including dynamic content via it's built-in support for C# Razor syntax. Essentially you can create something like hello.cshtml and then serve that as https://localhost:5200/hello.
Introduction to Razor Pages in ASP.NET Core. Razor Pages is a new aspect of ASP.NET Core MVC that makes coding page-focused scenarios easier and more productive. If you're looking for a tutorial that uses the Model-View-Controller approach, see Get started with ASP.NET Core MVC. This document provides an introduction to Razor Pages.
Because runtime compiled Razor Pages are not pre-compiled you can't easily add assemblies to access at runtime. All that's available by default is what is compiled into the application when the static server was built originally - it doesn't look for other assemblies in the startup folder or elsewhere at least not automatically.
Run dotnet new webapp from the command line. See Get started with Razor Pages for detailed instructions on how to create a Razor Pages project. Razor Pages is enabled in program.cs: AddRazorPages adds services for Razor Pages to the app. MapRazorPages adds endpoints for Razor Pages to the IEndpointRouteBuilder. Consider a basic page:
I've figured it out.
The basic idea is to give ApplicationPartManager
appropriate application parts.
It's important to note that:
FeatureLib_A.dll
) must be added as AssemblyPart
;FeatureLib_A.Views.dll
) must be added as CompiledRazorAssemblyPart
.Sample code:
public class Startup
{
// ...
public void ConfigureServices(IServiceCollection services)
{
var assemblyLoader = new DotNetCoreAssemblyLoader(searchPattern: "FeatureLib*.dll");
services.AddMvc()
.ConfigureApplicationPartManager(_ =>
{
foreach (var assembly in assemblyLoader.Assemblies)
{
if (assembly.FullName.Contains("Views"))
{
_.ApplicationParts.Add(new CompiledRazorAssemblyPart(assembly));
}
else
{
_.ApplicationParts.Add(new AssemblyPart(assembly));
}
}
})
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}
// ...
}
DotNetCoreAssemblyLoader
is a custom class, which looks for assembly files using given search pattern, and loads assemblies via AssemblyLoadContext.Default.LoadFromAssemblyPath
.
public class Startup
{
public Startup( IHostingEnvironment hostingEnvironment)
{
_hostingEnvironment = hostingEnvironment;
}
private readonly IHostingEnvironment _hostingEnvironment;
public void ConfigureServices(IServiceCollection services)
{
...
services.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2)
.ConfigureApplicationPartManager(ConfigureApplicationParts); ;
}
private void ConfigureApplicationParts(ApplicationPartManager apm)
{
string rootPath = _hostingEnvironment.ContentRootPath;
var pluginsPath = Path.Combine(rootPath, "Plugins");
var assemblyFiles = Directory.GetFiles(pluginsPath, "Plugin*.dll", SearchOption.AllDirectories);
foreach (var assemblyFile in assemblyFiles)
{
try
{
var assembly = Assembly.LoadFrom(assemblyFile);
if (assemblyFile.EndsWith(".Views.dll"))
apm.ApplicationParts.Add(new
CompiledRazorAssemblyPart(assembly));
else
apm.ApplicationParts.Add(new AssemblyPart(assembly));
}
catch (Exception e) { }
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With