Fluent Validation not Working in Bootstrap 4 Modal - asp.net-core

I was using data annotation validation on my Razor Pages which worked fine. For more complex valitaion I am using Fluent Validation, which works fine for everything unless it is in a Modal.
public class MyModel
{
[RegularExpression(#"^Test|Prod", ErrorMessage = "Please Choose A Type")]
public string Type { get; set; }
}
public class MyValidator : MyValidator<MyModel>
{
public MyValidator()
{
RuleFor(x => x.Username)
.Empty()
.When(support => x.Found == false)
.WithMessage("Not Found");
RuleFor(x => x.IsComplete)
.Must(x => x.Equals(true))
.WithMessage("Please confirm");
}
}
Below is example modal code - I have simiplified it so sorry for any mistakes. The code in the main body validates fine, but nothing I put in a modal with Fluent Validation works - it is just submitted without checks. I have several modals all doing the same thing. Data annotation validation works fine.
<div id="" class="modal" tabindex="-1" role="dialog">
<div class="modal-dialog-700" role="document">
<div class="modal-content modal-size">
<div class="modal-header">
<h6 class="modal-title">Test</h6>
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
</div>
<div class="modal-body">
<div class="container">
<div class="row justify-content-md-center">
<form asp-page-handler="update" method="post">
<div class="form-group">
<label asp-for="MyModel.Username" class="control-label">Test Code</label>
<textarea asp-for="MyModel.Username" class="form-control" rows="2"></textarea>
<span asp-validation-for="MyModel.Username" class="text-danger"></span>
</div>
<div class="form-group">
<button class="btn">
<span class="text">Update</span>
</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
#section Scripts {
#{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
I have also tried:
RuleFor(support => support.Found.ToString())
.Matches(#"^True$")

The real reason is that FluentValidation does not support all rules for client side validation.
Quoted from FluentValidation docs:
Note that not all rules defined in FluentValidation will work with ASP.NET’s client-side validation. For example, any rules defined using a condition (with When/Unless), custom validators, or calls to Must will not run on the client side. Nor will any rules in a RuleSet (although this can be changed - see the section below on “RuleSet for client-side messages”). The following validators are supported on the client:
NotNull/NotEmpty
Matches (regex)
InclusiveBetween (range)
CreditCard
Email
EqualTo (cross-property equality comparison)
MaxLength
MinLength
Length
So this is a behavior by design.

FluentValidation is a server-side framework, and does not provide any client-side validation directly. The following validators are supported on the client:
NotNull/NotEmpty
Matches (regex)
InclusiveBetween (range)
CreditCard
Email
EqualTo (cross-property equality comparison)
MaxLength
MinLength
Length
If you want to use validation rules on client-side, you can try FormHelper. Form Helper transforms server-side validations to client-side.
Nuget Package: https://www.nuget.org/packages/FormHelper
GitHub: https://github.com/sinanbozkus/formhelper

Related

Dropdown list is not working in the Asp.net Blazor Component

I am trying to learn the new feature in ASP.NET Blazor. I am using Visual Studio 2019. I am trying to create an Ideas Registration form. So the code for dropdownlist i had took from Bootstrap 4. It was not working as expected. Can you please tell me where i am working wrong?
Just a little overwhelmed here, any advice would be much appreciated.
Given Code:
<!-- Card Body -->
<div class="card-body">
<!-- <form -->
<form>
<div class="form-group">
<label for="exampleFormControlInput1">Title</label>
<input type="email" class="form-control" id="exampleFormControlInput1">
</div>
<div class="form-group">
<label for="exampleFormControlSelect1">Description</label>
<textarea class="form-control" id="exampleFormControlTextarea1" rows="4"></textarea>
</div>
<!-- Basic dropdown -->
<div class="form-group">
<button class="btn btn-primary dropdown-toggle mr-4" type="button" data-toggle="dropdown"
aria-haspopup="true" aria-expanded="false">
Basic dropdown
</button>
<div class="dropdown-menu">
<a class="dropdown-item" href="#">.Net</a>
<a class="dropdown-item" href="#">Python</a>
<a class="dropdown-item" href="#">Data Science</a>
<div class="dropdown-divider"></div>
</div>
</div>
<!-- Basic dropdown -->
where i am working wrong
According to the official docs](https://getbootstrap.com/docs/4.0/components/dropdowns/#data-toggledropdown-still-required):
Regardless of whether you call your dropdown via JavaScript or instead use the data-api, data-toggle="dropdown" is always required to be present on the dropdown’s trigger element.
I would suggest you should wrap your Basic dropdown in the following structure
<div class="dropdown">
<button data-toggle="dropdown" class="..." > ...</button>
<div class="dropdown-menu ...>
...
</div>
</div>
You didn't add an event handler for selection. At least you should add a #onclick for the toggle button. When clicking this button, show or hide the dropdown-menu.
Finally, if you want to implement the dropdown component with Blazor(without javascript), you should also replace the text content within the toggle button when someone selects a dropdown list item.
A Demo : How to Create A General Dropdown Component
Rather than simply fixing the issue, I think it's much better to create a general dropdown Component so that we can always invoke them in following way:
#{ var list = new List<string>{ ".NET", "Python","Java" }; }
<Dropdown TItem="string" OnSelected="#OnSelected" >
<InitialTip>This is a dropdown list</InitialTip>
<ChildContent>
<DropdownListItem Item="#list[0]">.NET</DropdownListItem>
<DropdownListItem Item="#list[1]">Python</DropdownListItem>
<div class="dropdown-divider"></div>
<DropdownListItem Item="#list[2]">Java</DropdownListItem>
</ChildContent>
</Dropdown>
#code {
private void OnSelected(string selection)
{
Console.WriteLine(selection);
}
}
Here the TItem is a generic type parameter that is the type of each dropdown list item and can be any .NET type.
Demo
How-To
Add a Shared/Dropdown.razor component:
#using Microsoft.AspNetCore.Components.Web
#typeparam TItem
<div class="dropdown">
<button class="btn btn-primary dropdown-toggle mr-4" data-toggle="dropdown" type="button" #onclick="e => this.show=!this.show "
aria-haspopup="true" aria-expanded="false">
#Tip
</button>
<CascadingValue name="Dropdown" Value="#this">
<div class="dropdown-menu #(show? "show":"")" >
#ChildContent
</div>
</CascadingValue>
</div>
#code {
[Parameter]
public RenderFragment InitialTip{get;set;}
[Parameter]
public RenderFragment ChildContent{get;set;}
[Parameter]
public EventCallback<TItem> OnSelected {get;set;}
private bool show = false;
private RenderFragment Tip ;
protected override void OnInitialized(){ this.Tip = InitialTip; }
public async Task HandleSelect(TItem item, RenderFragment<TItem> contentFragment)
{
this.Tip= contentFragment.Invoke(item);
this.show=false;
StateHasChanged();
await this.OnSelected.InvokeAsync(item);
}
}
Add a Shared/DropdownListItem.razor component:
#using Microsoft.AspNetCore.Components.Web
#typeparam TItem
<a class="dropdown-item" Item="#Item" #onclick="e=> Dropdown.HandleSelect(Item, ChildContent)" >#ChildContent(Item)</a>
#code {
[CascadingParameter(Name="Dropdown")]
public Dropdown<TItem> Dropdown {get;set;}
[Parameter]
public TItem Item{get;set;}
[Parameter]
public RenderFragment<TItem> ChildContent {get;set;}
}
Keep in mind that bootstrap dropdown requires bootstrap javascript to be referenced. And the Blazor template doesn't reference it by default.
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js#1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.min.js" integrity="sha384-OgVRvuATP1z7JjHLkuOU7Xw704+h835Lr+6QL9UvYjZE3Ipu6Tp75j7Bh/kR0JKI" crossorigin="anonymous"></script>
The accepted answer is great. However, as many have pointed out, the dropdown does not close if an option is not selected. The suggestion to create an #unblur event does not solve the case, as #unclick and #unblur do not seem to work together in .NET 5 (I read somewhere that it does works in the new .NET 6) - #unblur prevents #unclick to be triggered.
I found this solution (#onblur prevents #onclick in blazor server side app), changing the #onclick to #onmousedown and then creating the #onblur event (as suggested) has fixed the issue for me.

How to collapse/expand Razor components using Blazor syntax?

I'm currently implementing a form to create a new user along with their respective user rights. In this form, I have about 30 different IT systems and if the user account should have the access rights for that specific IT system, I want to provide a panel to the admin where some extra information must be entered regarding that specific IT system. I want to implement this using razor components. What I have so far is the core view for my "new user form" as well as a razor component for the additional information of a specific IT system. By clicking the + button, I want the component to be visible / expand right below the IT system. That's what It looks like so far:
The new user form:
<div class="row">
<div class="col-sm-2 font-weight-bold">GOODWILL PKW/Smart</div>
<div class="col-sm-2">
<label>Add</label>
<input type="checkbox" />
</div>
<div class="col-sm-2">
<label>Change</label>
<input type="checkbox" />
</div>
<div class="col-sm-2">
<label>Remove</label>
<input type="checkbox" />
</div>
<div class="col-sm-4">
<button #onclick="#collapseGoodwill">+</button>
</div>
</div>
<ModalGoodwillPKW ></ModalGoodwillPKW>
#code {
public void collapseGoodwill() {
}
}
The component:
<div class="panel panel-default border">
<div class="panel-heading alert-primary">
<h3 class="panel-title">Goodwill PKW/smart</h3>
</div>
<div class="panel-body">
<div class="container-fluid">
<div class="row">
<div class="col-sm-2 font-weight-bold">Profile</div>
<div class="col-sm-5">
<input type="checkbox" id="CB_c" />
<label>Salesman</label>
</div>
<div class="col-sm-5">
<input type="checkbox" id="CB_r" />
<label>Administrator</label>
</div>
</div>
</div>
</div>
</div>
Normally, I would use JQuery in the "collapseGoodwill" method to add a .collapse class to this element. But since I am experimenting with Blazor, I'd like to know if there is a 100% Javascript /JQuery free way of doing this.
Thanks!
Within Blazor, you always follow the pattern:
change data
--> new view rendered
Anytime you want to change the component's UI from outside, you should do it by changing the data (model/state/parameter/context/...).
As for this scenario, you can add a Collapsed field to indicate whether the panel itself is collapsed now:
<div class="panel panel-default border #Collapse">
<div class="panel-heading alert-primary">
<h3 class="panel-title">Goodwill PKW/smart</h3>
</div>
<div class="panel-body">
<div class="container-fluid">
<div class="row">
<div class="col-sm-2 font-weight-bold">Profile</div>
<div class="col-sm-5">
<input type="checkbox" id="CB_c" />
<label>Salesman</label>
</div>
<div class="col-sm-5">
<input type="checkbox" id="CB_r" />
<label>Administrator</label>
</div>
</div>
</div>
</div>
</div>
#code{
[Parameter]
public string Collapse{get;set;}="collapse"; // hide by default
}
And whenever you want to collapse it, just set this parameter to collapse:
<div class="row">
<div class="col-sm-2 font-weight-bold">GOODWILL PKW/Smart</div>
<div class="col-sm-2">
<label>Add</label>
<input type="checkbox" />
</div>
<div class="col-sm-2">
<label>Change</label>
<input type="checkbox" />
</div>
<div class="col-sm-2">
<label>Remove</label>
<input type="checkbox" />
</div>
<div class="col-sm-4">
<button #onclick="e => this.Collapsed = !this.Collapsed">
#( this.Collapsed ? "+" : "-")
</button>
</div>
</div>
<ModalGoodwillPKW Collapse="#( this.Collapsed ? "collapse": "")" ></ModalGoodwillPKW>
#code {
private bool Collapsed = true;
}
Demo:
[Edit] : we can even refactor the above code to expose less information by changing the field from string to boolean.
The ModalGoodwillPKW.razor:
<div class="panel panel-default border #(Collapsed? "collapse": "" ) ">
<div class="panel-heading alert-primary">
<h3 class="panel-title">Goodwill PKW/smart</h3>
</div>
...
#code{
[Parameter]
public bool Collapsed{get;set;}= true; // hide by default
}
The UserForm.razor:
<div class="row">
...
<div class="col-sm-4">
<button #onclick="e => this.Collapsed = !this.Collapsed">
#( this.Collapsed ? "+" : "-")
</button>
</div>
</div>
<ModalGoodwillPKW Collapsed="#Collapsed" ></ModalGoodwillPKW>
#code {
private bool Collapsed = true;
}
I had a similar issue, I had a dynamic list of sections that I wanted to collapse, and I couldn't get the bootstrap data-toggle approach to work due to Blazor mis-handling of # anchor tags.
I used the component idea:
<div class="row">
#if (Collapsed)
{
<span #onclick="#Toggle" class="oi oi-plus mr-1"/>
}
else
{
<span #onclick="#Toggle" class="oi oi-minus mr-1"/>
}
#Title
</div>
#if(!Collapsed)
{
#ChildContent
}
#code {
[Parameter]
public RenderFragment ChildContent { get; set; }
[Parameter]
public bool Collapsed { get; set; }
[Parameter]
public string Title { get; set; }
void Toggle()
{
Collapsed = !Collapsed;
}
}
Which I could then use like this:
#foreach (var i in c.Request)
{
<Collapsable Title="#i.SectionName" Collapsed="true">
<ChildContent>
#foreach (var kvp in i.Values)
{
<div class="row">
<div class="col-1"></div>
<div class="col-6 font-weight-bolder">#kvp.Key</div>
<div class="col-5">#kvp.Value</div>
</div>
}
</ChildContent>
</Collapsable>
}
This seems to work well, each section is independently collapsible.
I've not tried it nested though.
Blazor "#Collapse" div with Bootstrap Toggle Button
I took #cjb110 's excellent sample code above and changed it to use a bootstrap badge button as the toggle, which is how I often add more verbose help info to a form field group, by hiding it behind a toggle and using a bootstrap or material info button for if a user wants it.
Component Part
Here's the component part, which you'd probably add to your Blazor solution's Client project's Shared folder as file name Collapsible.razor (note: Blazor component file names are to be capitalized--I think)
<div class="my-1">
<h3>#Title</h3>
#if (Collapsed)
{
<button #onclick="#Toggle" class="badge badge-info mr-2" role="button" >
#ButtonText
</button>
}
else
{
<button #onclick="#Toggle" class="badge badge-info mr-2" role="button" >
#ButtonText
</button>
}
<label>
#LabelText
</label>
</div>
#if(!Collapsed)
{
<div class="card alert alert-info mb-3" role="alert">
#ChildContent
</div>
}
#code {
[Parameter]
public RenderFragment ChildContent { get; set; }
[Parameter]
public bool Collapsed { get; set; }
//input params coming from from page
[Parameter]
public string Title { get; set; }
[Parameter]
public string ButtonText { get; set; }
[Parameter]
public string LabelText { get; set; }
void Toggle()
{
Collapsed = !Collapsed;
}
}
Template Part
I call this the "template" part. You can change the
Title text,
ButtonText,
I use these info-btn toggles typically in forms, so I added a
<label/> tag with LabelText.
In the <ChildContent/> area, in the component file I set it up as a Bootstrap alert class div, so it doesn't require a <p> tag, but put anything in here you want to show up when the toggle is opened.
<Collapsible
Title=""
ButtonText="Info"
LabelText="Search People & Assign Roles: "
Collapsed="true">
<ChildContent>
Find a person, add their role to the product (i.e.: Estimator, Foreman, Customer)
</ChildContent>
</Collapsible>
I was facing issues with the accordion collapse in my project.
This is how I fixed the bootstrap collapse issue in my Blazor app.
I simply copied these dependencies in the index.html file in Blazor webapp and it worked fine.
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.16.0/umd/popper.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
Reference: https://www.w3schools.com/bootstrap4/tryit.asp?filename=trybs_collapsible&stacked=h
Let us know if this works for anyone else
There are some long and good answers. I thought I'd come in with the most important punchline, though.
You can hide whatever you want based on C# conditional logic. So you will VERY often use something like:
<div #onclick="()=>IsOpened = !IsOpened">Click on me to show the hidden control.</div>
#if (IsOpened){
<MyHiddenControl />
}
#code {
bool IsOpened;
}

