LazZiya / XLocalizer

Localizer package for Asp.Net Core web applications, powered by online translation and auto resource creating.
https://docs.ziyad.info
129 stars 14 forks source link

FluentValidation placeholders with XLocalizer #23

Closed morgrowe closed 3 years ago

morgrowe commented 3 years ago

Hi Ziya

Hope you had a nice weekend.

I stumbled onto something and couldn't figure out a way around it. I'm using FluentValidation for my validating my ViewModels. It supports the ability to use placeholders. This works as expected using the following code (not using XLocalizer):

    public class SearchViewModelValidation : AbstractValidator<SearchViewModel>
    {
        public SearchViewModelValidation()
        {
            RuleFor(svm => svm.SearchQuery)
                .MaximumLength(4000)
                .WithMessage("Your query must be less than {MaxLength} characters");
        }
    }

In my View, I'd see an error message like so: Your query must be less than 4000 characters should the user trigger it.

However, if I store the string in my database like so:

sdklfjkldsjfdff

Using XLocalizer v1.0.0, I can retrieve the string like this:

    public class SearchViewModelValidation : AbstractValidator<SearchViewModel>
    {
        public SearchViewModelValidation(ILocalizationService loc)
        {
            RuleFor(svm => svm.SearchQuery)
                .MaximumLength(4000)
                .WithMessage(loc.GetLocalizedString("DIRECTORYSEARCH_VALIDATION_MAXLENGTH"));
        }
    }

But unfortunately, the following exception is thrown:

sksssssssssssss

I have appended the whole stack trace to the end of this post. My theory is XLocalizer is expecting a number to be in-between the { and }. Is there a way of accommodating the placeholders FluentValidation supports and the ones XLocalizer provides?

Thanks, as always! Morgan

