How to localize error messages in .NET Core 6 - asp.net-core-6.0

I am new to .NET Core 6 and I have realized that several things should be done manually.
One of them is localization. For example, when using a simple action to change the user password, the message "Incorrect password." is returned in the errors collection when the old password mismatch.
I have spent a lot of time trying to localize that simple message so that it is shown in Spanish. I have read a lot of pages telling about this but none works. I think it is because this message is not a DataAnnotation message.
When I used .NET Framework, all of these were made automatically since the resource DLL is always installed by default. It seems that in .NET Core 6 those DLL's are missing, or at least, they are vey hidden.
As an attempt, I added this to Program.cs file:
builder.Services.AddMvc()
.AddViewLocalization(LanguageViewLocationExpanderFormat.Suffix, options => options.ResourcesPath = "Resources")
.AddDataAnnotationsLocalization();
builder.Services.AddLocalization(options => options.ResourcesPath = "Resources");
builder.Services.Configure<RequestLocalizationOptions>(options =>
{
var supportedCultures = new[] { new CultureInfo("en"), new CultureInfo("es") };
options.DefaultRequestCulture = new RequestCulture("es");
options.SupportedCultures = supportedCultures;
options.SupportedUICultures = supportedCultures;
});
app.UseRequestLocalization();
And also added a file "Resouces\ErrorMessages.es.resx" with an entry whose key is PasswordMismatch with the message in Spanish, but no avail.
Any help, please?

Alought I couldn't check the details of your resourcefile,I think there's something wrong with your resourcefile for the name you've shown
Resouces\ErrorMessages.es.resx
I tried in my case as below:
public class TestModel
{
[Required(ErrorMessage = "The Email field is required.")]
[EmailAddress(ErrorMessage = "The Email field is not a valid email address.")]
[Display(Name = "Email")]
public string Email { get; set; }
[Required(ErrorMessage = "The Password field is required.")]
[StringLength(8, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)]
[DataType(DataType.Password)]
[Display(Name = "Password")]
public string Password { get; set; }
[DataType(DataType.Password)]
[Display(Name = "Confirm password")]
[Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
public string ConfirmPassword { get; set; }
}
in Razor View:
<div class="row">
<div class="col-md-4">
<form asp-action="Create">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Email" class="control-label"></label>
<input asp-for="Email" class="form-control" />
<span asp-validation-for="Email" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Password" class="control-label"></label>
<input asp-for="Password" class="form-control" />
<span asp-validation-for="Password" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="ConfirmPassword" class="control-label"></label>
<input asp-for="ConfirmPassword" class="form-control" />
<span asp-validation-for="ConfirmPassword" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
</div>
</form>
</div>
</div>
It works well:
Update:
Since You are not interested in developing a multilingual site,I think the most simlpe solution is Custom an Attribute as below:
public class CusRequiredAttribute: RequiredAttribute
{
public CusRequiredAttribute()
{
//You could wrote the errormessage in your language as well
ErrorMessage = "{0}Required";
}
}
add [CusRequired]attrbute on the target property
for developing a multilingual site
Create an empty class named SharedResources and a resource file as below:
set as below in startup:
services.AddControllersWithViews()
.AddDataAnnotationsLocalization(
options =>
{
options.DataAnnotationLocalizerProvider = (type, factory) =>
factory.Create(typeof(SharedResources));
});
Result:

Related

How to use seach function by "int" in ASP.NET Core MVC?

I use this way add seach function.
But it only can search by string.
I want to add a function of seach by "int".
Anyone have suggest?
public async Task<IActionResult> Index(string searchString)
{
var movies = from m in _context.Movie
select m;
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}
return View(await movies.ToListAsync());
}
And it is my resource
https://learn.microsoft.com/en-us/aspnet/core/tutorials/first-mvc-app/search?view=aspnetcore-5.0
Also I have try like this way,but it seem didn't work
Search By Number in ASP.NET MVC
To search value by Int parameter, it is similar like search by string. In the View page, set the element's name attribute as same as the parameter name in the action method.
You can check the following sample:
<form asp-action="Index" method="get">
<div class="form-actions no-color">
<p>
Find by name: <input type="text" name="searchString" value="#ViewData["CurrentFilter"]" />
Find by ID: <input type="text" name="searchId" value="#ViewData["CurrentID"]" />
<input type="submit" value="Search" class="btn btn-default" /> |
<a asp-action="Index">Back to Full List</a>
</p>
</div>
</form>
Controller:
public async Task<IActionResult> Index(string searchString, int searchId)
{
var students = from stu in _context.Students
select stu;
//if (!String.IsNullOrEmpty(searchString))
//{
// students = students.Where(s => s.LastName.Contains(searchString.ToString()));
//}
if (searchId !=0)
{
students = students.Where(s => s.ID == searchId );
}
ViewData["CurrentFilter"] = searchString;
ViewData["CurrentID"] = searchId;
return View(await students.ToListAsync());
}
Then, the result as below:

