Razor Reusable Form View With Data Annotations - asp.net-core

We're using ASP.NET Core 3.1 with Razor to render many Twitter Bootstrap html forms, each with it's own model. Data Annotations are used for server and client side validation.
We have tried to create a generic, reusable partial view / component / tag helper / etc to render the same html for all fields, but found none of the method working.
Consider the following fields with data annotations:
[BindProperty, Required(ErrorMessage = "Field is required"), RegularExpression("[^\\s]+", ErrorMessage = "No spaces allowed")]
public string FirstName { get; set; }
[BindProperty, Required(ErrorMessage = "Field is required")]
public string LastName { get; set; }
[BindProperty] [EmailAddress(ErrorMessage = "Invalid e-mailaddress")]
public string Emailaddress { get; set; }
Each field should render the following html:
<div class="form-group row">
<label class="col-md-4 control-label" asp-for="FirstName/LastName/Emailaddress">
Your first name / last name / e-mailaddress
</label>
<div class="col-md-4">
<input asp-for="FirstName/LastName/Emailaddress" class="form-control input-md" />
<span class="invalid-feedback" asp-validation-for="FirstName/LastName/Emailaddress"></span>
</div>
</div>
The goal is to be able to create a simple Razor page, such as (or any other method):
<form method="Post">
<partial name="partialFormTextInput for="FirstName" />
<partial name="partialFormTextInput for="LastName" />
<partial name="partialFormTextInput for="Emailaddress" />
<input type="submit" value="Submit form">
</form>
It would greatly simplify the process of creating forms for different models. We have found that the primary issue is in the data annotations, as they do not render in partial views, nor in tag helpers.
Has anyone succeeded in creating this?
Update
If a partial view is used, the actual (simplified) result is:
<input type="text" id="FirstName" name="FirstName" value="">
<span class="invalid-feedback field-validation-valid" data-valmsg-for="FirstName" data-valmsg-replace="true"></span>
While the expected result is:
<input type="text" class="form-control input-md" data-val="true" data-val-regex="No spaces allowed" data-val-regex-pattern="[^\s]+" data-val-required="Field is required" id="FirstName" name="FirstName" value="">
<span class="invalid-feedback field-validation-valid" data-valmsg-for="FirstName" data-valmsg-replace="true"></span>
The data-val* attributes are missing on the input

Related

How to remove 'maxlength' html attribute when using 'MaxLength' DataAnnotations attribute?

I recently upgraded my web application from .NET Core 2.1 to Core 3.1.
Noticed that the unobtrusive validation of Max Length isn't working as before. There is html attribute maxlength being added to the input element. Because of this, user can put in only the max set number of characters in the input field. There is no message to inform the user that they have exceeded the max character limit of that particular field.
How do I notify user that they have reached/crossed the limit?
My code:
AddSpirit.cshtml
#model WebApp.ViewModels.SpiritViewModel
<div class="container pt-5">
<div class="row">
<div class="col-12">
<form asp-action="AddSpirit" method="POST">
<fieldset class="form-group">
<label asp-for="Name"></label>
<input asp-for="Name" class="form-control" />
<span asp-validation-for="Name" class="text-danger"></span>
</fieldset>
<fieldset class="form-group">
<label asp-for="Price"></label>
<input asp-for="Price" class="form-control" />
</fieldset>
<fieldset class="form-group">
<label asp-for="Stock"></label>
<input asp-for="Stock" class="form-control" />
</fieldset>
<button type="submit" class="btn btn-sm btn-danger text-uppercase py-2 px-3 px-md-3 mb-2">
Save Changes
</button>
</form>
</div>
</div>
</div>
#section Scripts {
#{await Html.RenderPartialAsync("_ValidationScriptsPartial"); }
}
SpiritViewModel.cs
using System.ComponentModel.DataAnnotations;
using Newtonsoft.Json;
namespace WebApp.ViewModels
{
public class SpiritViewModel
{
[JsonProperty("name")]
[MaxLength(5, ErrorMessage = "{0} should not be longer than {1} characters")]
[MinLength(2, ErrorMessage = "{0} should be longer than {1} characters")]
public string Name { get; set; }
[JsonProperty("price")]
[Required(ErrorMessage = "Enter the spirit's price.")]
[Range(10, 500, ErrorMessage = "Accepting only spirits in price range INR 10 - 500")]
public double Price { get; set; }
[JsonProperty("stock")]
public int Stock { get; set; }
}
}
Setting maxlength and minlength attribute values in cshtml would be a way to stop MaxLength or StringLength DataAnnotations limiting characters in the input field. Once the user is able to enter more characters, the unobtrusive validation works just fine.
<input asp-for="Name" maxlength="" minlength="" class="form-control" />

