Localizing data annotations in .razor pages [duplicate] - asp.net-core

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

How do you centralise ASP.NET Core Web API attribute validation on multiple DTOs with similar properties?

Is there a way to centralise the model validation of the same property name across multiple DTOs?
For example, if I have the following classes to be used as the request body in a Web API action.
public class RegisterRequest
{
[Required]
[EmailAddress]
public string EmailAddress { get; set; } = null!;
[Required]
[MinLength(8)]
[RegularExpression(UserSettings.PasswordRegex)]
public string Password { get; set; } = null!;
[Required]
[MaxLength(100)]
public string DisplayName { get; set; } = null!;
}
public class UserProfileRequest
{
[Required]
public int UserId { get; set; }
[Required]
[MaxLength(100)]
public string DisplayName { get; set; } = null!;
[Range(3, 3)]
public string? CCN3 { get; set; }
}
Can I centralise the attribute validation on DisplayName, duplicating the attributes goes against single responsibility principle. I believe I could achieve the centralised validation using an IFilterFactory and dropping the usage of attributes.
I opted to use a custom ActionFilterAttribute to achieve centralisation of the validation. The example below is for validating the country code (CCN3).
CountryCodeValidationAttribute.cs - custom attribute to be applied to properties (contains no logic)
[AttributeUsage(AttributeTargets.Property)]
public class CountryCodeValidationAttribute : Attribute
{
}
CountryCodeValidationActionFilter.cs - custom action filter that supports dependency injection and looks for the custom attribute on the properties. In my case I'm returning the standard invalid model bad request response.
public class CountryCodeValidationActionFilter : ActionFilterAttribute
{
private readonly ICountryService countryService;
private readonly IOptions<ApiBehaviorOptions> apiBehaviorOptions;
public CountryCodeValidationActionFilter(
ICountryService countryService,
IOptions<ApiBehaviorOptions> apiBehaviorOptions)
{
this.countryService = countryService;
this.apiBehaviorOptions = apiBehaviorOptions;
}
public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
var actionArguments = context.ActionArguments;
foreach (var actionArgument in actionArguments)
{
if (actionArgument.Value == null) continue;
var propertiesWithAttributes = actionArgument.Value
.GetType()
.GetProperties()
.Where(x => x.GetCustomAttributes(true).Any(y => y.GetType() == typeof(CountryCodeValidationAttribute)))
.ToList();
foreach (var property in propertiesWithAttributes)
{
var value = property.GetValue(actionArgument.Value)?.ToString();
if (value != null && await countryService.GetCountryAsync(value) != null) await next();
else
{
context.ModelState.AddModelError(property.Name, "Must be a valid country code");
context.Result = apiBehaviorOptions.Value.InvalidModelStateResponseFactory(context);
}
}
}
await base.OnActionExecutionAsync(context, next);
}
}
Program.cs - register the custom action filter.
builder.Services.AddMvc(options =>
{
options.Filters.Add(typeof(CountryCodeValidationActionFilter));
});
UserProfile.cs - apply the [CountryCodeValidation] attribute to the CountryCode property.
public class UserProfile
{
[Required]
[MaxLength(100)]
public string DisplayName { get; set; } = null!;
[CountryCodeValidation]
public string? CountryCode { get; set; }
}
I can take this same approach and apply it to the DisplayName property to create a centralised validation for it 👍.

Postman. Issue with multipart/form-data (415 Unsupported Media Type)

I got some issue during test my endpoint (with multipart/form-data) on Postman.
I have following method (some fields + photo). This method works with Swagger ok.
[HttpPost]
public async Task<bool> MailPhoto([FromForm] MailwithPhoto mailWithPhoto)
{...}
public class MailwithPhoto
{
public string mail_message { get; set; }
public IFormFile photo_file { get; set; }
public string userContact { get; set; }
public string category { get; set; }
public string userName { get; set; }
public string method { get; set; }
}
enter image description here
First need to change Key message to mail_message,or mail_message will not be binded.
415 is often due to Content-Type or Content-Encoding, or as a result of inspecting the data directly.
Here is a working demo(I use a .net core mvc project):
result:

asp.net core custom model binder just for one property