System.FormatException: Input string was not in a correct format.
   at System.Text.StringBuilder.FormatError()
   at System.Text.StringBuilder.AppendFormatHelper(IFormatProvider provider, String format, ParamsArray args)
   at System.String.FormatHelper(IFormatProvider provider, String format, ParamsArray args)
   at System.String.Format(String format, Object[] args)
   at XLocalizer.DB.DbStringLocalizer`1.GetLocalizedString(String name, Object[] arguments)
   at XLocalizer.DB.DbStringLocalizer`1.get_Item(String name)
   at Microsoft.Extensions.Localization.StringLocalizerExtensions.GetString(IStringLocalizer stringLocalizer, String name)
   at WLP.Infrastructure.Localization.Services.LocalizationService.GetString(String key) in C:\Web Server\Welsh Language Preference\WLP.Infrastructure\Localization\Services\LocalizationService.cs:line 32
   at WLP.Infrastructure.Localization.Services.LocalizationService.GetLocalizedString(String key) in C:\Web Server\Welsh Language Preference\WLP.Infrastructure\Localization\Services\LocalizationService.cs:line 27
   at WLP.Application.Validation.SearchViewModelValidation..ctor(ILocalizationService loc) in C:\Web Server\Welsh Language Preference\WLP.Application\Validation\SearchViewModelValidation.cs:line 11
   at System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor, Boolean wrapExceptions)
   at System.Reflection.RuntimeConstructorInfo.Invoke(BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite singletonCallSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.Resolve(ServiceCallSite callSite, ServiceProviderEngineScope scope)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.DynamicServiceProviderEngine.<>c__DisplayClass1_0.<RealizeService>b__0(ServiceProviderEngineScope scope)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngine.GetService(Type serviceType, ServiceProviderEngineScope serviceProviderEngineScope)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope.GetService(Type serviceType)
   at FluentValidation.AspNetCore.ServiceProviderValidatorFactory.CreateInstance(Type validatorType) in /home/jskinner/code/FluentValidation/src/FluentValidation.AspNetCore/ServiceProviderValidatorFactory.cs:line 32
   at FluentValidation.ValidatorFactoryBase.GetValidator(Type type) in /home/jskinner/code/FluentValidation/src/FluentValidation/ValidatorFactoryBase.cs:line 41
   at FluentValidation.AspNetCore.ValidatorDescriptorCache.GetCachedDescriptor(ClientValidatorProviderContext context, IHttpContextAccessor httpContextAccessor) in /home/jskinner/code/FluentValidation/src/FluentValidation.AspNetCore/ValidatorDescriptorCache.cs:line 51
   at FluentValidation.AspNetCore.FluentValidationClientModelValidatorProvider.CreateValidators(ClientValidatorProviderContext context) in /home/jskinner/code/FluentValidation/src/FluentValidation.AspNetCore/FluentValidationClientModelValidatorProvider.cs:line 73
   at Microsoft.AspNetCore.Mvc.ModelBinding.Validation.CompositeClientModelValidatorProvider.CreateValidators(ClientValidatorProviderContext context)
   at Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ClientValidatorCache.GetValidators(ModelMetadata metadata, IClientModelValidatorProvider validatorProvider)
   at Microsoft.AspNetCore.Mvc.ViewFeatures.DefaultValidationHtmlAttributeProvider.AddValidationAttributes(ViewContext viewContext, ModelExplorer modelExplorer, IDictionary`2 attributes)
   at Microsoft.AspNetCore.Mvc.ViewFeatures.ValidationHtmlAttributeProvider.AddAndTrackValidationAttributes(ViewContext viewContext, ModelExplorer modelExplorer, String expression, IDictionary`2 attributes)
   at Microsoft.AspNetCore.Mvc.ViewFeatures.DefaultHtmlGenerator.AddValidationAttributes(ViewContext viewContext, TagBuilder tagBuilder, ModelExplorer modelExplorer, String expression)
   at Microsoft.AspNetCore.Mvc.ViewFeatures.DefaultHtmlGenerator.GenerateInput(ViewContext viewContext, InputType inputType, ModelExplorer modelExplorer, String expression, Object value, Boolean useViewData, Boolean isChecked, Boolean setId, Boolean isExplicitValue, String format, IDictionary`2 htmlAttributes)
   at Microsoft.AspNetCore.Mvc.ViewFeatures.DefaultHtmlGenerator.GenerateTextBox(ViewContext viewContext, ModelExplorer modelExplorer, String expression, Object value, String format, Object htmlAttributes)
   at Microsoft.AspNetCore.Mvc.TagHelpers.InputTagHelper.GenerateTextBox(ModelExplorer modelExplorer, String inputTypeHint, String inputType, IDictionary`2 htmlAttributes)
   at Microsoft.AspNetCore.Mvc.TagHelpers.InputTagHelper.Process(TagHelperContext context, TagHelperOutput output)
   at Microsoft.AspNetCore.Razor.TagHelpers.TagHelper.ProcessAsync(TagHelperContext context, TagHelperOutput output)
   at Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperRunner.RunAsync(TagHelperExecutionContext executionContext)
   at AspNetCore.Views_Shared_Partials__Search.<ExecuteAsync>b__28_0() in C:\Web Server\Welsh Language Preference\WLP.Directory.WebUI\Views\Shared\Partials\_Search.cshtml:line 6
   at Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperExecutionContext.GetChildContentAsync(Boolean useCachedResult, HtmlEncoder encoder)
   at Microsoft.AspNetCore.Mvc.TagHelpers.RenderAtEndOfFormTagHelper.ProcessAsync(TagHelperContext context, TagHelperOutput output)
   at Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperRunner.<RunAsync>g__Awaited|0_0(Task task, TagHelperExecutionContext executionContext, Int32 i, Int32 count)
   at AspNetCore.Views_Shared_Partials__Search.ExecuteAsync() in C:\Web Server\Welsh Language Preference\WLP.Directory.WebUI\Views\Shared\Partials\_Search.cshtml:line 3
   at Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderPageCoreAsync(IRazorPage page, ViewContext context)
   at Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderPageAsync(IRazorPage page, ViewContext context, Boolean invokeViewStarts)
   at Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderAsync(ViewContext context)
   at Microsoft.AspNetCore.Mvc.ViewFeatures.HtmlHelper.RenderPartialCoreAsync(String partialViewName, Object model, ViewDataDictionary viewData, TextWriter writer)
   at AspNetCore.Views_Home_Index.ExecuteAsync() in C:\Web Server\Welsh Language Preference\WLP.Directory.WebUI\Views\Home\Index.cshtml:line 7
   at Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderPageCoreAsync(IRazorPage page, ViewContext context)
   at Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderPageAsync(IRazorPage page, ViewContext context, Boolean invokeViewStarts)
   at Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderAsync(ViewContext context)
   at Microsoft.AspNetCore.Mvc.ViewFeatures.ViewExecutor.ExecuteAsync(ViewContext viewContext, String contentType, Nullable`1 statusCode)
   at Microsoft.AspNetCore.Mvc.ViewFeatures.ViewExecutor.ExecuteAsync(ViewContext viewContext, String contentType, Nullable`1 statusCode)
   at Microsoft.AspNetCore.Mvc.ViewFeatures.ViewExecutor.ExecuteAsync(ActionContext actionContext, IView view, ViewDataDictionary viewData, ITempDataDictionary tempData, String contentType, Nullable`1 statusCode)
   at Microsoft.AspNetCore.Mvc.ViewFeatures.ViewResultExecutor.ExecuteAsync(ActionContext context, ViewResult result)
   at Microsoft.AspNetCore.Mvc.ViewResult.ExecuteResultAsync(ActionContext context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResultFilterAsync>g__Awaited|29_0[TFilter,TFilterAsync](ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResultExecutedContextSealed context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.ResultNext[TFilter,TFilterAsync](State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeResultFilters()
--- End of stack trace from previous location where exception was thrown ---
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResourceFilter>g__Awaited|24_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeFilterPipelineAsync()
--- End of stack trace from previous location where exception was thrown ---
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
   at Microsoft.AspNetCore.Builder.RouterMiddleware.Invoke(HttpContext httpContext)
   at Microsoft.AspNetCore.Localization.RequestLocalizationMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Diagnostics.StatusCodePagesMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
morgrowe commented 3 years ago

Ahh, I'm a numpty. I just realised I can just store:

Your query must be less than {{MaxLength}} characters

In database and it works (double {{ and }}).

Sorry!

LazZiya commented 3 years ago

Hi @morgrowe,

Thank you for your kind message, weekends are always awesome, hope it is so for you too :)

I was just typing to answer your issue, but it seems you already found a perfect solution, that is great 👍🏼

Have a nice day, Ziya