What is the best way to get a configured value to a class library for validation? - validation

I currently have a Blazor app that references a class library. One of the pages in the web app is used for updating an instance of a class model in the class library. To validate I'm using Validation attributes on the class model. One of the fields for input is email which, for our software, is validated via a configurable regular expression (because each of our sites can be different).
I think the best way to do this is using a custom ValidationAttribute but I don't know the best way to get a value from the web app's app settings to the custom Validation class.
The following code is an example of what I'm trying to accomplish:
Blazor page:
<EditForm Model="#name" OnValidSubmit="HandleValidSubmit">
<DataAnnotationsValidator />
<ValidationSummary />
<div>
<label for="FirstName">First:</label>
</div>
<div>
<input id="FirstName" class="form-control profile-form-control" #bind-value="name.FirstName" #bind-value:event="oninput" type="text" maxlength="30" autocomplete="off" />
</div>
<div>
<label for="LastName">Last:</label>
</div>
<div>
<input id="LastName" class="form-control profile-form-control" #bind-value="name.LastName" #bind-value:event="oninput" type="text" maxlength="100" autocomplete="off" />
</div>
<div>
<label for="Email">Email:</label>
</div>
<div>
<input id="Email" class="form-control profile-form-control" #bind-value="name.Email" #bind-value:event="oninput" type="text" maxlength="100" autocomplete="off" />
</div>
<div>
<button type="submit" class="btn btn-primary">
Save
</button>
</div>
</EditForm>
Model (in separate class library):
public class Person
{
[Required(ErrorMessage = "First name required")]
public string FirstName { get; set; }
[Required(ErrorMessage = "Last name required")]
public string LastName { get; set; }
[Required(ErrorMessage = "Email required")]
[EmailFromRegexValidator(ErrorMessage = "Email not valid")]
public string Email { get; set; }
}
Custom Validation:
public class EmailFromRegexValidator : ValidationAttribute
{
private const string defaultEmailValidationRegex = "^[\\w-]+(\\.[\\w-]+)*#([a-zA-Z0-9-]+(\\.[a-zA-Z0-9-]+)*?\\.[a-zA-Z]{2,6}|(\\d{1,3}\\.){3}\\d{1,3})(:\\d{4})?$";
protected override ValidationResult IsValid(object value, ValidationContext context)
{
string emailRegexString = null;
var configurationBuilder = new ConfigurationBuilder();
var path = Path.Combine(Directory.GetCurrentDirectory(), "appsettings.json");
if (File.Exists(path))
{
configurationBuilder.AddJsonFile(path, false);
var root = configurationBuilder.Build();
emailRegexString = root.GetSection("AppConfiguration").GetSection("EmailRegex").Value;
}
emailRegexString = emailRegexString ?? defaultEmailValidationRegex;
Regex emailRegex = new Regex(emailRegexString);
if (value is string && emailRegex.IsMatch(value as string))
{
return ValidationResult.Success;
}
else
{
return new ValidationResult(FormatErrorMessage(context.DisplayName));
}
}
}
The above code works but building configuration from a file path within a class library does not feel optimal. So I was curious if anyone had any better ideas for how to get a configurable value to the EmailFromRegexValidator?
Thanks!

Welcome! You can use ValidationContext to access a service that provides your configuration value.
protected override ValidationResult IsValid(object value, ValidationContext context)
{
var appConfig = (AppConfiguration) validationContext
.GetService(typeof(AppConfiguration));
string emailRegexString = appConfig.EmailRegex;
...
}
public class AppConfiguration
{
public string EmailRegex { get; set; }
}
You can bind AppConfiguration from the config file using to make it available in your DI container:
services.Configure<AppConfiguration>(configuration.GetSection("AppConfiguration"))

Related

Display validation error message - compare one input against another

