Blazor with .NET 8: Creating a Reusable FormComponent in Blazor

Blazor is one of the great technologies for building interactive User Interfaces. This helps to create UI Components using C# that can run in the browser. Although there are various standard components already available, sometimes we need to create our own components as per the demand of the application. Thankfully, Blazor offers such a great facility using which we can create our own component. 

In this article, we will see the approach of creating a reusable form component in Blazor. This component will accept an entity object as input and then it will generate the Form UI.

Step 1: Open Visual Studio 2022 and create a new Blazor WebAssembly Standalone application. Name this application as Blazor_Components.

Step 2: In this Blazor project, add a new folder and name it Models. In this folder, add class files named Student, Employee, and PropertyMetadata where we will add code for classed named Student, Employee, and PropertyMetadata respectively. Listing 1 shows the code for these classes.


public class Student
{
    public int StudentId { get; set; }
    public string? StudentName { get; set; }
    public DateOnly JoiningDate { get; set; } = new DateOnly();

    public TimeOnly ClassTime { get; set; } = new TimeOnly();
    
}

  public class Employee
  {
      public int EmpNo { get; set; }
      public string? EmpName { get; set; }
      public string? DeptName { get; set; }
      public int Salary { get; set; }
  }
  
 public class PropertyMetadata
 {
     public string? PropertyName { get; set; }
     public string? PropertyType { get; set; }
 }

Listing 1: Student, Employee, and PropertyMetadata classes

Since we need to read properties of the Entity classes e.g. Student and Employee to generate the Component's UI dynamically we need the PropertyMetadata class for reading the name of the property and its type from the Entity class. 

Step 3: In the Pages folder add a new Component, name this component as FormComponent.razor. We will make this component a Template component that will render the UI elements based on the Entity Type passed to it. We will use @typeparam which will be used to define a Parameter that is passed to this component to generate the UI. In this component add the code as shown in Listing 2


@using System.Dynamic
@using System.Reflection
@using System.Text.Json
@using Blazor_Components.Model
@using System.ComponentModel
@using System.Linq.Expressions
@inject IJSRuntime js
@typeparam TModel


    <EditForm Model="@Model" OnValidSubmit="Save">
        @foreach (PropertyMetadata property in PropertyInfoList)
        {
            <div class="form-group">
                <label>@property.PropertyName</label>


                @if (property.PropertyType == "System.Int32")
                {
                    var value = Convert.ToInt32(@Model.GetType().GetProperty(property.PropertyName)?.GetValue(Model));

                    <InputNumber ValueExpression="ValueExpression<int>(property.PropertyName)"  
                    Value="@((int)value)"
                    ValueChanged=@((int v)=>Change(v,property.PropertyName)) class="form-control" />
                }
                @if (property.PropertyType == "System.String")
                {
                    var value = @Model.GetType().GetProperty(property.PropertyName)?.GetValue(Model)?.ToString();
                    <InputText ValueExpression="ValueExpression<string>(property.PropertyName)"
                    Value="@((string)value)"
                    ValueChanged=@((string v)=>Change(v,property.PropertyName)) class="form-control" />
                }

                @if (property.PropertyType == "System.DateOnly")
                {
                    var value = (DateOnly)@Model.GetType().GetProperty(property.PropertyName)?.GetValue(Model);

                    <InputDate Type=InputDateType.Date ValueExpression="ValueExpression<DateOnly>(property.PropertyName)"
                    Value="@((DateOnly)value)"
                    ValueChanged=@((DateOnly v)=>Change(v,property.PropertyName)) class="form-control" />
                }
                @if (property.PropertyType == "System.TimeOnly")
                {
                    var value = (TimeOnly)@Model.GetType().GetProperty(property.PropertyName)?.GetValue(Model);

                    <InputDate Type=InputDateType.Time ValueExpression="ValueExpression<TimeOnly>(property.PropertyName)"
                    Value="@((TimeOnly)value)"
                    ValueChanged=@((TimeOnly v)=>Change(v,property.PropertyName)) class="form-control" />
                }

            </div>
        }
        <div class="btn-group-lg">
            <input type="submit" value="Save" class="btn btn-success" />
            <input type="reset" value="Clear" class="btn btn-warning" />
        </div>

    </EditForm>


