I have a simple MudBlazor MudSelect list
<MudSelect T="int" Label="Choose Coverage Type"
AnchorOrigin="Origin.BottomCenter" Variant="Variant.Outlined"
Margin="Margin.Dense" For="#(() => newLink.CoverageType)">
<MudSelectItem Value="0">No Coverage Selected</MudSelectItem>
<MudSelectItem Value="1">Auto</MudSelectItem>
<MudSelectItem Value="2">Cyber</MudSelectItem>
<MudSelectItem Value="3">D&O/EPL</MudSelectItem>
<MudSelectItem Value="4">General Liability</MudSelectItem>
<MudSelectItem Value="5">General Risk Management</MudSelectItem>
<MudSelectItem Value="6">Property</MudSelectItem>
</MudSelect>
In my class I have DataAnnotations for validation
//[RegularExpression ("^[1-6]$", ErrorMessage="A Coverage Type is Required")]
[Required]
public int CoverageType { get; set; }
I can get validation working on simple textboxes, but I want a validation error if the first item in the dropdown is slected "No Coverage Selected"
Does anyone know how to do this with the MudBlazor control? I have not found anything on their website.
Thanks for any help
You can try validating the field using the MudForm component.
Example:
https://try.mudblazor.com/snippet/QYQcaMkIrMqCiOQf
#using System.Text.RegularExpressions
#using System.ComponentModel.DataAnnotations
<MudForm #ref="form" class="my-3">
<MudSelect
T="int"
Label="Choose Coverage Type"
AnchorOrigin="Origin.BottomCenter"
Variant="Variant.Outlined"
Margin="Margin.Dense"
For="#(() => newLink.CoverageType)">
<MudSelectItem Value="0">No Coverage Selected</MudSelectItem>
<MudSelectItem Value="1">Auto</MudSelectItem>
<MudSelectItem Value="2">Cyber</MudSelectItem>
<MudSelectItem Value="3">D&O/EPL</MudSelectItem>
<MudSelectItem Value="4">General Liability</MudSelectItem>
<MudSelectItem Value="5">General Risk Management</MudSelectItem>
<MudSelectItem Value="6">Property</MudSelectItem>
</MudSelect>
</MudForm>
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="#(()=>form.Validate())">Validate</MudButton>
<MudButton Variant="Variant.Filled" Color="Color.Error" OnClick="#(()=>form.Reset())">Reset</MudButton>
<MudButton Variant="Variant.Filled" OnClick="#(()=>form.ResetValidation())">Reset Validation</MudButton>
#code {
private YourModel newLink = new YourModel();
private MudForm form;
class YourModel
{
[RegularExpression ("^[1-6]$", ErrorMessage="A Coverage Type is Required")]
[Required]
public int CoverageType { get; set; }
}
}
Related
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
I have a problem with displaying validation message in a Blazor app.
I have the following model (for testing purposes):
public class MyModel
{
[Required(ErrorMessage = "Amount is required")]
public int Amount { get; set; }
[Required(ErrorMessage = "NullableAmount is required")]
public int? NullableAmount { get; set; }
[RequiredIf(nameof(Foo), new[] { "bar" }, ErrorMessage = "Id is required")]
public int Id { get; set; }
[RequiredIf(nameof(Foo), new[] { "bar" }, ErrorMessage = "NullableId is required")]
public int? NullableId { get; set; }
public string Foo { get; set; }
}
I decorated the properties with the built-in RequiredAttribute, and created a custom RequiredIfAttribute, here's the code for that:
public class RequiredIfAttribute : ValidationAttribute
{
private readonly string _otherPropertyName;
private readonly string[] _otherPropertyValues;
public RequiredIfAttribute(string otherPropertyName, string[] otherPropertyValues)
{
_otherPropertyName = otherPropertyName;
_otherPropertyValues = otherPropertyValues;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var instance = validationContext.ObjectInstance;
var type = instance.GetType();
var propertyValue = type.GetProperty(_otherPropertyName)?.GetValue(instance);
if (!_otherPropertyValues.Contains(propertyValue?.ToString()))
{
return ValidationResult.Success;
}
if(value == null)
{
return new ValidationResult(ErrorMessage);
}
if(int.TryParse(value.ToString(), out var intValue))
{
if(intValue == 0)
{
return new ValidationResult(ErrorMessage);
}
}
return ValidationResult.Success;
}
}
I takes the name of the other property, the values for that property, and if the value of that other property matches one of the specified values, it checks if the property decorated with RequiredIf is null, or if it is an integer, 0. (I use it for a model with a nullable int property, which should not be null or 0 if the other property has a certain value).
On the WebAPI side it works fine, but in the Blazor form I have some problems. Here's my form:
<EditForm Model="MyModel" OnValidSubmit="OnSubmit">
<div style="display: flex; flex-direction: column; width: 300px;">
<DataAnnotationsValidator />
<ValidationSummary />
<label>
Foo:
<InputText #bind-Value="MyModel.Foo" />
</label>
<ValidationMessage For="#(() => MyModel.Foo)" />
<label>
Amount:
<InputNumber #bind-Value="MyModel.Amount" />
</label>
<ValidationMessage For="#(() => MyModel.Amount)" />
<label>
Null Amount:
<InputNumber #bind-Value="MyModel.NullableAmount" />
</label>
<ValidationMessage For="#(() => MyModel.NullableAmount)" />
<label>
Id:
<InputNumber #bind-Value="MyModel.Id" />
</label>
<ValidationMessage For="#(() => MyModel.Id)" />
<label>
Null Id:
<InputNumber #bind-Value="MyModel.NullableId" />
</label>
<ValidationMessage For="#(() => MyModel.NullableId)" />
<button type="submit">submit</button>
</div>
</EditForm>
I open the form, enter the value "bar" for Foo, than click submit, I get the following result:
Amount is valid, as it has the default value 0, and RequiredAttribute only checks for nulls.
NullAmount is invalid, it is shown in the ValidationSummary, the input field is also red, as it is invalid, and the validation message is also shown.
Id is invalid, as in the RequiredIf attribute I check int value against 0, it has the default 0 value, the error message is shown in the ValidationSummary, and here comes the interesting part, the input field is not red, and the validation message is not shown.
The same applies for the Nullable Id field, the error message is shown in the validation summary, but the field is not red, and the validation message is not shown.
Now, if I enter a valid value for Id and NullableId (the fields with my custom attribute), and after that I change Id to 0, and NullableId to empty, this is what happens:
Both input fields change to red, validation message is shown for both properties, but in the validation summary, both error messages are displayed twice.
Now, if I click submit, both input fields change to green, and the validation messages are gone, but still displayed in the summary.
I'm totally lost, don't know if I have a problem with my custom validation attribute, a problem in the Blazor component, or it is an unknown issue. The built-in Required attribute works fine, I just added it to the code to see if my form works with that.
Do you have any idea?
I've finally found the problem. In the validation attribute, when returning an error result, I have to pass memberName in the constructor.
return new ValidationResult(ErrorMessage, validationContext.MemberName)
Thank you so much #petyusa, I also ran into this issue and it was driving me nuts. I stumbled across your solution and it solved it immediately. However, I had to pass an array of string to the second argument (perhaps because I'm using .NET 5.0) instead of a simple string:
return new ValidationResult(ErrorMessage, new []{validationContext.MemberName});
I use the following to do validations on a form in blazor(server)
<EditForm Model="#place" OnValidSubmit="HandleValidSubmit" Context="placesEdit" >
<DataAnnotationsValidator />
<InputText #bind-Value="place.Name"/>
<ValidationMessage For="() => place.Name" />
</EditForm>
#{place=new Place(); }
the property Name as a [required] - Attribute. This works fine. When submitting the form I see the error message and the HandleValidSubmit isn't called
But when I try to do the same with a List the validation isn't happening. No error is displayed and HandleValidSubmit is called instead even if the requirements are not met:
<EditForm Model="#places" OnValidSubmit="HandleValidSubmit" Context="placesEdit" >
<DataAnnotationsValidator />
#foreach(var place in places) {
<InputText #bind-Value="place.Name"/>
<ValidationMessage For="() => place.Name" />
}
</EditForm>
#{places=new List<Place>(); }
What has do be done that the Validator also works in the loop?
Try if this helps:
Add the Microsoft.AspNetCore.Components.DataAnnotations.Validation NuGet package.
This is a pre-release package and latest version is 3.2.0-rc1.20223.4. There is a plan to include this on the native Blazor SDK but that version should work up to .NET 5. https://github.com/dotnet/aspnetcore/issues/22238#issuecomment-634266426
Replace your DataAnnotationsValidator with ObjectGraphDataAnnotationsValidator
You can check if your issue gets resolved with Step 2. If not, continue to Step 3.
Annotate your list property with ValidateComplexType.
You will need to create a class that will contain your list property.
check the docs for more information: https://learn.microsoft.com/en-us/aspnet/core/blazor/forms-validation#nested-models-collection-types-and-complex-types
If you're using IValidatableObject like me, the above solution won't work. The workaround is to create another property to link the validation into.
e.g.
public class MyModel : IValidatableObject
{
public List<Place> Places { get; } = new List<Place>();
public object PlacesValidation { get; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
var isValid = ...;
if (isValid)
yield return new ValidationResult("Places is invalid.", new[] { nameof(PlacesValidation ) });
}
}
<ValidationMessage For="() => Model.PlacesValidation"/>
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; }
}
I'm using the following code to render an editor for my model using ASP.NET MVC 3, it works perfect, except for I don't want the user to see or edit the "Id" field in my object.
<% using (Html.BeginForm())
{ %>
<%: Html.ValidationSummary(true, "Your input has errors, please correct and try again") %>
<%: Html.EditorForModel(Model)%>
<input type="submit" value="Update" />
<% } %>
In my model for the ID Field I have the following
[Display(AutoGenerateField = false)]
public int Id{ get; private set; }
Which granted is what I thought would work based on the description of the "AutoGenerateField" parameter. However this isn't working. I don't want to have to build the whole editor just for this one little oddity....
Use [ScaffoldColumn(false)] to hide fields
You could use the [HiddenInput] attribute:
[HiddenInput(DisplayValue = false)]
[Display(AutoGenerateField = false)]
public int Id { get; private set; }