Telling a server-rendering story with Razor components
I am currently looking into how to ease some frontend developers with react background into doing more backend work - our server code is typically written with the ASP.NET Core tech stack. Sadly, .NET doesn't tell a particularly good pure server-side render story. cshtml feels ancient at times, no co-located css, no co-located js. Now, with a few lines of code one can get razor components (which are typically associated with the rather proprietary Blazor tech stack) to kinda play well with the ASP.NET Core MVC stack. Add relevant services in Program.cs: builder.Services.AddRazorComponents(); Ability to return razor components from a controller action: This needs a tiny class which doesn't seem to exist within ASP.NET Core itself class ResultToActionResult(IResult result) : IActionResult { public Task ExecuteResultAsync(ActionContext context) => result.ExecuteAsync(context.HttpContext); } Now in a controller action, we can basically do: return new ResultToActionResult( new RazorComponentResult(typeof(TenantList), new Dictionary { ["Model"] = result.Other })); Providing a base class that exposes the provided model as a property makes this work quite similar to a model provided to a view. Now we are in the space of Razor Components, where component-based UI development can feel much more at home while retaining full control of the HTML sent to the client. co-located css: This already works out of the box with razor components, co-located js: I found it quite weird that there doesn't seem to be anything for that. Here, in a POC I took the following approach: Have a class that is able to hold registrations of js during a request: // register this at startup like // builder.Services.AddScoped(); public class JavascriptModuleRendering : IEnumerable { private readonly List scripts = []; public void Add(Type componentType) { // note that co-located js exists as concept // and will be added to wwwroot in a "publish" operation var fullName = componentType .FullName!.Replace("MyAssembly", "").Replace(".", "/"); scripts.Add($"{fullName}.razor.js"); } public IEnumerator GetEnumerator() => scripts.GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } Now, if you, as a component, want to make sure that your co-located js comes to the client, inherit from ComponentWithScriptBase: public class ComponentWithScriptBase : ComponentBase { [Inject] protected JavascriptModuleRendering JavascriptCollector { get; set; } = null!; protected override void OnInitialized() => JavascriptCollector.Add(GetType()); } Finally, in your layout you can add an "Outlet" for those scripts: where the ScriptsRenderer.razor looks like this @using MyAssembly @foreach(var script in JavascriptCollector) { } @code { [Inject] protected JavascriptModuleRendering JavascriptCollector { get; set; } = null!; } Conclusion So, with a couple of code pieces, we can leverage Blazor tech to tell a better pure server-side rendering story in ASP.NET Core. My question really is the typical Jurassic Park question: Am I too preocupied with being able to do it that I forgot to ask whether I should do it? I'm old enough to have seen a bunch of UI frameworks from Microsoft come and go. Should I lean into the Blazor tech in that way or keep living with the inferior cshtml stuff? Why does Microsoft don't tell a compelling server-side rendering story? Note that while this would be an iteration of an existing project, we didn't lean a lot into cshtml features beyond partial views and the like. Hope this stackexchange site is the right place for such a discussion, cheers!
I am currently looking into how to ease some frontend developers with react background into doing more backend work - our server code is typically written with the ASP.NET Core tech stack. Sadly, .NET doesn't tell a particularly good pure server-side render story. cshtml feels ancient at times, no co-located css, no co-located js.
Now, with a few lines of code one can get razor components (which are typically associated with the rather proprietary Blazor tech stack) to kinda play well with the ASP.NET Core MVC stack.
- Add relevant services in
Program.cs
:builder.Services.AddRazorComponents();
- Ability to return razor components from a controller action:
This needs a tiny class which doesn't seem to exist within ASP.NET Core itself
class ResultToActionResult(IResult result) : IActionResult
{
public Task ExecuteResultAsync(ActionContext context) =>
result.ExecuteAsync(context.HttpContext);
}
Now in a controller action, we can basically do:
return new ResultToActionResult(
new RazorComponentResult(typeof(TenantList),
new Dictionary { ["Model"] = result.Other }));
Providing a base class that exposes the provided model as a property makes this work quite similar to a model provided to a view.
Now we are in the space of Razor Components, where component-based UI development can feel much more at home while retaining full control of the HTML sent to the client.
- co-located css: This already works out of the box with razor components,
- co-located js: I found it quite weird that there doesn't seem to be anything for that. Here, in a POC I took the following approach:
Have a class that is able to hold registrations of js during a request:
// register this at startup like
// builder.Services.AddScoped();
public class JavascriptModuleRendering : IEnumerable
{
private readonly List scripts = [];
public void Add(Type componentType)
{
// note that co-located js exists as concept
// and will be added to wwwroot in a "publish" operation
var fullName = componentType
.FullName!.Replace("MyAssembly", "").Replace(".", "/");
scripts.Add($"{fullName}.razor.js");
}
public IEnumerator GetEnumerator() => scripts.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
Now, if you, as a component, want to make sure that your co-located js comes to the client, inherit from ComponentWithScriptBase
:
public class ComponentWithScriptBase : ComponentBase
{
[Inject]
protected JavascriptModuleRendering JavascriptCollector { get; set; } = null!;
protected override void OnInitialized() => JavascriptCollector.Add(GetType());
}
Finally, in your layout you can add an "Outlet" for those scripts:
where the ScriptsRenderer.razor looks like this
@using MyAssembly
@foreach(var script in JavascriptCollector)
{
}
@code {
[Inject]
protected JavascriptModuleRendering JavascriptCollector { get; set; } = null!;
}
Conclusion
So, with a couple of code pieces, we can leverage Blazor tech to tell a better pure server-side rendering story in ASP.NET Core. My question really is the typical Jurassic Park question:
Am I too preocupied with being able to do it that I forgot to ask whether I should do it?
I'm old enough to have seen a bunch of UI frameworks from Microsoft come and go. Should I lean into the Blazor tech in that way or keep living with the inferior cshtml stuff? Why does Microsoft don't tell a compelling server-side rendering story?
Note that while this would be an iteration of an existing project, we didn't lean a lot into cshtml features beyond partial views and the like.
Hope this stackexchange site is the right place for such a discussion, cheers!
What's Your Reaction?