Asp.net core server side localization is well documented and working for me. But how do you localize DataAnnotations on DTO models on the client side of Blazor webassembly?
On server side I've added the code below and DataAnnotations are localized. Everything is working as expected.
...
services
.AddRazorPages() .AddViewLocalization(Microsoft.AspNetCore.Mvc.Razor.LanguageViewLocationExpanderFormat.Suffix)
.AddDataAnnotationsLocalization(
options =>
{
options.DataAnnotationLocalizerProvider = (type, factory) =>
{
return factory.Create(typeof(CommonStrings));
};
});
...
But how do I do the same thing on Blazor client side (webassembly)?
For example I have this model which is on client side:
public class ApplicationUserDTO
{
public string Id { get; set; }
[Required(ErrorMessage ="Field {0} is required")]
[Display(Name ="First name")]
public string FirstName { get; set; }
[Required]
[Display(Name = "Last name")]
public string LastName { get; set; }
[Required]
[Display(Name = "Email")]
public string Email { get; set; }
[Required]
[Display(Name = "Username")]
public string Username { get; set; }
}
I want to post it to backend via <EditForm> component, and before I do that do the validation on client side.
I also want to localize it like i would on aspnet.core server - Error/validation messages and display names...
I tried with LocalizedValidator component:
public class MessageValidatorBase<TValue> : ComponentBase, IDisposable
{
private FieldIdentifier _fieldIdentifier;
private EventHandler<ValidationStateChangedEventArgs> _stateChangedHandler
=> (sender, args) => StateHasChanged();
[CascadingParameter]
private EditContext EditContext { get; set; }
[Parameter]
public Expression<Func<TValue>> For { get; set; }
[Parameter]
public string Class { get; set; }
protected IEnumerable<string> ValidationMessages =>
EditContext.GetValidationMessages(_fieldIdentifier);
protected override void OnInitialized()
{
_fieldIdentifier = FieldIdentifier.Create(For);
EditContext.OnValidationStateChanged += _stateChangedHandler;
}
public void Dispose()
{
EditContext.OnValidationStateChanged -= _stateChangedHandler;
}
}
and then created component:
#typeparam TValue
#inherits MessageValidatorBase<TValue>
#inject StringLocalizationService _localizer
#foreach (var message in ValidationMessages)
{
<div class="#Class">
#_localizer[message]
</div>
}
but the problem is I get already expanded string here. For example if I have error message like this "The field {0} is required" I get "The field First name is required" which will not be localized since I don't have the resource with that key and I don't intend to translate the same error message for every property name...
[EDIT]
I just want to know if there is something trivial I didn't do instead of implementing it completely on my own
WebAssembly example.
Example property
[MaxLength(5, ErrorMessageResourceName = "LengthError", ErrorMessageResourceType = typeof(Resources.App))]
public string Prefix { get; set; }
Create a folder in your client called Resources.
Add a `.resx' file for each language plus a default (no language).
Make sure your set the access Modifier to Public
Example output in French.
Related
I am getting an error
Validation failed for one or more entities. See 'EntityValidationErrors' property for more details.
when trying to submit on a simple create page. Below is the controller code and model. I can not figure out what the issue is.
All fields except ID are nullable in SQL. I know that the issue is coming from the fields resolution and technician - if I put them on the create form (which they are not on it now as I do not want them filled out) the submit works fine Any ideas?
Thanks,
EB
Controller:
public ActionResult Create()
{
HelpDesk b1 = new HelpDesk();
return View(b1);
}
[HttpPost]
public ActionResult Create(HelpDesk model)
{
db.HelpDesks.Add(model);
db.SaveChanges();
return RedirectToAction("Index");
}
Model:
public int ID { get; set; }
[DisplayName("Requested By")]
public string RequestedBy { get; set; }
[Required(ErrorMessage = "Requested By Required.")]
public string Request { get; set; }
[Required(ErrorMessage = "Request Required.")]
public string Resolution { get; set; }
[DisplayName("Assigned To")]
public string Technician { get; set; }
public string Status { get; set; }
public string CreatedBy { get; set; }
public string ModfiedBy { get; set; }
public Nullable<System.DateTime> CreateDate { get; set; }
public Nullable<System.DateTime> ModifiedDate { get; set; }
All fields except ID is nullable in SQL.
That's not what you told Entity Framework, which is what's throwing the error. (And, as the error indicates, you should really check the EntityValidationErrors property on the exception, or an inner exception, for specific information about the error.) You told Entity Framework that these fields are required:
[Required(ErrorMessage = "Requested By Required.")]
public string Request { get; set; }
[Required(ErrorMessage = "Request Required.")]
public string Resolution { get; set; }
(It looks like you may have mixed up some of the property attributes, judging by the messages on them.)
I know that the issue is coming from the fields resolution and technician - if I put them on the create form (which they are not on it now as I do not want them filled out) the submit works fine
Sounds like that's the problem then. Resolution is marked as required, and you're not including it. Either include it or don't make it required.
I am developing a ASP.NET MVC 3 application, i am using entity framework code first in order to create the classes of my app, and i also have a repository in order to perform the operations on it, keeping clean the DBContext and the DBEntities definitions.
My doubt is about the render of the views and the way where a edit model is saved.
If I have this entity that represent a user stored in my DB:
//Entity:
public class User
{
[Key]
public int IdUser { get; set; }
public string UserName { get; set; }
public string Password { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
}
And i want to show a View with the FirstName, LastName, Email and NewPassword, ConfirmPasword and CurrentPassword, in order to let the user change his data, typing the CurrentPassword to confirm the changes, so my doubt is, fieds like ConfirmPasword and CurrentPassword aren´t in my entity so i need to create a new model for this View and the copy the information that i want from my new Model to my database entity in order to save it? Like:
public class UpdateUserModel
{
[Required]
[Display(Name = "Name")]
public string FirstName{ get; set; }
[Required]
[Display(Name = "Last Name")]
public string LastName{ get; set; }
[Required]
[DataType(DataType.EmailAddress)]
[Display(Name = "Not valid email")]
public string Email { get; set; }
[DataType(DataType.Password)]
[Display(Name = "New password")]
public string NewPasword{ get; set; }
[DataType(DataType.Password)]
[Display(Name = "Confirm the New Pasword")]
[Compare("NewPasword", ErrorMessage = "Password doesn´t mach.")]
public string ConfirmPasword{ get; set; }
[Required(ErrorMessage = "Need to specify the current password to save changes")]
[DataType(DataType.Password)]
[Display(Name = "Current Password")]
public string CurrentPassword { get; set; }
}
and in the controller i made:
public ActionResult UpdateUser(UpdateUserModel model)
{
User u = (User)Membership.GetUser();
u.FirstName = model.FirstName;
u.LastName = model.LastName;
u.Email = model.Email;
if (!String.IsNullOrEmpty(model.NewPassword))
{
u.Password = FormsAuthentication.HashPasswordForStoringInConfigFile(model.NewPassword.Trim(), "md5");
}
repository.UpdateUser(u);
return View();
}
There are any way of doing this having a controller like:
public ActionResult UpdateUser(User u)
{
repository.UpdateUser(u);
return View();
}
Because if i have that, how i can add the field like, ConfirmPassword or CurrentPassword in order to made the validation for this specific View.
If I were you, I wouldn't use domain model in my presentation layer. I would create a view model (another class) which will be very similar to my domain model. I would then use auto-mapping tool to map from my domain model to the view model.
This is a very common scenario, so if you Google for "view and domain" models you should find everything you need.
public class User {
[Key]
public int IdUser { get; set; }
public string UserName { get; set; }
public string Password { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
}
public class UpdateUserViewModel {
// Original fields
public string Password { get; set; }
public string PasswordConfirmation { get; set;
}
You could then configure auto-mapper to remove your boiler plate code:
public ActionResult ShowUser()
{
var domainModel = new User(); // I'm assuming that properties are set somewhere
var viewModel = new UserViewModel();
Autommaper.Map(domainModel, viewModel);
return View(viewModel);
}
This is very rough, but hopefully you get an idea.
Update 1: **
As i understood is better to create a new model for each view and then map it into the entity
It's not just better, it provides better separation of concerns, makes your code easily testable. Just by looking at the name of the class, I can see its purpose UpdateUserViewModel, RegisterUserViewModel etc).
Original fields, in this class is supposed to be the Metadata with the validation and that stuff isn't?
By original fields I mean:
public class UserViewModel{
public string UserName { get; set; }
public string FirstName { get; set; }
}
These fields are already in your User class, so I saved my time by not typing them in again.
This will be change my model from MVC to MVVM or not beacuse i still have a controller?
I believe what I've suggested is still an MVC pattern, rather than MVVM.
About the Automaper, are you using github.com/AutoMapper/AutoMapper?
Automapper is something that I have used. There are few tools out there and they do pretty much the same thing. Try out few and find one that suits your requirements the most.
Good luck.
Usually I use areas for different parts of my project, as an aside of where to put this extra code.
Pretty much you are going to add to your model folder a viewmodel.cs class. Inside this class will hold your definitions for how the data will be modelled in the view. These viewmodels will reflect the parts of the entity you wish the user to interact with. The interaction will be done in the controllers via [HttpGet] where you pass in the view model to be interacted with, and [HttpPost] where you send the model back and then map it to an entity.
ViewModels.cs:
public class UserViewModel
{
public string UserName { get; set; }
}
SomeController:
public ActionResult getView()
{
var uvm = new UserViewModel();
return View(uvm);
}
View getView.cshtml:
#model project.namespace.UserViewModel
#using (Html.BeginForm())
{
#Html.EditorFor(m => m.UserName)
<input type="submit" value="New User Name" />
}
Back in controller:
[HttpPost]
public ActionResult getView(UserViewModel model)
{
var entity = new ActualEntity();
entity.username = model.UserName;
//more mapping
//commit changes somewhere
return RedirectToAction("getView");
}
For the edition of my user, I've to ensure that password and the repeat password are the same. I found the "Compare" validator, but I cant make it work.
my model looks like the following:
public class UserEditionViewModel{
[Compare("User.Password")]
public String RepeatPassword{get;set;}
public User User {get;set;}
public List<Language> AvailableLanguages{get;set;}
public List<Country> AvailableCountries{get;set;}
}
and the User model:
public class User{
[Required]
public String Name{get;set;}
//lot of other properties omitted...
[RegularExpression(#"(|.*(?=.{6,})(?=.*\d)(?=.*[a-zA-Z]).*)", ErrorMessageResourceType = typeof(LocalizationResources.Views.User.Edition), ErrorMessageResourceName = "InvalidPassword")]
//And I've localization attributes
public String Password{get;set;}
}
In the view I only have something like:
#Html.PasswordFor(m=>m.User.Password)
#Html.PasswordFor(m=>m.RepeatPassword)
But I ever get this error, even if the two items are matching:
'Password repeat' and 'User.Password' do not match.
I also got this error when I'm doing the client validation.
For me the most obvious error is that it can't found the subproperty. Am I right? If yes, how to avoid this behavior. If no, what can be the problem???
A workaround would be to create another property on the UserEditionViewModel that reads and writes to the inner Userclass.
public String UserPassword
{
get
{
return User.Password;
}
set
{
User.Password = value;
}
}
And then bind your controls to that property instead, and change the [Compare("User.Password")] to [Compare("UserPassword")]. I'm not really sure if it can be done any other way short of writing your own custom validator.
I had a similar problem and ended up writing my own validator for this which turned out surprisingly complex since you can have any layer of inheritance to get to your property. If there is another solution, I'd be equally happy to know about it.
You can try this which worked for me..
In your project -> References-> right click->Manage NuGet Packages..
install DataAnnotationsExtensions package.
Then validate your model as follows:
public class Employee
{
[Required(ErrorMessage="Name field Required")]
public string name { get; set; }
[Required(ErrorMessage = "Name field Required")]
public string email { get; set; }
[Required(ErrorMessage = "Depatrment field Required")]
public string department { get; set; }
[Required(ErrorMessage = "Designation field Required")]
public string designation { get; set; }
public string phone { get; set; }
[Required(ErrorMessage = "Password field Required")]
[Display(Name="Password")]
public string password { get; set; }
[Required(ErrorMessage="Confirm password")]
[Display(Name="Re-type Password")]
[EqualToAttribute("password",ErrorMessage="Password miss-match")]
public string Re_Password { get; set; }
}
That's it
I'm trying to develop a fairly simple app, for use in the office and for my own training purposes.
At present I am a little stuck with getting a dropdownlist to appear.
The Data Structure
table: Resource
ResourceID (int) (PK)
Name (varchar)
Description (varchar)
ResourceTypeID (int) (FK)
table: REsourceType
ResourceTypeID (int) (PK)
Title (varchar)
Description (varchar)
So a simple datastructure. Each Resource is of one type, but each type can be applied to many resources. I have 3 model files:
Resource
namespace ESF_ResourceManager.Models
{
public class Resource
{
[Key]
public int ResourceID { get; set; }
[Required(ErrorMessage="Please enter a name for the resource")]
[StringLength(50, ErrorMessage="Resource name is too long, 50 characters or less")]
public string Name { get; set; }
[Required(ErrorMessage="Please neter a meaningful description of this resource")]
public string Description { get; set; }
[Required(ErrorMessage="Please specify the turn around time in minutes")]
public int TurnAroundTime { get; set; }
// links resource to a resource type
public int ResourceTypeID { get; set; }
//public virtual ICollection<ResourceType> ResourceTypes { get; set; }
// public virtual ResourceType ResourceTypeID { get; set; }
}
}
ResourceType
namespace ESF_ResourceManager.Models
{
public class ResourceType
{
[Key]
public int ResourceTypeID { get; set; }
[Required(ErrorMessage = "Please enter a title for the resource type")]
[StringLength(50, ErrorMessage = "Resource type name is too long, 50 characters or less")]
public string Title { get; set; }
[Required(ErrorMessage = "Please enter a meaningful description for the resource type")]
public string Description { get; set; }
public virtual ICollection<Resource> Resources { get; set; }
//public virtual Resource Resource {get; set;}
}
}
ResourceAdminManager
namespace ESF_ResourceManager.Models
{
public class ResourceAdminManager : DbContext
{
public DbSet<Resource> Resources { get; set; }
public DbSet<ResourceType> ResourceTypes { get; set; }
}
}
I have developed the views and controllers for both the Resource and ResourceType. ResourceType is really straight forward and that set is working just fine. The trouble I am having is understanding how to get the Resource views for Create and Edit to display the options for ResourceType in a drop down list and how to display the title of the ResourceType not the ID in all the views.
I have spent quite some time looking over this and have not yet found anything the aids my understanding. So, please as a newbie take it easy with me and I'm sure I'll get there with your help.
Many thanks
nathj07
View Model - CreateResourceViewModel.cs:
public class CreateResourceViewModel
{
public Resource Resource { get; set; }
public ICollection<ResourceType> ResourceTypes { get; set; }
}
Controller (ResourceController.cs):
[HttpGet]
public ActionResult Create()
{
var dbContext = new ResourceDbContext();
var model = new CreateResourceViewModel
{
Resource = new Resource(),
ResourceTypes = dbContext.ResourceTypes.ToList()
};
return View(model);
}
[HttpPost]
public ActionResult Create(CreateResourceViewModel model)
{
// process model
return RedirectToAction("Success");
}
View (Create.cshtml):
#model ResourceMVC.Models.CreateResourceViewModel
<div class="editor-label">
#Html.LabelFor(x => x.Resource.ResourceTypeId)
</div>
<div class="editor-field">
#Html.DropDownListFor(x => x.Resource.ResourceTypeId,
new SelectList(Model.ResourceTypes, "ResourceTypeId", "Title"),
"-- Select Resource Type --")
</div>
This will bind the Id of the selected ResourceType to the ResourceTypeId of the new Resource object when it is POSTed back to the ResourceController.
You haven't really associated the ResourceTypes to a Resource yet.
Add to Resource:
[Include]
[Association("GiveItAName", "ResourceID", "ResourceTypeID")]
public EntityCollection<ResourceType> ResourceTypes { get; set; }
or
[Include]
[Association("GiveItAName", "ResourceID", "ResourceTypeID")]
public ResourceType ResourceType { get; set; }
Use [ForeignKey] attribute to mark the ID fields as FK field.
Then you should be able to do things like MyResource.ResourceType.Title Use Fiddler and the WCF Binary add-in to verify that EF is returning the objects and properly including the child elements.
I am developing a application for Sales Order Management using ASP.NET MVC 3.0. I need to develop a page where Customer Details can be added.
Customer Details Include
public class Customer
{
public int ID { get; set; }
public string Code { get; set; }
public string Name { get; set; }
public string Alias { get; set; }
public int DefaultCreditPeriod { get; set; }
public Accounts Accounts { get; set; }
public IList<Address> Addresses { get; set; }
public IList<Contact> Contacts { get; set; }
}
public class Accounts
{
public int ID { get; set; }
public string VATNo { get; set; }
public string CSTNo { get; set; }
public string PANNo { get; set; }
public string TANNo { get; set; }
public string ECCNo { get; set; }
public string ExciseNo { get; set; }
public string ServiceTaxNo { get; set; }
public bool IsServiceTaxApplicable { get; set; }
public bool IsTDSDeductable { get; set; }
public bool IsTCSApplicable { get; set; }
}
public class Address
{
public int ID { get; set; }
public AddressType Type { get; set; }
public string Line1 { get; set; }
public string Line2 { get; set; }
public string Line3 { get; set; }
public string Line4 { get; set; }
public string Country { get; set; }
public string PostCode { get; set; }
}
public class Contact
{
public int ID { get; set; }
public ContactType Type { get; set; }
public string Name { get; set; }
public string Title { get; set; }
public string PhoneNumber { get; set; }
public string Extension { get; set; }
public string MobileNumber { get; set; }
public string EmailId { get; set; }
public string FaxNumber { get; set; }
public string Website { get; set; }
}
Customer Requires a single page to fill all the customer details(General info, Account Info,Address Info and Contact Info). There will be multiple Addresses(Billing, Shipping, etc) and multiple Contacts (Sales, Purchase). I am new to MVC. How to Create the View for the above and Add multiple Address dynamically?
I often create wrapper models to handle this kind of situation e.g.
public class CustomerWrapperModel
{
public Customer Customer { get; set;}
public Accounts Accounts { get; set;}
public List<Address> AddressList { get; set}
//Add
public CustomerWrapperModel()
{
}
//Add/Edit
public CustomerWrapperModel(Customer customer, Accounts accounts, List<Address> addressList)
{
this.Customer = customer;
this.Accounts = accounts;
this.AddressList = addressList;
}
}
then declare the View to be of type CustomerWrapperModel and use editors like so:
#model MyNamespace.CustomerWrapperModel
#Html.EditorFor(model => model.Customer)
#Html.EditorFor(model => model.Accounts)
#Html.EditorFor(model => model.AddressList)
and have a controller to receive the post that looks like this:
[HttpPost]
public ActionResult(Customer customer, Accounts accounts, List<Address> addressList)
{
//Handle db stuff here
}
As far as adding addresses dynamically I found the best way to do this if you're using MVC validation and want to keep the list structured correctly with the right list indexes so that you can have the List parameter in your controller is to post the current Addresses to a helper controller like this:
[HttpPost]
public PartialResult AddAddress(List<Address> addressList)
{
addressList.Add(new Address);
return PartialView(addressList);
}
then have a partial view that just renders out the address fields again:
#model List<MyNamespace.Address>
#{
//Hack to get validation on form fields
ViewContext.FormContext = new FormContext();
}
#Html.EditorForModel()
make sure you address fields are all in one container and then you can just overwrite the existing ones with the returned data and your new address fields will be appended at the bottom. Once you have updated your container you can do something like this to rewire the validation:
var data = $("form").serialize();
$.post("/Customer/AddAddress", data, function (data) {
$("#address-container").html(data);
$("form").removeData("validator");
$("form").removeData("unobtrusiveValidation");
$.validator.unobtrusive.parse("form");
});
NB. I know some people with have an issue with doing it this way as it requires a server side hit to add fields to a page that could easily just be added client side (I always used to do it all client side but tried it once with this method and have never gone back). The reason I do it this way is because it's the easiest way to keep the indexes on the list items correct especially if you have inserts as well as add and your objects have a lot of properties. Also, by using the partial view to render the data you can ensure that the validation is generated on the new fields for you out of the box instead of having to hand carve the validation for the newly added client side fields. The trade off is in most cases a minor amount of data being transferred during the ajax request.
You may also choose to be more refined with the fields you send to the AddAddress controller, as you can see I just post the entire form to the controller and ignore everything but the Address fields, I am using fast servers and the additional (minor) overhead of the unwanted form fields is negligible compared to the time I could waste coding this type of functionality in a more bandwidth efficient manner.
You pass your root model object to the View call in your controller like this:
public ActionResult Index() {
var customer = GetCustomer(); // returns a Customer
return View(customer);
}
And then your view looks something like this:
#model Customer
<!DOCTYPE html>
<!-- etc., etc. -->
<h1>Customer #Model.Name</h1>
<ul>
#foreach (var address in Model.Addresses) {
<li>#address.Line1</li>
}
</ul>
One gets the picture.
The code above depends on the #model directive, which is new in ASP.NET MVC 3 (see this blog post).
Is a good question :D for normal navigation properties such as Accounts doing this is not to hard:
#Html.EditorFor(model => model.Accounts.ID)
#Html.EditorFor(model => model.Accounts.VATNo)
will do something you want. But for collection navigation properties (Addresses and Contacts) you can't do this in one place by default. I suggest you use a different page for Addresses (and one for Contacts). Because it is the easiest way. But if you want to do this in one place (and also with out AJAX requests), you can create view by Customer, use scaffolding for model and it's simple navigation properties, and for lists (Addresses, Contacts) you must add them with JavaScript to the input fields (for example for each Address added, put it in an Array) and post fields to server. At server you can get main model and simple properties by default model-binder and for lists, you can 1) create your own model binder 2) parse them from inputted strings by yourself. Good lock