Entered Max value must be greater than Min. Right now my code displays error message when Max is same as Min (using compare). Is there validator that can be used to compare one input against another?
MyData.cs:
public class MyData
{
[Required]
public double Min { get; set; }
[Compare("Min", ErrorMessage = "checks for matching min value")]
public double Max { get; set; }
}
Form.razor:
<div class="modal-body">
<EditForm EditContext="#context">
<DataAnnotationsValidator />
<label class="form-label" for="Min">Min</label>
<input class="form-control" #bind=model.Min type="text">
<label class="form-label" for="Max">Max</label>
<input class="form-control" #bind=model.Max type="text">
<ValidationMessage For="#(() => model.Max)" />
</EditForm>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" #onclick="() => Done()">Apply</button>
</div>
#code {
private MyData model = new MyData();
private EditContext context;
protected override void OnInitialized()
{
model = (MyData)(modalDialog?.ModalRequest.InData ?? new MyData());
context = new EditContext(model);
}
private void Done()
{
if (#model.Max < #model.Min)
{
context.Validate(); #*this displays error message*#
}
else
{
modalDialog?.Close(ModalResult.OK(model));
}
}
To validate for greater than or less than against another property instead of a value using Data Annotations you require to create a custom validation attribute as shown below:
GreaterThan attribute
// Custom attribute for validating greater than other property
public class GreaterThan : ValidationAttribute
{
private readonly string _comparisonProperty;
public GreaterThan(string comparisonProperty)
{
_comparisonProperty = comparisonProperty;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
ErrorMessage = ErrorMessageString;
var currentValue = (double)value; // cast to double same as property type
var property = validationContext.ObjectType.GetProperty(_comparisonProperty);
if (property == null)
throw new ArgumentException("Property with this name not found");
var comparisonValue = (double)property.GetValue(validationContext.ObjectInstance); // cast to property type
// comparison condition
if (currentValue < comparisonValue)
return new ValidationResult(ErrorMessage);
return ValidationResult.Success;
}
}
LessThan attribute
You can use the same code above to create for LessThan attribute by changing the name and comparison condition to currentValue > comparisonValue.
Below is an example on how to use Data Annotations to validate your model and display validation errors in the form. It includes GreaterThan custom validation attribute together with other common validation attribute.
Demo
Class:
public class MyData
{
[Required]
[MaxLength(40, ErrorMessage = "Name should be less than 40 characters")]
[MinLength(4, ErrorMessage ="Name should be more than 4 characters")]
public string Name { get; set; }
[Required]
[DataType(DataType.Date)]
public DateTime? BirthDate { get; set; }
[Required]
public double Min { get; set; }
[GreaterThan("Min", ErrorMessage = "Max must be greater than Min")]
public double Max { get; set; }
[Required(ErrorMessage = "Password is required.")]
public string Password { get; set; }
[Required(ErrorMessage = "Confirmation Password is required.")]
[Compare("Password", ErrorMessage = "Password and Confirmation Password must match.")]
public string ConfirmPassword { get; set; }
}
Razor:
Note: you can upgrade the styling for your form fields and validation message to your liking.
#page "/"
#using BlazorApp1.Models
<EditForm Model="#myData" OnValidSubmit="#HandleValidSubmit">
<DataAnnotationsValidator/>
<ValidationSummary/>
<p>
<label for="Name">Name: </label>
<InputText id="Name" #bind-Value="myData.Name"/>
<ValidationMessage For="() => myData.Name"/>
</p>
<p>
<label for="Min">Min: </label>
<InputNumber id="Min" #bind-Value="myData.Min"/>
<ValidationMessage For="() => myData.Min"/>
</p>
<p>
<label for="Max">Max: </label>
<InputNumber id="Max" #bind-Value="myData.Max"/>
<ValidationMessage For="() => myData.Max"/>
</p>
<p>
<label for="BirthDate">BirthDate: </label>
<InputDate id="BirthDate" #bind-Value="myData.BirthDate"/>
<ValidationMessage For="() => myData.BirthDate"/>
</p>
<p>
<label for="Password">Password: </label>
<InputText id="Password" #bind-Value="myData.Password"
type="password"/>
<ValidationMessage For="() => myData.Password"/>
</p>
<p>
<label for="ConfirmPassword">ConfirmPassword: </label>
<InputText id="ConfirmPassword" #bind-Value="myData.ConfirmPassword"
type="password"/>
<ValidationMessage For="() => myData.ConfirmPassword"/>
</p>
<button type="submit">Submit</button>
</EditForm>
#code {
private readonly MyData myData = new();
private void HandleValidSubmit()
{
// Save the data
}
}
Output:

How to get multiple answers from radio buttons when clicking submit ASP.NET CORE MVC

This is a quiz with around 40 questions and each question with multiple answers.
Everything looks fine until I try to catch the answers from the view to the controller because everything returns 0 or null, Please help!
This is what I have
The Database looks like this:
Model Evaluacion:
namespace ENCB_Placement_Test_Official.Models
{
public class Evaluacion
{
public int ExamenId { get; set; }
public int RegistroId { get; set; }
public int ReactivoId { get; set; }
public string RespuestaAlumno { get; set; }
public string Pregunta { get; set; }
public string Respuesta1 { get; set; }
public string Respuesta2 { get; set; }
public string Respuesta3 { get; set; }
public string Respuesta4 { get; set; }
}
}
Services folder with a class called RepositorioExaluaciones
namespace ENCB_Placement_Test_Official.Servicios
{
public interface IRepositorioEvaluaciones
{
Task EnviarRespuesta(Evaluacion evaluacion);
Task<IEnumerable<Evaluacion>> ObtenerEvaluaciones();
}
public class RepositorioEvaluaciones: IRepositorioEvaluaciones
{
private readonly string connectionString;
public RepositorioEvaluaciones(IConfiguration configuration)
{
connectionString = configuration.GetConnectionString("DefaultConnection");
}
public async Task<IEnumerable<Evaluacion>> ObtenerEvaluaciones()
{
using var connection = new SqlConnection(connectionString);
return await connection.QueryAsync<Evaluacion>(#"SELECT Examenes.Id AS ExamenId,
RegistroId,
ReactivoId,
RespuestaAlumno,
Pregunta,
Respuesta1,
Respuesta2,
Respuesta3,
Respuesta4
FROM Examenes
INNER JOIN Reactivos
ON Examenes.ReactivoId = Reactivos.Id
WHERE Examenes.RegistroId = 1");
}
public async Task EnviarRespuesta(Evaluacion evaluacion)
{
using var connection = new SqlConnection(connectionString);
connection.Execute(#"UPDATE Examenes
SET RespuestaAlumno = #RespuestaAlumno
WHERE RegistroId = #RegistroId", evaluacion);
}
}
}
Controller Evaluaciones
namespace ENCB_Placement_Test_Official.Controllers
{
public class EvaluacionesController:Controller
{
private readonly IRepositorioEvaluaciones repositorioEvaluaciones;
public EvaluacionesController(IRepositorioEvaluaciones repositorioEvaluaciones)
{
this.repositorioEvaluaciones = repositorioEvaluaciones;
}
public async Task<IActionResult> Evaluar()
{
if (!ModelState.IsValid)
{
return View();
}
var obtenerExamen = await repositorioEvaluaciones.ObtenerEvaluaciones();
return View(obtenerExamen);
}
[HttpPost]
public async Task<IActionResult> Evaluar(Evaluacion evaluacion)
{
if (!ModelState.IsValid)
{
return View(evaluacion);
}
await repositorioEvaluaciones.EnviarRespuesta(evaluacion);
return View();
}
}
}
View Evaluar, this is where I think I have the problem because when I debug the app and click the submit button I just get nulls and zeros
#model IEnumerable<Evaluacion>
#{
ViewData["Title"] = "Start Assesment";
var contador = 1;
}
<h1>Start Assesment</h1>
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<form asp-action="Evaluar" method="post">
#foreach (var reactivo in Model)
{
<div class="mb-3">
<label class="form-label">#contador . #reactivo.Pregunta</label>
<div class="form-check">
<input class="form-check-input" type="radio" id="#reactivo.ExamenId" name="#reactivo.ExamenId" value="Respuesta1" checked />
<label class="form-check-label" for="#reactivo.ExamenId">#reactivo.Respuesta1</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" id="#reactivo.ExamenId" name="#reactivo.ExamenId" value="Respuesta2" />
<label class="form-check-label" for="#reactivo.ExamenId">#reactivo.Respuesta2</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" id="#reactivo.ExamenId" name="#reactivo.ExamenId" value="Respuesta3" />
<label class="form-check-label" for="#reactivo.ExamenId">#reactivo.Respuesta3</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" id="#reactivo.ExamenId" name="#reactivo.ExamenId" value="Respuesta4" />
<label class="form-check-label" for="#reactivo.ExamenId">#reactivo.Respuesta4</label>
</div>
</div>
contador++;
}
<button type="submit" class="btn btn-primary">Send Response</button>
</form>
It is supposed to return an answer like "Respuesta1" and it should be stored in the Model.RespuestaAlumno and I just don't have any idea on how can I do it. please help.
In your Model Design, You need a property to receive the value of the selected answer(maybe you already have that property in your model,I don't understand spanish), So I create that property:
public class Evaluacion
{
public int ExamenId { get; set; }
public int RegistroId { get; set; }
public int ReactivoId { get; set; }
public string RespuestaAlumno { get; set; }
public string Pregunta { get; set; }
public string Respuesta1 { get; set; }
public string Respuesta2 { get; set; }
public string Respuesta3 { get; set; }
public string Respuesta4 { get; set; }
public string Answer { get; set; }
}
Then in your View, name uses property names for binding and it asked to start at index 0. I write a simple demo here, you can refer to it:
#model IEnumerable<Evaluacion>
#{
ViewData["Title"] = "Start Assesment";
var contador = 1;
}
<h1>Start Assesment</h1>
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<form asp-action="Evaluar" method="post">
#foreach (var reactivo in Model)
{
int i = contador - 1;
<div class="mb-3">
<label class="form-label">#contador . #reactivo.Pregunta</label>
<div class="form-check ">
<input class="form-check-input" type="radio" name="[#i].Answer" value="#reactivo.Respuesta1" checked />
<label class="form-check-label" for="#reactivo.ExamenId">#reactivo.Respuesta1</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="[#i].Answer" value="#reactivo.Respuesta2" />
<label class="form-check-label" for="#reactivo.ExamenId">#reactivo.Respuesta2</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="[#i].Answer" value="#reactivo.Respuesta3" />
<label class="form-check-label" for="#reactivo.ExamenId">#reactivo.Respuesta3</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="[#i].Answer" value="#reactivo.Respuesta4" />
<label class="form-check-label" for="#reactivo.ExamenId">#reactivo.Respuesta4</label>
</div>
</div>
contador++;
}
<button type="submit" class="btn btn-primary">Send Response</button>
</form>
Finally, The type of the model passed into the controller from the page is IEnumerable, You can't just use Evaluacion to receive, You need to change like this:
[HttpPost]
public async Task<IActionResult> Evaluar(List<Evaluacion> evaluacion)
{
//.......
return View(evaluacion);
}
Demo:
You can see the project can receive the answer successfully.

How can I get the selected item from a Telerik UI for ASP.NET Core DropDownList

I'm using a kendo-dropdownlist tag helper from the Telerik UI for ASP.NET Core library. So far I have been able to bind the values that can be selected, but I can't figure out how to get the selected item when a post request is sent.
I have a login form:
#page
#model PITS.Areas.Authentication.Pages.Login2Model
#{
}
<form method="post">
<input class="form-control k-textbox" asp-for="UserName" type="text" />
<input class="form-control k-textbox" asp-for="Password" type="password" />
<kendo-dropdownlist name="administraties"
filter="FilterType.Contains"
placeholder="Selecteer Administratie"
style="width: 100%;"
bind-to="Model.Organizations">
</kendo-dropdownlist>
<input type="submit" class="btn btn-primary pull-right" value="Login">
</form>
and a PageModel
public class Login2Model : PageModel
{
[BindProperty]
public string UserName { get; set; }
[BindProperty]
public string Password { get; set; }
[BindProperty]
public IEnumerable<SelectListItem> Organizations { get; set; }
public void OnGet()
{
this.Organizations = _getOrganizations();
}
private IList<SelectListItem> _getOrganizations()
{
return new List<SelectListItem>
{
new SelectListItem {Value = Guid.NewGuid().ToString(), Text = "Google"},
new SelectListItem {Value = Guid.NewGuid().ToString(), Text = "Apple"},
new SelectListItem {Value = Guid.NewGuid().ToString(), Text = "Microsoft"}
};
}
}
I would expect an attribute on the kendo-dropdownlist taghelper but I haven't found it yet. Could someone tell me how to get the selected item?
This assumes you are using Razor.
Use the "for" property to bind the kendo-dropdownlist to a page model property.
for="MySelection"
Then in your page model.
public string MySelection { get; set; }