mvc 5 custom validation for variables inside of model object

I have searched for this question in multiple places and was unable to find exactly what I am looking for. Let's say I have this MVC Model Structure:
public class Person {
[Required]
public string Name { get; set; }
public int Age { get; set; }
}
public class Workers {
[AgeRequired]
public Person Pilots { get; set; }
public Person Chefs { get; set; }
}
and here would be my cshtml code:
#Model Workers
<div>
<label asp-for="Pilots.Name"></label>
<input asp-for="Pilots.Name"></input>
<span asp-validation-for="Pilots.Name"></span>
</div>
<div>
<label asp-for="Pilots.Age"></label>
<input asp-for="Pilots.Age"></input>
<span asp-validation-for="Pilots.Age"></span>
</div>
<div>
<label asp-for="Chefs.Name"></label>
<input asp-for="Chefs.Name"></input>
<span asp-validation-for="Chefs.Name"></span>
</div>
<div>
<label asp-for="Chefs.Age"></label>
<input asp-for="Chefs.Age"></input>
<span asp-validation-for="Chefs.Age"></span>
</div>
Person is a generic Model class that holds information about Pilots or Chefs. What I want is for my AgeRequired Custom Validation Attribute to make Age required only when referring to Pilots, not Chefs. Would that be possible?
I have it working on the backend side, after the form has been submitted, however I would like this to be on the front end as well. Here is my code for my Attribute:
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
public class AgeRequiredAttribute: ValidationAttribute, IClientModelValidator
{
public override bool IsValid(object value)
{
Workers workers = value as Workers;
return workers.Age > 0;
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ClientModelValidationContext context)
{
yield return new ModelClientValidationRule("agerequired", "{0} is a required field.");
}
}
}
and here is my javascript code for the front end validation:
/// <reference path="jquery.validate.js" />
/// <reference path="jquery.validate.unobtrusive.js" />
$.validator.addMethod("agerequired",
function (value, element, parameters) {
return value > 0;
});
$.validator.unobtrusive.adapters.add("agerequired", [], function (options) {
options.rules.agerequired= {};
options.messages["agerequired"] = options.message;
});
ClientValidationEnabled and UnobstrusiveJavaScriptEnabled are both set to true.
This custom attribute will work when I have it on the Age field itself, but that makes it required for both Pilots and Chefs. I only want it required for Pilots.
Thank you in advance for your help!
I was actually able to create a work around for those who are interested.
If you put in the data-val-[attribute] that would get generated by MVC directly into your input tag or select tag, followed by the error message you want to throw, it will do the front end validation, and still do the back end validation as MVC will notice that the complex object has information in it. It's not ideal, but probably what I will have to do.
For example:
#Model Workers
<div>
<label asp-for="Pilots.Name"></label>
<input asp-for="Pilots.Name"></input>
<span asp-validation-for="Pilots.Name"></span>
</div>
<div>
<label asp-for="Pilots.Age"></label>
<input asp-for="Pilots.Age" data-val-agerequired="Age is Required for Pilots."></input>
<span asp-validation-for="Pilots.Age"></span>
</div>
<div>
<label asp-for="Chefs.Name"></label>
<input asp-for="Chefs.Name"></input>
<span asp-validation-for="Chefs.Name"></span>
</div>
<div>
<label asp-for="Chefs.Age"></label>
<input asp-for="Chefs.Age"></input>
<span asp-validation-for="Chefs.Age"></span>
</div>
Will work. It's not ideal, but it allows us to keep the MVC Back End validation in the same spot.
Another option is to have person age as optional in a base class.
public int? Age { get; set; }
Inherit Chef and Pilot from Person. In pilot, make the age value non-optional - you'll probably want to make sure they have a minimal age too using Data Annotations
public new int Age { get; set; }
The model is now a list of Persons. The client should be able to work out which is optional and which is not

ASP.NET core persisting values between Get and Post error validation

