Click here to Skip to main content
15,121,597 members
Articles / Web Development / Blazor
Article
Posted 12 Oct 2020

Stats

29.2K views
32 bookmarked

Microsoft Blazor - Dynamic Content

Rate me:
Please Sign up or sign in to vote.
4.93/5 (37 votes)
20 Jan 2021CPOL5 min read
Demonstration of this amazing technology that Microsoft released two years ago as a part of ASP.NET Core - Blazor!
What happens if you don't know how your page should look at development time and its content and page controls should be shown based on data that can be changed dynamically by a user or a site administrator? You need to generate your page dynamically.

Introduction

If you ask me why I am so excited about this technology, I will answer - well, it’s because I hate JavaScript (like many others), and this time Microsoft delivered something so useful and powerful for Web developers, that now it becomes a real alternative to JavaScript and all these bloatware frameworks created to extend its pointless life (Angular, React, etc.)

Working at my current place (Pro Coders), we used Blazor from its preview release and tried many amazing things, mostly with Blazor Server-Side that is built on top of SignalR. I should also mention that the technique I am going to share today can be used for client-side Blazor WebAssembly too.

If you are interested there is another article about dynamic forms:

Dynamic Content

As you may know, to define Blazor UI, we use Razor pages - technology that was around for about 5 years, and I will not waste your time digging into Razor, assuming it is simple and well known for you.

What happens if you don't know how your page should look at development time and its content and page controls should be shown based on data that can be changed dynamically by a user or a site administrator? You need to generate your page dynamically.

Let's talk about an example from real life- content management systems where the site administrator can decide which fields can be populated by a user in the user profile page and let me formulate a user story that we will implement today.

User Story #1: Dynamic UI Generation

  • Generate a UI page based on a control list received from a service
  • Support two types of controls: TextEdit and DateEdit
  • Control list has properties for UI generation: Label, Type, Required

Implementation - Create a Project

Let's create a new Blazor project and let's keep it simple. Open Visual Studio 2019 and click Create a new project, then find Blazor template and click Next:

Image 1

Enter the project name, select Blazor Server App on the next page, and click Create:

Image 2

You will see a new solution created for you and it will contain several pages that Visual Studio template added for learning purposes. You can build and run the solution to see the created application in a browser, by clicking on the play button (green triangle).

Image 3

Implementation - Define Model and Service

I prefer starting from the defining model, so create a new class ControlDetails.cs and put the following code into it:

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace DemoDynamicContent
{
    public class ControlDetails
    {
        public string Type { get; set; }
        public string Label { get; set; }
        public bool IsRequired { get; set; }
    }
}

Now we can create a service class that, for now, will return some test data, so create ControlService.cs and add this code:

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace DemoDynamicContent
{
    public class ControlService
    {
        public List<ControlDetails> GetControls()
        {
            var result = new List<ControlDetails>();
            result.Add(new ControlDetails { Type = "TextEdit", 
                                            Label = "First Name", IsRequired = true });
            result.Add(new ControlDetails { Type = "TextEdit", 
                                            Label = "Last Name", IsRequired = true });
            result.Add(new ControlDetails { Type = "DateEdit", 
                                            Label = "Birth Date", IsRequired = false });
            return result;
        }
    }
}

In this code, we specify three controls that we want to show on our dynamic page: First Name, Last Name, and Birth Date.

The last bit that we need to do is to register our service for Dependency Injection, so simply open Startup.cs, locate [ConfigureServices(IServiceCollection services)] method and add a line of code at the bottom, so it looks like:

C#
public void ConfigureServices(IServiceCollection services)
{
    services.AddRazorPages();
    services.AddServerSideBlazor();
    services.AddSingleton<WeatherForecastService>();
    // added line for ControlService
    services.AddSingleton<ControlService>();
}

Implementation - Dynamic page

Let's reuse Counter.razor page (already created by Visual Studio template) for simplicity. We need to delete all lines except the first one and start adding our own code, firstly use Dependency Injection to inject our service:

Razor
page "/counter"
@inject ControlService _controlService

Now we need to execute _controlService and iterate through the returned list of controls.

Razor
@page "/counter"
@inject ControlService _controlService

@foreach (var control in _controlService.GetControls())
{

}

For each control, we will want to show a label that is marked by * if it is a required control:

Razor
@page "/counter"
@inject ControlService _controlService

@foreach (var control in _controlService.GetControls())
{
    @if (control.IsRequired)
    {
        <div>@(control.Label)*</div>
    }
    else
    {
        <div>@control.Label</div>
    }
}

The final bit is to render control of a particular type in a switch statement:

Razor
@page "/counter"
@inject ControlService _controlService

@foreach (var control in _controlService.GetControls())
{
    @if (control.IsRequired)
    {
        <div>@(control.Label)*</div>
    }
    else
    {
        <div>@control.Label</div>
    }

    @switch (control.Type)
    {
        case "TextEdit":
            <input required="@control.IsRequired">
            break;

        case "DateEdit":
            <input required="@control.IsRequired" type="date">
            break;
    }
}

