DEV Community

Mohsen Kokabi
Mohsen Kokabi

Posted on

Learning Blazor with creating dynamic form

Introduction, Blazor 3 Models:

WebAssembly

As it's name says this model is utilizing the WebAssembly. The WebAssembly is a modern browser's feature which in short is the capability to support compiled high level languages such as C++ as binary instruction to run directly in the browser. This feature allows developers to use languages other than JavaScript for UI. Microsoft's implementation is instructing DotNet Core in WebAssembly to allow C# directly be used in the browser. At run time the DotNet Core and the project assemblies would get downloaded.
dlls

The template for this model is blazorwasm. If you don't have it, install it using:

dotnet new -i Microsoft.AspNetCore.Blazor.Templates::3.0.0-preview9.19424.4
Enter fullscreen mode Exit fullscreen mode

The main part of code is in defining the host in Program.cs

public static IWebAssemblyHostBuilder CreateHostBuilder(string[] args) =>
    BlazorWebAssemblyHost.CreateDefaultBuilder()
                .UseBlazorStartup<Startup>();
Enter fullscreen mode Exit fullscreen mode

The startup only has to define the starting component in Configure method:

app.AddComponent<App>("app");
Enter fullscreen mode Exit fullscreen mode

Similar to client side frameworks app component is used in the index.html

<app>Loading...</app>
Enter fullscreen mode Exit fullscreen mode

WebAssembly Hosted

This model is using the same concepts as above but it would create one separate DotNet core application to host and another DotNet standard application which has all the client side elements and components.
The template for this model is blazorwasm --hosted.
The Server application would use the Startup of the client project in Configure method.

app.UseClientSideBlazorFiles<Client.Startup>();
Enter fullscreen mode Exit fullscreen mode

The client startup is just like the one in the above model. It would also add an endpoint mapping:

endpoints.MapFallbackToClientSideBlazor<Client.Startup>("index.html");
Enter fullscreen mode Exit fullscreen mode

Blazor Server

In this model, the application would be running entirely on DotNet Core on server and all the UI interaction would communicate to the server using SignalR. This model has some downsides like the application would be very chatty as every UI interaction would go back to server and also server has to maintain the state for each client so special care is needed to manage the memory consumption on the server.
The template for creating this model is blazorserver

Sample

In this sample, we are going to create a form based on the layout stored on the server. The 2nd model (WebAssembly Hosted) is used as it has a better separation of client and server and also has the shared elements in separated project.

dotnet new blazorwasm --hosted -o Hosted
Enter fullscreen mode Exit fullscreen mode

Shared classes

Add these classes to the Hosted.Shared project:

public class Element
{
    public virtual string ElementType { get; set; }

    public string Name { get; set; }

    public string Label { get; set; }
}

public class TextInput : Element
{
    public override string ElementType { get => "TextInput"; }

    public string PlaceHolder { get; set; }
}

public class RadioButton : Element
{
    public override string ElementType { get => "RadioButton"; }

    public Dictionary<string, string> Options { get; set; }

}
public class Form
{
    public List<Element> Elements { get; set; }
}
Enter fullscreen mode Exit fullscreen mode

Server side

In Hosted.Server project edit the ConfigureServices method from

services.AddMvc().AddNewtonsoftJson();
Enter fullscreen mode Exit fullscreen mode

to

services.AddMvc().AddNewtonsoftJson(options =>
{
    options.SerializerSettings.TypeNameHandling = Newtonsoft.Json.TypeNameHandling.Auto;
});

Enter fullscreen mode Exit fullscreen mode

This will add the types of unknown elements while serialization:
json

Now we can create a controller to return a Form:

[Route("[controller]")]
[ApiController]
public class FormController : ControllerBase
{
    [HttpGet]
    public Form Get()
    {
        return new Form 
        { 
            Elements = new List<Element> 
            { 
                new TextInput 
                { 
                    Name = "txtName",  Label = "Name", PlaceHolder="Enter your name"
                },
                new RadioButton 
                { 
                    Name = "radGender", Label = "Gender", 
                    Options = new Dictionary<string, string> { { "M", "Male" }, {"F", "Female"} }
                }
            }
        };
    }
}
Enter fullscreen mode Exit fullscreen mode

Client side

To demonstrate the components which are the core of Blazor we are creating a corresponding component for each element. Component's name is coming from their file name (it should starts with Capital letter). They are a Html with a @code section which would have all the events, properties, logic and other things. While the syntax is similar to ASP.Net razor, under the hood it's technically very different.

We are creating a folder called Components at the same level of Pages. For this sample we would only create TextInput.razor and RadioButton.razor.

TextInput.razor

<input type="text" name="@Name" placeholder="@PlaceHolder">

@code {
    [Parameter]
    public string Name { get; set; }

    [Parameter]
    public string PlaceHolder { get; set; }
}
Enter fullscreen mode Exit fullscreen mode

RadioButton.razor

@foreach (var option in @Options)
{
    <input type="radio" id="@option.Key" name="@Name" value="@option.Key">
    <label for="male">@option.Value</label>
    <br>
}

@code {
    [Parameter]
    public string Name { get; set; }

    [Parameter]
    public Dictionary<string, string> Options { get; set; }
}
Enter fullscreen mode Exit fullscreen mode

Add a page called DynamicForm.razor under Pages folder.

@page "/dynamicform"
@using Newtonsoft.Json
@using Hosted.Shared
@inject HttpClient Http

<h1>Dynamic form</h1>


@if (form == null)
{
    <p><em>Loading...</em></p>
}
else
{
  <table class="table">
    <tbody>
      @foreach (var element in form.Elements)
      {
        <tr>
          <td>@element.Label</td>
          @switch (element.ElementType)
          {
            case "TextInput":
            {
              <td><Hosted.Client.Components.TextInput Name="@element.Name" PlaceHolder="@((element as TextInput).PlaceHolder)" /></td>
              break;
            }
            case "RadioButton":
            {
              <td><Hosted.Client.Components.RadioButton Name="@element.Name" Options="@((element as RadioButton).Options)" /></td>
              break;
            }
            default:
            {
              <td>Unknow control</td>
              break;
              }
            }
        </tr>
      }
    </tbody>
  </table>
}

@code {
    Form form;

    protected override async Task OnInitializedAsync()
    {
        var st = await Http.GetStringAsync("Form");

        form = JsonConvert.DeserializeObject<Form>(st, settings: new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.Auto });
    }

}
Enter fullscreen mode Exit fullscreen mode

Use this page in NavMenu.razor by adding a li as:

<li class="nav-item px-3">
    <NavLink class="nav-link" href="dynamicform">
        <span class="oi oi-list-rich" aria-hidden="true"></span> dynamic form
    </NavLink>
</li>
Enter fullscreen mode Exit fullscreen mode

The output would be like:
running

The code is available on github

Top comments (1)

Collapse
 
blyzer profile image
Enver Daniel Francisco Baez

Every example I see with blazor Dynamic form is in blazor server, is for some reason in particular?