I'm new to web development so I don't know a good way on how to persist data between requests.
This is my site so far:
The elephant title is being fetched from an API on the GET request, according to the titleId query parameter. When I press login, model validations are being run, for example that email and password must have been entered. However, when error page is returned, elephant text is empty since that value was not persisted. What are the best approaches to persist that value so that is still visible when POST error is returned? Does it has to be included in the POST data? I don't want to request the API again.
Code behind:
public class IndexModel : PageModel
{
private string apiTitle;
public string ApiTitle { get { return apiTitle; } set { apiTitle = value; } }
// Bind form values
[BindProperty]
public User user { get; set; }
public Task<IActionResult> OnGetAsync(string titleId)
{
if (!string.IsNullOrEmpty(titleId))
{
ApiTitle = await GetTitleFromApiAsync(titleId);
}
return Page();
}
public async Task<IActionResult> OnPostLoginAsync()
{
if (!IsLoginFormValid())
{
// When this is returned, for example if no password was entered,
// elephant title goes missing since apiTitle is null?
return Page();
}
var user = await LoginEmailAsync();
return RedirectToPage("/Profile");
}
}
Html:
#page
#model IndexModel
#{
ViewData["Title"] = "Home page";
}
<link rel="stylesheet" href="css/index.css">
<script src='http://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js'></script>
<div class="login-page">
<span id="label_api_title">#Model.ApiTitle</span>
<div class="form">
<form class="login-form" method="post" asp-page-handler="Login">
<input type="text" placeholder="Email" asp-for="User.Email"/>
<span asp-validation-for="User.Email" class="text-danger"></span>
<input type="password" placeholder="Password" asp-for="User.Password1" />
<span asp-validation-for="User.Password1" class="text-danger"></span>
<button>login</button>
<p class="message">Not registered? Create an account</p>
</form>
</div>
</div>
<script src="js/index.js"></script>
Yes. What you see is the expected behavior. Remember, Http is stateless. You are making 2 separate http calls, one for the GET and one for POST. The second call has no idea what the first call did ( or even there was first call at all!)
If you want to have a way to read the ApiTitle property value in the Post call and return that to the view, you need to persist it somewhere so that it is available between http calls. But in your case, all you need is to include that in the form post and have the framework bind it for you.
In your case, you can simply use a public property (Which is settable and gettable) for this. No need to keep a private variable. Decorate your property with BindProperty attribute so the model binder will bind the data on this property.
public class CreateModel : PageModel
{
[BindProperty]
public string ApiTitle { get; set; }
//Your existing code goes here
}
Now inside your form tag, have an input hidden element for the ApiTitle. This way, when the form is submitted, the value of ApiTitle property will be send in the request data.
<form class="login-form" method="post" asp-page-handler="Login">
<input type="hidden" asp-for="ApiTitle"/>
<input type="text" placeholder="Email" asp-for="User.Username"/>
<!--Your other existing form elements -->
<button>login</button>
</form>
Now in your OnPostLoginAsync method, you can read the ApiTitle value if needed. When you return the Page (when validation fails), the UI will display the ApiTitle property value in your span element.
public async Task<IActionResult> OnPostLoginAsync()
{
var title = this.ApiTitle; // If you want to read this value
if (!ModelState.IsValid)
{
return Page();
}
return RedirectToPage("/Profile");
}
It could be that your not sending it inside your form in the razor and its only in the get request and not in the post request:
public Task<IActionResult> OnGetAsync(string titleId)//<----its here
{
if (!string.IsNullOrEmpty(titleId))
{
ApiTitle = await GetTitleFromApiAsync(titleId);
}
return Page();
}
//-->need to pass title id in post
public async Task<IActionResult> OnPostLoginAsync(string titleId)
{
if (!string.IsNullOrEmpty(titleId))
{
ApiTitle = await GetTitleFromApiAsync(titleId);
}
if (!IsLoginFormValid())
{
// When this is returned, for example if no password was entered,
// elephant title goes missing since apiTitle is null?
return Page();
}
var user = await LoginEmailAsync();
return RedirectToPage("/Profile");
}
and in your razor add the span in the form:
<span id="label_api_title">#Model.ApiTitle</span>
<div class="form">
<form class="login-form" method="post" asp-page-handler="Login">
<span id="label_api_title">#Model.ApiTitle</span>/*<--------here*/
<input type="text" placeholder="Email" asp-for="User.Email"/>
<span asp-validation-for="User.Email" class="text-danger"></span>
<input type="password" placeholder="Password" asp-for="User.Password1"/>
<span asp-validation-for="User.Password1" class="text-danger"></span>
<button>login</button>
<p class="message">Not registered? Create an account</p>
</form>

Asp.Net MVC5 - Editorfor template, how to set field as not required?