How can I make a fancy checkbox template for ASP.NET Core?

I've got a lot of booleans in my model, and we're using Bootstrap, so for every boolean property I'm copy/paste refactoring:
<div class="form-group">
<div class="custom-control custom-checkbox ">
<input asp-for="IsFoo"/>
<label asp-for="IsFoo"></label>
</div>
</div>
... but that's dumb. I tried adding this to Views/Shared/EditorTemplates/bool.cshtml:
#model bool?
<div class="form-group">
<div class="custom-control custom-checkbox ">
<input asp-for="#Model"/>
<label asp-for="#ViewData.TemplateInfo.FormattedModelValue"></label>
</div>
</div>
... and calling it with #Html.EditorFor(m => m.IsFoo) but all I'm getting back is a plain input element from the default template.
what am I doing wrong here name the template 'boolean.cshtml'
is ViewData.TemplateInfo.FormattedValue the right value to get the Display(Name="xxx") Attribute from the property nope. ViewData.ModelMetadata.DisplayName
is there some new & improved version instead of Editor Templates in ASP.NET Core that I should be using (like Tag Helpers?) instead of the "old" way, and if so, how do I go about it?
Use the <partial> tag-helper:
<partial name="MyCheckbox" for="IsFoo" />
It works with binding properties too:
class MyModel
{
public List<MyCheckboxModel> MyCheckboxList { get; set; }
}
class MyCheckboxModel
{
public Boolean IsChecked { get; set; }
}
#for( Int32 i = 0; i < this.Model.MyCheckboxList.Count; i++ )
{
<partial name="MyCheckbox" for="MyCheckboxList[i]"
}
Change your partial-view to:
#model MyCheckboxModel
<div class="form-group">
<div class="custom-control custom-checkbox">
<input asp-for="#Model"/>
<label asp-for="#Model"></label>
</div>
</div>
The for="" attribute causes the name/id/binding context in the partial to match the named property, so ASP.NET will do the magic to ensure that <input asp-for="#Model" /> will correspond to Model.MyCheckBoxList[0] and so on.

