Connecting Telerik Blazor Control to Entity and Ardalis.SmartEnum - telerik

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.

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

Related

.net core razor page #Html.Displayfor showing a random number

I'm using .net core (.net 5) to create a calculator.
Here's my model:
public class VogelsRulePlusModel
{
[Required]
[Range(0,30)]
public double SpherePower { get; set; }
[Required]
[Range(-30,0)]
public double CylinderPower { get; set; }
[DisplayFormat(DataFormatString = "{0:0.00}")]
public double Result { get; set; }
}
To display the output of the calculation, I'm using the the following tag "helper"
<div class="form-group">
<label asp-for="Result" class="control-label">#Html.DisplayFor(m => Model.Result)</label>
</div>
For some reason, when the page renders, I get a random number.
Here's the html this tag "helper" creates:
<div class="form-group">
<label class="control-label" for="Result">6.00</label> <-- ??
</div>
Does anyone know why this is happening and how to make it stop?
UPDATE: I'm such a doofus...again, I had the wrong controller in here.
This is what I'm using for the view.
public IActionResult VogelsRulePlus(VogelsRulePlusModel baseCurve)
{
if (!ModelState.IsValid)
return BadRequest(ModelState);
baseCurve.Result = BaseCurve.VogelsRulePlus(baseCurve.SpherePower, baseCurve.CylinderPower);
return View(baseCurve);
}

How to convert selected datetime picker value to timespan in Blazor

When reading from the database it's getting a value. But when I tried to add a new time slot as time (want to convert this to timespan as the database field is declared as Time(7) so the format is hh:mm:ss:fffffff) I found it difficult to save that.
I tried datetimepicker, then I tried time also, but its getting error.
My model class:
public class Model
{
public short TimeSlotID { get; set; }
public TimeSpan FromTime { get; set; }
public TimeSpan ToTime { get; set; }
}
Timerazor.razor
<div class="row">
<div class="col">
<div id="datetimepicker-control">
<!--<input type="time" name="FromTime" value="#item.FromTime.ToString("hh mm tt")" /> -->
</div>
<div class="col">
<input type="time" name="FromTime" value="#item.ToTime.ToString("hh mm tt")" />
</div>
#code
{
// i dont know how to pass the timespan value while inserting the time. here.
int rows = timeslotdal.InsertTimeSlot(modeltimeSlot);
}
for inserting
public int Insert(Model model)
{
List<SqlParameter> parameters = new List<SqlParameter>()
{
new SqlParameter("#TimeSlotID", model.TimeSlotID),
new SqlParameter("#FromTime", model.FromTime),
new SqlParameter("#ToTime", model.ToTime),
};
return db.ExecuteNonQuery("storedproceduremodel", parameters);}
}
You cannot bind a <input type='time'> control to a C# TimeSpan directly, you will need to add an intermediate step of binding to a DateTime and then pass the time element of the value through to the TimeSpan property.
See https://stackoverflow.com/a/54869699/41403 for an example of how to do this.

ASP.Net MVC 6 PartialView model binding confusion

Background
We need to submit model with items. For that purpose, we make this complex model:
public class PaymentViewModel
{
public decimal? Payment { get; set; }
public string Description { get; set; }
public List<SplittedPaymentViewModel> SplittedPayment { get; set; } = new List<SplittedPaymentViewModel>();
}
public class SplittedPaymentViewModel
{
public short SplittedPaymentId { get; set; }
public decimal? Payment { get; set; }
public string Description { get; set; }
}
For rendering html, we this two views, one regular: CreatePayment
#model ZevApp.ViewModels.Record.PaymentViewModel
...
<input class="form-control" asp-for="Payment" type="number" />
<input class="form-control" asp-for="Description" />
...
<div class="panel-body" id="SplittedPayments">
#foreach (var spItem in Model.SplittedPayment)
{
#Html.Partial("SplittedPaymentPartial", spItem);
}
...
</div>
And the other Partial: SplittedPaymentPartial
#model ZevApp.ViewModels.Record.SplittedPaymentViewModel
...
<input class="form-control" asp-for="Payment" type="number" />
<input class="form-control" asp-for="Description" />
...
As you can see, from the first view, we call partial view and pass them item by item form the SplittedPayment list.
Unexpectedly behavior
We run the code but unexpectedly behavior is occur:
Each partial view bind Payment and Description form parent PaymentViewModel, not from a passed item?
Does anybody know what is wrong?
Each view define model at the beginning of the file.
From the controller we return PaymentViewModel, and for a test, there are tree SplittedPaymentViewModels. But we can't see values from the items.
I found that problem was CreatePayment.cshtml (thanks to the people from MVC community). It doesn't provide any information about the spItem expression when invoking the partial view.
var savePrefix = ViewData.TemplateInfo.HtmlFieldPrefix;
for (var i = 0; i < Model.SplittedPayment.Count; i++)
{
ViewData.TemplateInfo.HtmlFieldPrefix = Html.NameFor(m => m.SplittedPayment[i]);
var spItem = Model.SplittedPayment[i];
#Html.Partial("SplittedPaymentPartial", spItem);
ViewData.TemplateInfo.HtmlFieldPrefix = savePrefix;
}
You can find full description on https://github.com/aspnet/Mvc/issues/4106

