Click here to Skip to main content
14,662,524 members
Articles » Web Development » ASP.NET » Howto
Article
Posted 12 Oct 2020

Stats

10.5K views
11 bookmarked

Microsoft Blazor - Dynamic Content

Rate this:
5.00 (18 votes)
Please Sign up or sign in to vote.
5.00 (18 votes)
17 Oct 2020CPOL
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 also should mention that the technique I am going to share today can be used for client-side Blazor WebAssembly too.

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:

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:

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:

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:

page "/counter"
@inject ControlService _controlService

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

@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:

@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:

@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 4Implementation - 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:

@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.

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

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

Comments and Discussions

 
Questionmaster/detail Pin
Laurentiu LAZAR19-Oct-20 23:22
MemberLaurentiu LAZAR19-Oct-20 23:22 
AnswerRe: master/detail Pin
euklad20-Oct-20 11:35
professionaleuklad20-Oct-20 11:35 
GeneralRe: master/detail Pin
Laurentiu LAZAR21-Oct-20 1:15
MemberLaurentiu LAZAR21-Oct-20 1:15 
GeneralRe: master/detail Pin
Laurentiu LAZAR21-Oct-20 2:27
MemberLaurentiu LAZAR21-Oct-20 2:27 
AnswerRe: master/detail Pin
euklad20-Oct-20 23:12
professionaleuklad20-Oct-20 23:12 
Questionany feedback on my post? Pin
euklad15-Oct-20 22:26
professionaleuklad15-Oct-20 22:26 
QuestionWell done! Pin
defwebserver15-Oct-20 4:33
Memberdefwebserver15-Oct-20 4:33 
QuestionMessage Closed Pin
12-Oct-20 2:26
MemberMember 1496215212-Oct-20 2: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.