@code {
    [Parameter]
    public TModel? Model { get; set; }
    public List<PropertyMetadata>? PropertyInfoList { get; set; } = new List<PropertyMetadata>();
    private bool shouldRender = true;
    int counter = 0;
    [Parameter]
    public EventCallback<TModel> onSave { get; set; }

    protected override void OnInitialized()
    {
        foreach (var property in Model.GetType().GetProperties())
        {
            PropertyInfoList?.Add(new PropertyMetadata() { PropertyName = property.Name, PropertyType = property.PropertyType.ToString() });
        }
        base.OnInitialized();
    }


   

    void Change(object val, string pname)
    {
        var type = val.GetType();

        if (val.GetType() == typeof(System.Int32))
        {
            Model?.GetType()?.GetProperty(pname)?.SetValue(Model, Convert.ToInt32(val));
        }
        if(val.GetType() == typeof(System.String))
        {
            Model?.GetType()?.GetProperty(pname)?.SetValue(Model, val);
        }
        if (val.GetType() == typeof(System.DateOnly))
        {
            Model?.GetType()?.GetProperty(pname)?.SetValue(Model, val);
        }
        if (val.GetType() == typeof(System.TimeOnly))
        {
            Model?.GetType()?.GetProperty(pname)?.SetValue(Model, val);
        }

        var v = Model;

    }

    private Expression<Func<PropertyType>> ValueExpression<PropertyType>(string pname)
    {
        var expConstant = Expression.Constant(Model);
        var expression = Expression.Property(expConstant, pname);
        return Expression.Lambda<Func<PropertyType>>(expression);
    }


    async void Save()
    {
        await onSave.InvokeAsync(Model);
    }
     

    
}

Listing 2: The FormComponent Code

As shown in Listing 2, we have a typeparam TModel, this represents the Model that will be passed to the component. We will read all properties from the Model and we will store these properties and their type in the List of PropertyMetadata class in the OnInitialized() method. The most important section of the Listing 2 code is the ValueExpression() and Change() method. The ValueExpression() method will be used to bind the PropertyName of the model class to UI Elements so that when we change the value in the UI the Property value of the Property from the model class to set. The Change() method will detect the type of the data entered in the UI element and will make sure that the Property of the model class to set to an appropriate data type.  Listing 2 declares the EventCallback to emit an event to the container component of this component. We are emitting this event in the Save() method. To generate the UI we are using the EditForm component of the Blazor and to generate form input elements we are iterating through the List of PropertyMetadata and then based on the type of the property we are generating input components. We are generating input components like InputText, InutNumber, InputDate, etc. The EditForm submit is bound with the Save() method.

Since we have the FormCOmponent ready, we will use it for our Model.

Step 4: In the Pages folder, add a new Razor component and name it StudentComponent.razor.  In this component, we will add code as shown in Listing 3  

        


@page "/studentform"
@using Blazor_Components.Model
@using System.Text.Json
@inject IJSRuntime js
<h3>Student Component</h3>

<div class="container" >

    <FormComponent Model="@Student" onSave="((Student s)=>onStudentSaveReceive(s))" />
      
    
        <hr/>
    <div class="container">
    <strong>
    @JsonSerializer.Serialize(Student);
    </strong>
    </div>

    <FormComponent Model="@Employee" onSave="((Employee e)=>onEmployeeSaveReceive(e))" />
        <div class="container">
            <strong>
                @JsonSerializer.Serialize(Employee);
            </strong>
        </div>
   
</div>

@code {
    public Student? Student { get; set; } = new Student();
    public Employee? Employee { get; set; } = new Employee();
    public string? Name { get; set; } = "Mahesh";
 
    protected override void OnInitialized()
    {
        base.OnInitialized();
    }
    async void onStudentSaveReceive(Student studentwicw bt)
    {
        await js.InvokeVoidAsync("alert", $"Students { JsonSerializer.Serialize(student)}");
        
    }
    async void onEmployeeSaveReceive(Employee employee)
    {
        await js.InvokeVoidAsync("alert", $"Employee {JsonSerializer.Serialize(employee)}");
       
    }
    
}

Listing 3: The StudentComponent

As shown in Listing 3, we are using the FormComponent twice and passing the Student and the Employee models to it. We are also subscribing the OnSave event callback to FormComponent to make sure that the data entered in Form is emitted and the StudentComponent can listen to it. This data we will be showing on UI in JSON form as well as in JavaScript alert which we will use the IJSRntime interface that is used for JavaScript interoperability in Blazor.

Step 5: Modify the NavMenu.razor from the Layout folder by adding navigation for StudentComponet as shown in Listing 4

 <div class="nav-item px-3">
     <NavLink class="nav-link" href="studentform">
         <span class="bi bi-list-nested-nav-menu" aria-hidden="true"></span> Student Form
     </NavLink>
 </div>


Listing 4: The Student Form Navigation

Run the application and lick on the Link for Student Form we will have the Student Form Component loaded as shown in Figure 1




Figure 1: The FormComponent  in StudentForm  

That's it. We can enter data in Text Elements and click on the Save button so that the data gets emitted from FormComponent to StudentCompoent.

The code for this article can be downloaded from this link.

Conclusion: The Blazor is one of the great technologies that allows to create custom reusable UI using C#.


Popular posts from this blog

Uploading Excel File to ASP.NET Core 6 application to save data from Excel to SQL Server Database

ASP.NET Core 6: Downloading Files from the Server

ASP.NET Core 6: Using Entity Framework Core with Oracle Database with Code-First Approach