Connecting Telerik Blazor Control to Entity and Ardalis.SmartEnum

380 views Asked by At

Using SmartEnum's https://github.com/ardalis/smartenum to handle Entity Framework 5.0 fields for things such as State, Country, Etc.

public sealed class StatesEnum : SmartEnum<StatesEnum>
{
    public static readonly StatesEnum NotSpecified = new StatesEnum("(Select One)", 0);
    public static readonly StatesEnum Alabama = new StatesEnum("Alabama", 1); 
    public static readonly StatesEnum Alaska = new StatesEnum("Alaska", 2);
    public static readonly StatesEnum Arizona = new StatesEnum("Arizona", 3);
    public static readonly StatesEnum Arkansas = new StatesEnum("Arkansas", 4);
    public static readonly StatesEnum California = new StatesEnum("California", 5);
    ...
 }

I am creating a Blazor Component for creating a job posting in our company's job board that uses these SmartEnum field types as one of the values in the entity. In the component I am using Telerik's Blazor DropDownList https://docs.telerik.com/blazor-ui/components/dropdownlist/overview to handle the fields that use these SmartEnum values. I have it correctly displaying the enum text and values using this code:

<TelerikDropDownList Id="dropStates" Data="@StatesEnum.List" TextField="@nameof(StatesEnum.Name)" ValueField="@nameof(StatesEnum.Value)" @bind-Value="@JobPostingVM.StateId"></TelerikDropDownList>

I am attempting to directly connect my View Model class (JobPostingVM) to the drop down list instead of creating a separate property for the selected value but I run into issues with the code complaining that you cant cast a SmartEnum to a type of int. Looking for a elegant solution outside of setting a property to store each selected value in this long form.

1

There are 1 answers

1
Eric R On

I am a Support Engineer with Telerik on the UI for Blazor team. I did some testing with the described scenario and found that it will require using a value external to the model.

For example, use a selectedValue variable as shown in the Bind to a Model documentation. The selectedValue variable can be set in the OnInitializedAsync method. Let me provide an example of this below.

Example

In the example, I am using a person model that is bound to a form with a SmartEnum for the SelectedState property. I am also setting default values in the model so that it displays the binding on page load. Let's go over each below.

Person Model

The person class is a basic CLR object with default values set for the collection properties.

public class Person
{
    // Added for test
    [Required(ErrorMessage = "Select a state")]
    public StatesEnum SelectedState { get; set; } = StatesEnum.Alabama; // Default Value

    // Original properties
    [Required(ErrorMessage = "Enter your first name")]
    [StringLength(10, ErrorMessage = "That name is too long")]
    public string FirstName { get; set; }

    [Required(ErrorMessage = "Enter your last name")]
    [StringLength(15, ErrorMessage = "That name is too long")]
    public string LastName { get; set; }

    [Required(ErrorMessage = "An email is required")]
    [EmailAddress(ErrorMessage = "Please provide a valid email address.")]
    public string Email { get; set; }

    public int? Gender { get; set; } = 4; // Default Value

    [Required]
    [Range(typeof(DateTime), "1/1/2020", "1/12/2030", ErrorMessage = "Value for {0} must be between {1:dd MMM yyyy} and {2:dd MMM yyyy}")]
    public DateTime StartDate { get; set; }

    [Required(ErrorMessage = "Choose the team and technology you want to work on")]
    public string PreferredTeam { get; set; } = "Blazor"; // Default Value
}
The SelectedState SmartEnum

The selected state is a property dervid from the SmartEnum implementation.

public sealed class StatesEnum : SmartEnum<StatesEnum>
{
    private StatesEnum(string name, int value) : base(name, value)
    {
    }

    public static readonly StatesEnum NotSpecified = new StatesEnum("(Select One)", 0);
    public static readonly StatesEnum Alabama = new StatesEnum("Alabama", 1);
    public static readonly StatesEnum Alaska = new StatesEnum("Alaska", 2);
    public static readonly StatesEnum Arizona = new StatesEnum("Arizona", 3);
    public static readonly StatesEnum Arkansas = new StatesEnum("Arkansas", 4);
    public static readonly StatesEnum California = new StatesEnum("California", 5);
}
Blazor Component

The custom form comonent then uses the OnInitializedAsync method to populate the model and set the selected value.

@page "/"

@using TBACS.DropDownListSmartEnumBinding.Models