Implementation - Running

Now you can compile and run your solution. On the appeared browser window, click Counter menu item to see the result:

Image 4

Implementation - Adding Control Binding

Extending this idea further, we will need to bind generated razor controls to properties in our razor page and store them in a Dictionary for example, where Key is Label and Value is the razor control value.

Here, I added the @code section that has service execution logic and all the properties and events that we bind to controls. The bindings work in both directions.

The resulting code will look like:

Razor
@page "/counter"
@inject ControlService _controlService

@foreach (var control in ControlList)
{
    @if (control.IsRequired)
    {
        <div>@(control.Label)*</div>
    }
    else
    {
        <div>@control.Label</div>
    }

    @switch (control.Type)
    {
        case "TextEdit":
            <input @bind-value="@Values[control.Label]" required="@control.IsRequired" />
            break;

        case "DateEdit":
            <input type="date" value="@Values[control.Label]" 
             @onchange="@(a => ValueChanged(a, control.Label))" 
             required="@control.IsRequired" />
            break;
    }
}

<br/>
<button @onclick="OnClick">Submit</button>

@code
{
    private List<ControlDetails> ControlList;
    private Dictionary<string, string> Values;

    protected override async Task OnInitializedAsync()
    {
        ControlList = _controlService.GetControls();
        Values = ControlList.ToDictionary(c => c.Label, c => "");
    }

    void ValueChanged(ChangeEventArgs a, string label)
    {
        Values[label] = a.Value.ToString();
    }

    string GetValue(string label)
    {
        return Values[label];
    }

    private void OnClick(MouseEventArgs e)
    {
        // send your Values
    }
}

If you run your solution now, having a breakpoint in the OnClick method, then enter values in page controls, and click the Submit button, you will see the entered values in the watch panel at the bottom:

Image 5

It is brilliant, we store entered values in a Dictionary and now can supply it to a service that saves values to a database.

The full solution with the resulting code can be found on my GitHub page:

Summary

In this exercise, we implemented a UI page that generates controls using data received from a service. The Data controls the Presentation. Supplying data stored in a database, we present content to users. If we want to present slightly different content - we need to only change the data in our database and users see our changes, no recompilation, or deployment required.

But this solution has a small limitation - it will support only those controls that we specify in the [switch] statement on the razor page. Each time we need to show a control that is not specified in the [switch] statement, we need to extend it, adding code with new control and recompile solution.

Next Challenge - Custom Controls for Dynamic Content

Is it possible to create an externally extendable dynamic page, which will support all controls that we can add later in a separate assembly without the recompilation of our dynamic page?

Yes, it is possible - using a technique that is shared in my next blog.

There is a similar apporach uisng open-source library:

Thank you and see you next time!

History

  • 12th October, 2020: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Ev Uklad
Software Developer (Senior) Pro Coders
Australia Australia
Programming enthusiast and the best practices follower

Comments and Discussions

 
Questiondynamic HTML decoration? Pin
sorvik14-Jun-21 2:40
Membersorvik14-Jun-21 2:40 
GeneralMy vote of 5 Pin
LightTempler21-Jan-21 11:12
MemberLightTempler21-Jan-21 11:12 
Questionmaster/detail Pin
Laurentiu LAZAR20-Oct-20 0:22
MemberLaurentiu LAZAR20-Oct-20 0:22 
AnswerRe: master/detail Pin
Ev Uklad20-Oct-20 12:35
professionalEv Uklad20-Oct-20 12:35 
GeneralRe: master/detail Pin
Laurentiu LAZAR21-Oct-20 2:15
MemberLaurentiu LAZAR21-Oct-20 2:15 
GeneralRe: master/detail Pin
Laurentiu LAZAR21-Oct-20 3:27
MemberLaurentiu LAZAR21-Oct-20 3:27 
AnswerRe: master/detail Pin
Ev Uklad21-Oct-20 0:12
professionalEv Uklad21-Oct-20 0:12 
AnswerRe: master/detail Pin
Ev Uklad10-Nov-20 12:13
professionalEv Uklad10-Nov-20 12:13 
Questionany feedback on my post? Pin
Ev Uklad15-Oct-20 23:26
professionalEv Uklad15-Oct-20 23:26 
AnswerRe: any feedback on my post? Pin
lucas-smcaetano22-Jan-21 10:00
Memberlucas-smcaetano22-Jan-21 10:00 
GeneralRe: any feedback on my post? Pin
Ev Uklad22-Jan-21 10:31
professionalEv Uklad22-Jan-21 10:31 
QuestionWell done! Pin
defwebserver15-Oct-20 5:33
Memberdefwebserver15-Oct-20 5:33 
QuestionMessage Closed Pin
12-Oct-20 3:26
MemberMember 1496215212-Oct-20 3:26 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.