KeyPairValue in ASP.NET MVC3 Razor

I have a weird problem.
I'm making dynamic form in Razor. I'm using dictionary to store dynamically added inputs.
I generate code like that:
<input type="hidden" value="96" name="Inputs[0].Key">
<input type="text" name="Inputs[0].Value">
I receive in my controller this dictionary. It always has as many elements that I added, but all of them are empty.
This is part of my model:
public class MetriceModelTaskSchedule
{
public IEnumerable<KeyValuePair<long, string>> Inputs { get; set; }
}
What can be wrong here?
What can be wrong here?
The fact that the KeyValuePair<TKey, TValue> class has the Key and Value properties which are readonly. They do not have a setter meaning that the model binder simply cannot set their value.
So as always start by defining a view model:
public class InputViewModel
{
public long Key { get; set; }
public string Value { get; set; }
}
and then:
public class MetriceModelTaskSchedule
{
public IEnumerable<InputViewModel> Inputs { get; set; }
}
Alternatively you could use a Dictionary:
public class MetriceModelTaskSchedule
{
public IDictionary<long, string> Inputs { get; set; }
}
Also make sure that you have respected the standard naming convention for your input fields in the view so that the model binder can successfully bind them to your model:
<div>
<input type="text" name="Inputs[0].Key" value="1" />
<input type="text" name="Inputs[0].Value" value="value 1" />
</div>
<div>
    <input type="text" name="Inputs[1].Key" value="2" />
    <input type="text" name="Inputs[1].Value" value="value 2" />
