dotnet / aspnetcore

ASP.NET Core is a cross-platform .NET framework for building modern cloud-based web applications on Windows, Mac, or Linux.
https://asp.net
MIT License
35.21k stars 9.95k forks source link

Content Management Feature #52214

Closed TehWardy closed 10 months ago

TehWardy commented 10 months ago

Is there an existing issue for this?

Is your feature request related to a problem? Please describe the problem.

I'd like to load Blazor components (think .razor file contents) from my database. The idea behind this is to overhaul my content managment system such that we no longer need javascript anywhere in our product stack.

Describe the solution you'd like

At the moment my CMS supports as part of a page render "tags" so I can put in to my tooling [component[name]] and the back end replaces that with some content from the db and then serves up the page.

I'd like to move all the page building functionality on to the front end in blazor so I can do something like this ...

@page "/{*Path}"
@using Microsoft.AspNetCore.Http.Extensions
@inject NavigationManager NavigationManager

@Render(Content)

@code {

    [Parameter]
    public string Path { get; set; }
    public string Content { get; set; }

    protected override async void OnInitialized()
    {
        using var api = new HttpClient { BaseAddress = new Uri($"{NavigationManager.BaseUri}/Api/") };
        Content = await api.GetAsync("Page?path=" + Path);
    }
}

The "Content" string here would contain razor content so think of that like rendering a razor component tag.

My existing CMS has a classic MVC controller that renders a page (all server side, the page references a layout, both are in the db) and the layout looks something like this ...

<div class="row">
    <div class="col-md-10">
        <a href="/">
            <img class="header-logo" src="[api[root]]DMS/Content/CompanyLogo.png" alt="[app[name]]" />
        </a>
        [component[topnav]]
    </div>
    <div class="col-md-2 text-end p-2">
        <div class="row">
            <div class="col-sm-12">
                [component[CultureFlags]]
            </div>
            <div class="col-sm-12">
                <span id="date"></span>  <span id="time"></span>
            </div>
            <div class="col-sm-12">
                [component[UserProfile]]
            </div>
        </div>
    </div>
    <div class="col-md-12">
        <h2>[page[title]]</h2>
    </div>
</div>
<div class="row">
    <div class="col-md-12">
        <div class="row">
            <div class="col-md-12">
                [content[body]]  //<-- dynamically injected block of raw content from the DB
            </div>
        </div>
    </div>
</div>

<!-- cut for berevity -->
<script src="..."></script>
<script src="..."></script>

<script type="text/javascript">
    $(function() {
        initContent();  //<-- defined in my js framework, calls an init() function in each js component
    });
</script>

... notice my use of [tag[value]] a custom tagging mechanism I built for the CMS that allows me to dynamically build pages from smaller pieces like content blocks, components and translated string resources.

I'd like to move this to the client and have blazor do the same work the server currently does rom only the need to pull the content, but instead of referencing blocks of javascript i'd like to be able to drop in say [component[somegrid]] which contains a razor blob that configures say a quickgrid component, so the client side code would render say instead of the tag, then "execute" that like normal razor content.

The "MyGridComponent" component could then render say a QuickGrid instance but the existance of that on the page is entirely defined by my database.

Additional context

This may exist somewhere already but I can't find it anywhere. If this does great, can someone give me an example of how I might achieve this, otherwise what options do I have for achieving this goal?

My understanding thus far is that the entire front end is "known" and must be pre-compiled in to the blazor assemblies other than for example the data in a grid, but I can't dynamically decide from a DB where to put a grid tag on the page.

Is this possible?

TehWardy commented 10 months ago

I think I an what I need from some combination of components built in to the front end library plus "effective use of" this ...

@using MyNamespace.Components @((MarkupString)MyContentWithComponentTags)

... i'm digging through trying things out now.

SteveSandersonMS commented 10 months ago

I can't dynamically decide from a DB where to put a grid tag on the page.

You can. Check out the DynamicComponent, which lets you render any component by type, and optionally passing a dictionary of parameters.

So for example if your DB contained some blob of text like:

Hello this is some text [component(X)] goodbye

... then you could write some C# logic that parses your custom syntax. For example you could use a regex to split this into the string literals and the [...] blocks, and then:

@foreach(var item in parsedSegments)
{
    if (item.ItemType == ItemTypes.Literal)
    {
        <text>@item.TextContent</text>
    }
    else if (item.ItemType == ItemTypes.Component)
    {
        <DynamicComponent Type="@item.ComponentType" />
    }
}

... assuming you can parse your custom syntax into a series of objects that have properties like TextContent, ComponentType, and so on.

Obviously bear in mind the security implications of dynamic server-side content. Whatever controls the content fed into this system can more or less run arbitrary code on your server, if this is executing server-side. There's less risk if this is WebAssembly-only.

SteveSandersonMS commented 10 months ago

Note that there is a CMS built on Blazor that has features like this: https://www.oqtane.org/

TehWardy commented 10 months ago

Nice!!! Thanks Steve, this is amazing stuff!