<div class="container-fluid">
    <div class='row my-4'>
        <div class='col-12 col-lg-9 border-right'>
            <div class="row example-wrapper">
                <div class="col-xs-12 col-sm-6 offset-sm-3 example-col card p-4">
                    <EditForm Model="@person" OnValidSubmit="@HandleValidSubmit" class="k-form">
                        <DataAnnotationsValidator />
                        <fieldset>
                            <legend>User Details</legend>
                            <div class="form-group">
                                <label for="StatesDDL">
                                    <span>Select State <span class="k-required">*</span></span>
                                </label>
                                <TelerikDropDownList Data="@StatesEnum.List"
                                                     @bind-Value="@selectedValue"
                                                     TextField="@nameof(StatesEnum.Name)"
                                                     ValueField="@nameof(StatesEnum.Value)"
                                                     Id="StatesDDL"
                                                     Width="100%">
                                </TelerikDropDownList>
                            </div>

                            <div class="form-group">
                                <label for="FNameTb">
                                    <span>First Name <span class="k-required">*</span></span>
                                </label>
                                <TelerikTextBox @bind-Value="@person.FirstName" Width="100%" Id="FNameTb" />
                            </div>

                            <div class="form-group">
                                <label for="LNameTb">
                                    <span>Last Name <span class="k-required">*</span></span>
                                </label>
                                <TelerikTextBox @bind-Value="@person.LastName" Width="100%" Id="LNameTb" />
                            </div>

                            <div class="form-group">
                                <label for="GenderDDL">
                                    <span>Gender <span class="k-field-info">optional</span></span>
                                </label>
                                <TelerikDropDownList @bind-Value="@person.Gender" DefaultText="Select gender"
                                                     Data="@genders" TextField="Text" ValueField="Id"
                                                     Width="100%" PopupHeight="auto" Id="GenderDDL">
                                </TelerikDropDownList>
                            </div>

                            <div class="form-group">
                                <label for="EmailTb">
                                    <span>Email <span class="k-required">*</span></span>
                                </label>
                                <TelerikTextBox @bind-Value="@person.Email" Width="100%" Id="EmailTb" />
                            </div>

                        </fieldset>

                        <fieldset>
                            <legend>Team Preferences</legend>
                            <div class="form-group">
                                <label for="StartDateDP">
                                    <span>Start date <span class="k-required">*</span></span>
                                </label>
                                <TelerikDatePicker @bind-Value="@person.StartDate" Width="100%" Id="StartDateDP" />
                            </div>
                            <div class="form-group">
                                <label for="TeamDDL">
                                    <span>Preferred Team <span class="k-required">*</span></span>
                                </label>
                                <TelerikDropDownList @bind-Value="@person.PreferredTeam"
                                                     DefaultText="Preferred team" Id="TeamDDL"
                                                     Data="@Teams" Width="100%">
                                </TelerikDropDownList>
                            </div>
                        </fieldset>

                        <ValidationSummary />

                        @if (ShowSuccessMessage)
                        {
                        <div class="alert alert-info">
                            Application form submitted Successfully, we will get back to you
                        </div>
                        }

                        <div class="text-right">
                            <TelerikButton ButtonType="@ButtonType.Button" OnClick="@CancelForm">Cancel</TelerikButton>
                            <TelerikButton ButtonType="@ButtonType.Submit" Primary="true">Submit</TelerikButton>
                        </div>
                    </EditForm>
                </div>
            </div>

        </div>
    </div>
</div>

@code{
    Person person { get; set; }
    bool ShowSuccessMessage { get; set; }

    protected override Task OnInitializedAsync()
    {
        // Bind model on load
        GetDefaultPerson();

        return base.OnInitializedAsync();
    }

    async void HandleValidSubmit()
    {
        // implement actual data storage here
        ShowSuccessMessage = true;
        await Task.Delay(2000);
        ShowSuccessMessage = false;
        GetDefaultPerson();
        StateHasChanged();
    }

    void CancelForm()
    {
        GetDefaultPerson();
    }

    // External selected value primitive
    int selectedValue { get; set; } = 0;

    void GetDefaultPerson()
    {
        // in reality you may be pulling data from a service or authentication logic
        // not only for the form model, but also for the data sources below
        person = new Person()
        {
            StartDate = DateTime.Now.AddDays(7)
        };

        // Get the selected value from the model.
        selectedValue = person.SelectedState.Value;
    }

    IEnumerable<DropDownModel> genders = new List<DropDownModel>
    {
        new DropDownModel {Text = "female", Id = 1},
        new DropDownModel {Text = "male", Id = 2},
        new DropDownModel {Text = "other", Id = 3},
        new DropDownModel {Text = "I'd rather not say", Id = 4}
    };

    List<string> Teams = new List<string>
    {
        "Blazor", "Python", "Ruby", "Java", "JavaScript", "Assembler"
    };
}
Misc. Models

The other models for the example are built around the DropDown.

Menu Item

public class MenuItem
{
    public string Text { get; set; }
    public string Url { get; set; }
    public List<MenuItem> Items { get; set; }
}

DropDownModel

public class DropDownModel
{
    public int? Id { get; set; }
    public string Text { get; set; }
}

Wrapping Up

I believe the example answers the question. From what I understand, it seems like the result is what you were hoping to avoid, unfortunately. Let me know if you have any additional questions.

I hope this helps! Thank you for developing with UI for Blazor.

Eric D. Rohler