I'm new to .NET Core 2.0 Razor pages, and I'm finding it difficult to solve my problem in a clean way.
I create a simple login form in which the user must enter it's email and password. I use a model with the properties for the email and password with the corresponding data annotations.
public class AuthorizeOrganizationCommand
{
[Required(ErrorMessage ="Please fill in your email")]
public string Email { get; set; }
[Required(ErrorMessage ="Please fill in your password")]
public string Password { get; set; }
}
My pagemodel looks like this:
public class IndexModel : PageModel
{
public IndexModel()
{
}
[BindProperty]
public AuthorizeOrganizationCommand Command { get; set; }
public void OnGet()
{
}
public IActionResult OnPost()
{
if (!ModelState.IsValid)
{
return Page();
}
// Code to validate the credentials
return RedirectToPage("/Index");
}
}
When my ModelState is invalid, I want to visualize an error message. This works just fine with the following code:
<div class="form-group">
<label class="form-label" asp-for="Command.Email">Email</label>
<input type="text" class="form-control" asp-for="Command.Email">
<small class="invalid-feedback d-block">
<span asp-validation-for="Command.Email"></span>
</small>
</div>
Additionally I want to add the CSS class .is-invalid
on my input element when my Modelstate is invalid for that specific property. This results in a red-edged input element (bootstrap 4). I made it work with the following code:
<input type="text" class="form-control @(ModelState["Command.Email"]?.ValidationState == Microsoft.AspNetCore.Mvc.ModelBinding.ModelValidationState.Invalid ? "is-invalid": string.Empty)" asp-for="Command.Email">
To be honest, I don't like this solution.
The hard-coded "Command.Email" breaks the code when a rename is performed on the class instance name or property. After trying several things I didn't find a good and clean way to solve this.
Better solution:
MVC InputTagHelper automatically adds a class to the input field depending on the model validation state. In case of validation error it adds the class input-validation-error
so you can customize the input by defining css styles for this class e.g.
.input-validation-error {
border: 1px solid red;
}
If instead you prefer to use your own css classes there is also a workaround to change the default classes that InputTagHelper adds to the input. You can find the code in this gist. To summarize you have to create a class that extends the DefaultHtmlGenerator class and replaces the default input validation css classes with whatever you like.
public class CustomHtmlGenerator : DefaultHtmlGenerator
{
public CustomHtmlGenerator(IAntiforgery antiforgery, IOptions<MvcViewOptions> optionsAccessor,
IModelMetadataProvider metadataProvider, IUrlHelperFactory urlHelperFactory, HtmlEncoder htmlEncoder,
ValidationHtmlAttributeProvider validationAttributeProvider) :
base(antiforgery, optionsAccessor, metadataProvider, urlHelperFactory, htmlEncoder, validationAttributeProvider)
{
}
protected override TagBuilder GenerateInput(ViewContext viewContext, InputType inputType, ModelExplorer modelExplorer, string expression,
object value, bool useViewData, bool isChecked, bool setId, bool isExplicitValue, string format,
IDictionary<string, object> htmlAttributes)
{
var tagBuilder = base.GenerateInput(viewContext, inputType, modelExplorer, expression, value, useViewData, isChecked, setId, isExplicitValue, format, htmlAttributes);
FixValidationCssClassNames(tagBuilder);
return tagBuilder;
}
public override TagBuilder GenerateTextArea(ViewContext viewContext, ModelExplorer modelExplorer, string expression, int rows,
int columns, object htmlAttributes)
{
var tagBuilder = base.GenerateTextArea(viewContext, modelExplorer, expression, rows, columns, htmlAttributes);
FixValidationCssClassNames(tagBuilder);
return tagBuilder;
}
private void FixValidationCssClassNames(TagBuilder tagBuilder)
{
tagBuilder.ReplaceCssClass(HtmlHelper.ValidationInputCssClassName, "is-invalid");
tagBuilder.ReplaceCssClass(HtmlHelper.ValidationInputValidCssClassName, "is-valid");
}
}
public static class TagBuilderHelpers
{
public static void ReplaceCssClass(this TagBuilder tagBuilder, string old, string val)
{
if (!tagBuilder.Attributes.TryGetValue("class", out string str)) return;
tagBuilder.Attributes["class"] = str.Replace(old, val);
}
}
and then register it to the dependency injection
public void ConfigureServices(IServiceCollection services)
{
// All your usual stuff
// Configure custom html generator to override css classnames
services.AddSingleton<IHtmlGenerator, CustomHtmlGenerator>();
}
Original answer:
You could define the css class as a property in your PageModel.
public class IndexModel : PageModel
{
public IndexModel()
{
}
[BindProperty]
public AuthorizeOrganizationCommand Command { get; set; }
public string InputClass { get; set; }
public void OnGet()
{
}
public IActionResult OnPost()
{
if (!ModelState.IsValid)
{
InputClass = "is-invalid";
return Page();
}
// Code to validate the credentials
return RedirectToPage("/Index");
}
}
and then:
<input type="text" class="form-control @Model.InputClass" asp-for="Command.Email">
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With