MVC4 client validation not displaying

When I submit my form, The client validation is working, but it is not displaying the error messages for the invalid form fields...
The Model
public class Blog : MainDbContext
{
public int Id { get; set; }
[Display(Name="Author")]
public int Profile { get; set; }
[Required(AllowEmptyStrings = false, ErrorMessage = "Title is required.")]
public string Title { get; set; }
[Required(AllowEmptyStrings = false, ErrorMessage = "At least one Tag is required.")]
public string Tags { get; set; }
}
The ViewModel
public class BlogEditViewModel
{
public BlogEditViewModel(Blog blogItem, IEnumerable<SelectListItem> staffList)
{
this.BlogItem = blogItem;
this.StaffList = staffList;
}
public Blog BlogItem { get; set; }
public IEnumerable<SelectListItem> StaffList { get; private set; }
}
The View
<section>
#Html.LabelFor(model => model.BlogItem.Tags)
#Html.EditorFor(model => Model.BlogItem.Tags, null, "Tags")
#Html.ValidationMessageFor(model => model.BlogItem.Tags)
</Section>
The Layout
<script src="/Scripts/jquery-1.7.1.js" type="text/javascript"></script>
<script src="/Scripts/jquery.unobtrusive-ajax.js" type="text/javascript"></script>
<script src="/Scripts/jquery.validate.js" type="text/javascript"></script>
<script src="/Scripts/jquery.validate.unobtrusive.js" type="text/javascript"></script>
The Output (on form submitted)
<input type="text" value="ok" name="Tags" id="Tags" data-val-required="At least one Tag is required." data-val="true" class="text-box single-line input-validation-error">
<span data-valmsg-replace="true" data-valmsg-for="BlogItem.Tags" class="field-validation-valid"></span>
What i expect is the above Span tag to contain the error message as defined n the Model.
The problem I suspect relates to the naming in the EditorFor, as you can see I use an overload to specify the 'htmlfieldname', as without this the form data fails to map the form fields with the model & fails to save the submitted from data.
If I dont use the overloaded EditorFor, the validation works, but as mentioned above the, the form data fails to map the form fields with the model
how do I get the validation to work?
Thanks
kb
Add this script in your View and client side validation is working fine,
<script src="#Url.Content("~/Script/jquery.validate.min.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Script/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
I think issue is you have to remove null, "Tags"
#Html.EditorFor(model => Model.BlogItem.Tags)
or you have to assign null, "Tags" to
#Html.ValidationMessage("Tags")
Ex:
View
#Html.LabelFor(m => m.Name)
#Html.TextBoxFor(m => m.Name, new { Name="asd"})
#Html.ValidationMessage("asd")
Model
public class RegisterModel
{
[Required(ErrorMessage = "Name is required.")]
public string Name { get; set; }
}

MVC3 ModelState fails after changing to using a ViewModel

I've changed my controller to pass a VoucherBatchViewModel instead of a VoucherBatch
[HttpPost]
public ActionResult Edit(Guid id, VoucherBatchViewModel voucherBatchViewModel)
{
if (!ModelState.IsValid)
{
SetupDropDowns();
And the vm:
public class VoucherBatchViewModel
{
public VoucherBatch VoucherBatchInVM { get; set; }
public string CreationReference { get; set; }
public int NumberToMove { get; set; }
public int VoucherCodeLength { get; set; }
}
Question: Why is my ModelState invalid? The drop down has not populated the correct field in the ViewModel. The html does produce the correct
<div class="editor-field">
#Html.DropDownList("VoucherProviderId",
new SelectList(ViewBag.VoucherProviders as System.Collections.IEnumerable,
"Id", "Name", Model.VoucherBatchInVM.VoucherProviderId))
and Edit get contains this which I use to populate the DropDown.
ViewBag.VoucherProviders = uow.VoucherProviders.OrderBy(v => v.Name).ToList();
Html:
<select id="VoucherProviderId" name="VoucherProviderId"><option value="0469f9ba-c4ea-401a-86f1-095208c6a7fb">Name</option>
<option selected="selected" value="e0aeed44-3574-46f1-a493-0a6a87948942">Voucher Provider 1</option>
<option value="5abe1158-282b-4330-9b11-01de503a2f16">Voucher Provider 2</option>
Can't see any VoucherProviderId property in your VoucherBatchViewModel. So your HTML is not valid. It should be:
<select id="VoucherProviderId" name="VoucherBatchInVM.VoucherProviderId">
...
</select>
And to achieve this markup you I would recommend you using strongly typed helpers:
#Html.DropDownListFor(
x => x.VoucherBatchInVM.VoucherProviderId,
new SelectList(ViewBag.VoucherProviders as System.Collections.IEnumerable, "Id", "Name")
)
And please replace this ViewBag which is making me vomit with a strongly typed model property:
#Html.DropDownListFor(
x => x.VoucherBatchInVM.VoucherProviderId,
new SelectList(Model.VoucherProviders, "Id", "Name")
)

Resources