Setting properties using asp-for in MVC Core App with EF Core?

As I understand it from these docs: https://learn.microsoft.com/en-us/aspnet/core/mvc/views/tag-helpers/intro?view=aspnetcore-3.1 , asp-for is used to transfer values from input elements to backend C# class properties, for example:
<input type="text" id="wsite" name="wsite" maxlength="11" asp-for="WebsiteName">
Along with '#folderName ClassName;' at the top, lets you transfer to this example property:
public string WebsiteName { get; set; }
However, testing this out with console.WriteLine show that the property is still null after the form containing the input has been submitted. Any idea what I'm missing?
Edit: Updated to show my property name and asp-for value match, and to add my controller:
[HttpPost]
public IActionResult Post()
{
DBCRUD.Initialize(_context);
return NoContent();
}
The asp-for tag should match the variable-name.
Try defining your html-form like:
#model Classname
<form asp-action="ActionName" asp-controller="ControllerName" ...>
<input type="text" asp-for="VarName">
and your controller:
public MyReturnVariable ActionName(ClassName class) {
Console.WriteLine(class.VarName);
}
The Tag Helpers is used with Model binding and creating and rendering HTML elements(display the model properties) in the web page.
So, in the Web page (or view page), at the top of the header, we should add the following code to assign the model.
#model MVCSample.Models.BookModel
Then, using the following code to display the properties:
<div class="row">
<div class="col-md-4">
<form asp-action="AddBook">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="ID" class="control-label"></label>
<input asp-for="ID" class="form-control" />
<span asp-validation-for="ID" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="bookName" class="control-label"></label>
<input asp-for="bookName" class="form-control" />
<span asp-validation-for="bookName" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Title" class="control-label"></label>
<input asp-for="Title" class="form-control" />
<span asp-validation-for="Title" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
</div>
</form>
</div>
</div>
Code in the controller:
[HttpGet]
public IActionResult AddBook()
{
BookModel book = new BookModel()
{
ID = 1001,
bookName = "War and Peace",
Title = "War and Peace"
};
return View(book);
}
Code in the model:
public class BookModel
{
public int ID { get; set; }
public string bookName { get; set; }
public string Title { get; set; }
}
More details information, you could check the Model Binding.

Label tag asp-for is not displaying

I have this ViewModel class
public class ThirdPartyTransfer
{
public int Id { get; set; }
[Display(Name = "Transfer Amount")]
public decimal TransferAmount { get; set; }
}
and in my C# Razor Pages, ThirdPartyTransfer.cshtml
<div class="form-group">
<label asp-for="ThirdPartyTransfer.TransferAmount" />
<input asp-for="ThirdPartyTransfer.TransferAmount" class="form-control" />
<span asp-validation-for="ThirdPartyTransfer.TransferAmount" class="text-danger"></span>
</div>
and what is being rendered out is below
<div class="form-group">
<label for="ThirdPartyTransfer_TransferAmount" />
<input class="form-control" type="text" data-val="true" data-val-number="The field Transfer Amount must be a number." data-val-required="The Transfer Amount field is required." id="ThirdPartyTransfer_TransferAmount" name="ThirdPartyTransfer.TransferAmount" value="" />
<span class="text-danger field-validation-valid" data-valmsg-for="ThirdPartyTransfer.TransferAmount" data-valmsg-replace="true"></span>
</div>
I wonder why I could see the textbox but not the label.
Well, Steve, your issue is related to your HTML. A label tag isn't supposed to be self-closing.
Change that to <label asp-for=""></label> and you're good to go!
From Microsoft Docs;
Self-closing Tag Helpers
Many Tag Helpers can't be used as self-closing tags. Some Tag Helpers are designed to be self-closing tags. Using a Tag Helper that was not designed to be self-closing suppresses the rendered output. Self-closing a Tag Helper results in a self-closing tag in the rendered output. For more information, see this note in Authoring Tag Helpers.

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