I have a simple model for my asp.net core controller:
[HttpPost]
public async Task<DefaultResponse> AddCourse([FromBody]CourseDto dto)
{
var response = await _courseService.AddCourse(dto);
return response;
}
My model is :
public class CourseDto
{
public int Id { get; set; }
public string Name { get; set; }
public string Genre { get; set; }
public string Duration { get; set; }
public string Level { get; set; }
public string AgeRange { get; set; }
public string Notes { get; set; }
public bool Active { get; set; }
public string OrganisationCode { get; set; }
}
I'm trying to set value of "OrganisationCode" using a custom mode binder or action filter, but had no success.
I would be thnakful if you advise whats the right way to updat ethe model before executing the action.
Thanks.
I will show you here a very simple custom model binder I have just written (and tested in .Net Core 2.0):
My model binder:
public class CustomModelBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
var value = valueProviderResult.FirstValue; // get the value as string
var model = value.Split(",");
bindingContext.Result = ModelBindingResult.Success(model);
return Task.CompletedTask;
}
}
My model (and notice, only one property has my custom model binder annotation):
public class CreatePostViewModel
{
[Display(Name = nameof(ContentText))]
[MinLength(10, ErrorMessage = ValidationErrors.MinLength)]
public string ContentText { get; set; }
[BindProperty(BinderType = typeof(CustomModelBinder))]
public IEnumerable<string> Categories { get; set; } // <<<<<< THIS IS WHAT YOU ARE INTERESTER IN
#region View Data
public string PageTitle { get; set; }
public string TitlePlaceHolder { get; set; }
#endregion
}
What it does is: it receives some text like "aaa,bbb,ccc", and converts it into array, and return it to the ViewModel.
I hope that helps.
DISCLAIMER: I am not an expert in model binders writing, I have learnt that 15 minutes ago, and I found your question (with no helpful answer), so I tried to help. This is a very basic model binder, some improvements are surely required. I learned how to write it from the official documentation page.
The [FromBody] attribute you are using on the action parameter. means that you direct the default behavior of Model Binding to use the formatters instead. That is why your custom Model Binder does not work.
And [FromBody] is reading the content (request body). So you won't get the request body from your Action Filter, as the request body is a non-rewindable stream, so it suppose to be read only once (I'm assuming that you are trying to read the request body from Action Filter).
My suggestion is to use your custom model binder and remove the FromBody Attribute.

MVC4 model custom function

Sorry for newbie questions, i'm brand new to MVC and OOP
I have the following model for my USER db table
namespace MyApp.Models
{
public class User
{
public int user_id { get; set; }
public string username { get; set; }
public string password { get; set; }
public string salt { get; set; }
public string email { get; set; }
public sbyte status { get; set; }
public System.DateTime creation_date { get; set; }
public sbyte type { get; set; }
public virtual Doctor Doctor { get; set; }
public virtual Owner Owner { get; set; }
public virtual UserToken UserToken { get; set; }
public virtual Veterinarian Veterinarian { get; set; }
}
}
Actually in order to recall a particular USER based on the mail or the id i use a specific class called CustomDbFunctions
namespace MyApp.Models.DAL
{
public static class CustomDbFunctions
{
public static User GetUserEntityFromEmail(string email, DbContext db)
{
return db.Users.FirstOrDefault(u => u.email == (string)email);
}
}
}
in that way i use in my code
User user = CustomDbFunctions.GetUserEntityFromEmail(email, db)
and this it 100% OK with me, but i don't know if this kind of approach is correct or not, or if there's a better way like
//select the single user by calling only the class USER
User mySelectedUser = new User(email)
Thank you very much.
Well for understanding how to access your data in your MVC4 application you could read this tutorial from the Asp.Net MVC main page. Read the whole tutorial about MVC4 and you'll get a solid idea on how to work with it.
But I also recommend this tutorial on a good Entityframework design pattern, it's called Repository Pattern, I just a nice way to get all your code ordered (like all other patterns). Let me know.

ServiceStack - calling 3rd party web service with class

I am trying to call a 3rd party web service
Their REST API uses the following URL style.
http://www.VoiceBase.com/services?version=1.0&apikey=your-apikey&password=secret&action=list&status=processing
All of their service calls go to the same /services
How do I create a class so the following would work?
var client = new JsonServiceClient("http://www.voicebase.com");
var response = client.Get<ResponseVoiceBaseListClass>(new VoiceBaseListClass());
Additional classes I have created but I am not quite there yet
public class VoiceBaseBaseClass
{
public string version { get; set; }
public string apikey { get; set; }
public string password { get; set; }
public VoiceBaseBaseClass()
{
this.version = "1.0";
this.apikey = "API";
this.password = "password";
}
}
public class VoiceBaseListClass : VoiceBaseBaseClass, IReturn<ResponseVoiceBaseListClass>
{
public string action { get; set; }
public string status { get; set; }
public VoiceBaseListClass()
: base()
{
this.action = "list";
this.status = "processing";
}
}
public class ResponseVoiceBaseListClass
{
public string requestStatus { get; set; }
public string statusMessage { get; set; }
public string fileStatus { get; set; }
public List<string> mediaIds { get; set; }
public ResponseVoiceBaseListClass()
{
this.mediaIds = new List<string>();
}
}
Using the above classes the call that goes to the server is
/json/syncreply/VoiceBaseListClass?action=list&status=processing&version=1.0&apikey=API&Password=password
Is there a way I can force the service stack client to go to the
/Services
instead of
/json/syncreply/VoiceBaseListClass
I found a way to do this and it is working great for me.
[RestService("/services", "GET")]
public class VoiceBaseListClass : VoiceBaseBaseClass, IReturn<ResponseVoiceBaseListClass>
{
}
Although this is a deprecated attribute - the new attribute is called Route
https://github.com/ServiceStack/ServiceStack/wiki/Release-Notes
Chris