How to prevent immediate trigger jQuery validation?

There is some ViewModel:
class MyViewModel
{
[Required(ErrorMessage = "Field {0} is required")]
public string Email { get; set; }
}
I use jquery validation for front-end:
<script src="https://ajax.aspnetcdn.com/ajax/jquery.validate/1.16.0/jquery.validate.min.js">
</script>
<script src="https://ajax.aspnetcdn.com/ajax/jquery.validation.unobtrusive/3.2.6/jquery.validate.unobtrusive.min.js">
</script>
The fragment of Razor markup:
<form asp-controller="Account" asp-action="Register" role="form">
<div class="form-group">
<div asp-validation-summary="All" class="text-danger"></div>
</div>
<div class="form-group">
<label asp-for="Email"></label>
<input asp-for="Email" class="form-control" aria-describedby="email" />
<span asp-validation-for="Email" class="text-danger"></span>
</div>
</form>
The issue is validation is triggered immediately when user get the html page. So one sees error for email field when she inputs nothing yet (Field Email is required). How can I prevent this behavior (triggered on submit)?
There is action:
public IActionResult SomeAction(MyViewModel model = null)
{
return View(model);
}
i.e. controller pass to action null model (value by default). It is the reason of that behavior of jquery validation

Why is the Bind attribute seemingly breaking my model binding of nested objects?

