Search code examples
c#classblazordry

Can a generic Blazor Component handle multiple classes with the same structure?


I have created a Blazor Component to handle editing a database record which is defined by a class. I would like this component to be reused to edit other records - that have the same structure, but different field names. For example, here is one of my class definitions:

public class AccountTypeCode
{
    public int AccountTypeId { get; set; }
    [Required]
    public string AccountType { get; set; } = string.Empty;
    [Required]
    public string? AccountTypeDescription { get; set; } = string.Empty;
    public string AdditionalInformation { get; set; } = string.Empty;
    public int DisplayOrder { get; set; } = 0;
    public DateOnly? DateTerminated { get; set; }
}

Another class might refer to CustomerTypeId, CustomerType, CustomerTypeDescription, etc.

In my component's @code section I currently have code like:

if (recordEdit is not null && !string.IsNullOrEmpty(recordEdit.AccountType))
{
    onSaveButton(recordEdit.AccountType);
}

Where recordEdit is of type AccountTypeCode.

What I want to do is replace, for example, recordEdit.AccountType with a generic recordEdit.RecordType

By doing this I can reuse my component many times without having to write a new component for each table that I want to edit.

This is what my component looks like:

enter image description here


Solution

  • You can use an Interface or a generic type and use Reflection.

    For example, create an IRecordType Interface:

    public interface IRecordType
    {
        string RecordType { get; set; }
        string? RecordDescription { get; set; }
        string AdditionalInformation { get; set; }
        int DisplayOrder { get; set; }
        DateOnly? DateTerminated { get; set; }
    }
    

    Now you should implement it your related classes (for the AccountTypeCode and CustomerTypeCode if that's its class name):

    public class AccountTypeCode : IRecordType
    {
        public int AccountTypeId { get; set; }
        [Required]
        public string AccountType { get; set; } = string.Empty;
        [Required]
        public string? AccountTypeDescription { get; set; } = string.Empty;
        public string AdditionalInformation { get; set; } = string.Empty;
        public int DisplayOrder { get; set; } = 0;
        public DateOnly? DateTerminated { get; set; }
    
        // here is the implementation of the interface properties
        string IRecordType.RecordType
        {
            get => AccountType;
            set => AccountType = value;
        }
        string? IRecordType.RecordDescription
        {
            get => AccountTypeDescription;
            set => AccountTypeDescription = value;
        }
    }
    

    Now in your razor component add the @typeparam directive to basically declares a generic type parameter named TRecordType for the component. It allows the component to work with a specific type that will be specified when the component is used:

    @using System.Reflection
    @typeparam TRecordType where TRecordType : class, IRecordType, new()
    
    @*
       Some razor markup...
    *@
    
    @code{
    private TRecordType recordEdit = new();
    }
    

    So generally this allows you to use Interface's RecordType and RecordDescription properties instead of the specific classe's properties (eg. AccountType and AccountTypeDescription).

    For example in your Form component:

    <div>
            <label>Record Type *</label>
            <InputText @bind-Value="recordEdit.RecordType" />
        </div>
        <div>
            <label>Record Description *</label>
            <InputText @bind-Value="recordEdit.RecordDescription" />
        </div>
    

    Also please conside refactoring your classes and properties names, it is a good practice to name your id property just as Id, your class name from AccountTypeCode to AccountType and AcountType string in this context to AccountTypeName string.

    Hope this helps.

    Edit

    Use or call your component this way:

    <GenericEditComponent TRecordType="AccountTypeCode" />
    <GenericEditComponent TRecordType="CustomerTypeCode" />