</div>
...

get value in input text box and send it to controller

What are the ways to render the input value filename and send it to a controller :
<div id="fileuploaddiv" class="fileuploaddivclass">
<form action="#Model.FormAction" method="#Model.FormMethod"
enctype="#Model.FormEnclosureType">
<input type="hidden" name="key" value="uploads/${filename}" id="filename" />
<input type="hidden" name="AWSAccessKeyId" value="#Model.AWSAccessKey" />
<input type="hidden" name="Content-Type" value="image/jpeg">
<div>
Please specify a file, or a set of files:
<input type="file" name="file" />
</div>
<input type="submit" value="Upload" />
</form>
</div>
You need to look up some MVC3 conventions (I'd recommend NerdDinner as a good starting tutorial), but here is a somewhat similar approach to what you want to do:
#Model YourViewModel
<div id="fileuploaddiv" class="fileuploaddivclass">
#using(Html.BeginForm(Model.FormAction, Model.FormController, FormMethod.Post)
#Html.HiddenFor(model.key => ${fileName})
#Html.HiddenFor(model.AWSAccessKeyID)
#Html.HiddenFor(model.Content-Type)
#<input type="submit" value="Submit My Form" />
#Html.EndForm()
</div>
Your model would look like (And I'm confused here because you seem to be dynamically setting the controller and action, which is unusual):
public class YourViewModel
{
public string FormAction { get; set; }
public string FormController { get; set; }
public int AWSAccessKeyID { get; set; }
public string Content-Type { get; set; }
}
Now on to controllers:
[HttpGet]
public ActionResult WhateverControllerName()
{
YourViewModel yvm = new YourViewModel();
//Initalize viewmodel here
Return view(yvm);
}
[HttpPost]
public ActionResult WhateverControllerName(YourViewModel yvm)
{
if (ModelState.IsValid) {
//Do whatever you want here. Perhaps a redirect?
}
return View(yvm);
}
Note: I am garbage at syntax, so you'll have to check this, but Visual Studio should tell you what works.

Resources