Could someone help me resolve this issue. I'm trying to limit over posting with bind param action but it seems that it doesn't work at all. When I removed the Bind keyword, everything started to work as a charm.
Here is the code sample:
View Model:
public class ProductCreateViewModel
{
public Product Product { get; set; }
public ICollection<IFormFile> Images { get; set; }
}
Action:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("Product.Id,Product.CategoryId,Product.Description,Product.Title")] ProductCreateViewModel productVM)
{
if (ModelState.IsValid)
{
_context.Add(productVM.Product);
await _context.SaveChangesAsync();
return RedirectToAction("Index");
}
ViewData["CategoryId"] = new SelectList(_context.Categories.Include(c => c.Categories).Where(c => c.ParentCategoryId == null), "Id", "Name", productVM.Product.CategoryId);
return View(productVM);
}
View:
#model CatalogWebApp.Models.ProductsViewModels.ProductCreateViewModel
#{
ViewData["Title"] = "Add Product";
ViewData["BigPageTitle"] = "Products";
ViewData["PageBoxTitle"] = "Add New Product";
}
<form asp-action="Create">
<div class="form-horizontal">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Product.CategoryId" class="col-md-2 control-label"></label>
<div class="col-md-10">
<select name="Product.CategoryId" class ="form-control">
#foreach(Category item in (ViewBag.CategoryId as SelectList).Items)
{
<option value="#item.Id">#item.Name</option>
if (item.Categories != null && item.Categories.Count > 0)
{
foreach (var subCat in item.Categories)
{
<option value="#subCat.Id">--#subCat.Name</option>
}
}
}
</select>
</div>
</div>
<div class="form-group">
<label asp-for="Product.Description" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="Product.Description" class="form-control" />
<span asp-validation-for="Product.Description" class="text-danger" />
</div>
</div>
<div class="form-group">
<label asp-for="Product.Title" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="Product.Title" class="form-control" />
<span asp-validation-for="Product.Title" class="text-danger" />
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>
</form>
<div>
<a asp-action="Index">Back to List</a>
</div>
#section Scripts {
#{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
Could someone pelase indicate if I have a problem or it is only a known asp.net core issue?
I'm not quite sure why you using Bind for your case.
Just create sepatate ViewModel with only properties you need like ProductCreateStort.
Then use this ViewModel in your controller signature and inherit your main model from it.
This way you won't mess with Bind and limit your params on POST
While I'm fairly new to ASP.NET Core myself (and coming to this question 7 months late), I ran into this same issue. I think the key here is that you have to bind "Product" for it to be considered. Binding "Product.Id" by itself doesn't appear to be good enough. So this should work:
[Bind("Product,Product.Id,Product.CategoryId,Product.Description,Product.Title")]
Of course, Hamid Mosalla's comment is a better option if ALL of your bound properties are on the nested object (which leads to wonder why you need a view model in the first place). In my case, I have a nested object AND a local property, so using the "Prefix" solution wasn't the right thing to do.
Anyway, hope this helps someone.
You need to pass your values as params string[], not as a single string separated by commas:
[Bind("Product.Id","Product.CategoryId","Product.Description","Product.Title")]
See Source