I'm trying to use an editor template for a complex property, but the fields in the template is by default set to require the user to fill them in. Is there an easy way to avoid this and still use 'editorfor'? I know there is a [required] data annotation, but i'm not using it and i can't find the opposite command. The template displays correctly, i just don't want the fields to be required.
Property in my view model:
[UIHint("EditPriceInfo")]
[Display(Name="Price info")]
public PriceInfo PriceInfo { get; set; }
From the template file '/Views/Shared/EditorTemplates/EditPriceInfo.cshtml'. (Created by scaffolding from the class 'PriceInfo', see below.):
#model MyProject.Models.PriceInfo
<div class="form-group">
#Html.LabelFor(model => model.HourPrice, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.HourPrice)
#Html.ValidationMessageFor(model => model.HourPrice)
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.DayPrice, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.DayPrice)
#Html.ValidationMessageFor(model => model.DayPrice)
</div>
</div>
Call to the template from the 'Edit'-view:
<h4>#Html.DisplayNameFor(model => model.PriceInfo)</h4>
<div class="form-group">
<div class="col-md-10">
#Html.EditorFor(model => model.PriceInfo)
#Html.ValidationMessageFor(model => model.PriceInfo)
</div>
</div>
And the properties in the original 'PriceInfo' entity:
[Display(Name = "Price/hour")]
public decimal HourPrice { get; set; }
[Display(Name = "Price/day")]
public decimal DayPrice { get; set; }
According to the accepted answer for this related question:
ModelMetadata.IsRequired will tell you if a complex type (or value type wrapped in Nullable) is required by a RequiredAttribute, but it will give you false positives for value types that are not nullable (because they are implicitly required).
This means that the property in my example would be 'implicitly required' since it's a complex type that's not wrapped in Nullable<T>. I solved the issue by setting the properties in the 'PriceInfo' entity class to be nullable, like this (question marks after the type):
[Display(Name = "Price/hour")]
public decimal? HourPrice { get; set; }
[Display(Name = "Price/day")]
public decimal? DayPrice { get; set; }

Model object passed to HttpPost action is having null values

I have a model with properties declared, Controller actions. and View with Viewmodel specified. I fill data in the form and submit, but model has only null values for all properties. If i try with view model i get same null values in HttpPost action.
My Model:
public class Supplier
{
public string SupplierSequenceNumber { get; set; }
public string SupplierName { get; set; }
public string SupplierActive { get; set; }
}
My Controller:
[HttpGet]
public ActionResult Add()
{
SupplierVM objSupplierVM = new SupplierVM();
return View(objSupplierVM);
}
[HttpPost]
public ActionResult Add(Supplier objSupplier)
{
return View();
}
My View:
#model AIEComm.ViewModel.SupplierVM
#using (Html.BeginForm("Add", "Supplier", FormMethod.Post, new { id = "formAddSupplier" }))
{
<div class="control-group">
#Html.LabelFor(m=>m.objSupplier.SupplierName, new{#class = "control-label"})
<div class="controls">
#Html.TextBoxFor(m => m.objSupplier.SupplierName, new { placeholder = "Swatch Style" })
#Html.ValidationMessageFor(m=>m.objSupplier.SupplierName)
</div>
</div>
<div class="control-group">
#Html.LabelFor(m=>m.objSupplier.SupplierActive, new{#class = "control-label"})
<div class="controls">
#Html.DropDownListFor(m=>m.objSupplier.SupplierActive,new SelectList(AIEComm.Models.Utilities.YesNoSelectList,"Value","Text"),new{#class=""})
#Html.ValidationMessageFor(m=>m.objSupplier.SupplierName)
</div>
</div>
<div class="control-group">
<div class="controls">
<input type="submit" class="btn btn-primary" id="btnSubmit" value="Add"/>
</div>
</div>
}
The reason for this is the following code:
m => m.objSupplier.SupplierName
You're generating HTML elements with a model that is inside a ViewModel. This is a good approach, and your problem can be solved quite easily.
It's a good approach because you're keeping things organised, but it's not working because the above code is saying:
Ok, using the ViewModel object (m), take the objSupplier object and then use the SupplierName property.
This is fine, but then when you're submitting data, you're saying (to the action):
Hi, I have a SupplierName property. Please put this into the objSupplier object which you can find inside the ViewModel object.
To which the action says "Well, I am expecting an objSupplier object, but what's this ViewModel you speak of?"
A simple solution is to create a new partial view to generate your form. It's model type should be:
_SupplierForm.cshtml
#model Supplier
#* // The Form *#
In your View, continue to use the same ViewModel, but pass in the correct supplier model:
#model AIEComm.ViewModel.SupplierVM
#Html.Partial("_SupplierForm", Model.objSupplier)

Resources