Create IdentityResult localization system. Introduce ILocalizeErrorMessage interface.

pull/341/head
Halil ibrahim Kalkan 7 years ago
parent c1fd5cce2a
commit 7b4c0bc23e

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using Localization.Resources.AbpUi;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Options;
using Volo.Abp.Authorization;
@ -10,6 +11,7 @@ using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Entities;
using Volo.Abp.ExceptionHandling;
using Volo.Abp.Http;
using Volo.Abp.Localization;
using Volo.Abp.Localization.ExceptionHandling;
using Volo.Abp.UI;
using Volo.Abp.Validation;
@ -23,12 +25,15 @@ namespace Volo.Abp.AspNetCore.Mvc.ExceptionHandling
protected ExceptionLocalizationOptions LocalizationOptions { get; }
protected IStringLocalizerFactory StringLocalizerFactory { get; }
protected IStringLocalizer<AbpUiResource> L { get; }
protected IServiceProvider ServiceProvider { get; }
public DefaultExceptionToErrorInfoConverter(
IOptions<ExceptionLocalizationOptions> localizationOptions,
IStringLocalizerFactory stringLocalizerFactory,
IStringLocalizer<AbpUiResource> abpUiStringLocalizer)
IStringLocalizer<AbpUiResource> abpUiStringLocalizer,
IServiceProvider serviceProvider)
{
ServiceProvider = serviceProvider;
StringLocalizerFactory = stringLocalizerFactory;
L = abpUiStringLocalizer;
LocalizationOptions = localizationOptions.Value;
@ -101,6 +106,16 @@ namespace Volo.Abp.AspNetCore.Mvc.ExceptionHandling
protected virtual void TryToLocalizeExceptionMessage(Exception exception, RemoteServiceErrorInfo errorInfo)
{
if (exception is ILocalizeErrorMessage localizeErrorMessageException)
{
using (var scope = ServiceProvider.CreateScope())
{
errorInfo.Message = localizeErrorMessageException.LocalizeMessage(new LocalizationContext(scope.ServiceProvider));
}
return;
}
if (!(exception is IHasErrorCode exceptionWithErrorCode))
{
return;
@ -166,7 +181,7 @@ namespace Volo.Abp.AspNetCore.Mvc.ExceptionHandling
aggException.InnerException is AbpValidationException ||
aggException.InnerException is EntityNotFoundException ||
aggException.InnerException is AbpAuthorizationException ||
aggException.InnerException is BusinessException)
aggException.InnerException is IBusinessException)
{
return aggException.InnerException;
}

@ -0,0 +1,10 @@
namespace Microsoft.Extensions.Localization
{
public static class AbpStringLocalizerFactoryExtensions
{
public static IStringLocalizer Create<TResource>(this IStringLocalizerFactory localizerFactory)
{
return localizerFactory.Create(typeof(TResource));
}
}
}

@ -1,9 +1,10 @@
using System;
using JetBrains.Annotations;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp
{
public class ApplicationInitializationContext
public class ApplicationInitializationContext : IServiceProviderAccessor
{
public IServiceProvider ServiceProvider { get; set; }

@ -0,0 +1,9 @@
using Volo.Abp.Localization;
namespace Volo.Abp.ExceptionHandling
{
public interface ILocalizeErrorMessage
{
string LocalizeMessage(LocalizationContext context);
}
}

@ -0,0 +1,20 @@
using System;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Localization;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.Localization
{
public class LocalizationContext : IServiceProviderAccessor
{
public IServiceProvider ServiceProvider { get; }
public IStringLocalizerFactory LocalizerFactory { get; }
public LocalizationContext(IServiceProvider serviceProvider)
{
ServiceProvider = serviceProvider;
LocalizerFactory = ServiceProvider.GetRequiredService<IStringLocalizerFactory>();
}
}
}

@ -30,7 +30,9 @@ namespace Volo.Abp.Identity
services.Configure<AbpLocalizationOptions>(options =>
{
options.Resources.Get<IdentityResource>().AddVirtualJson("/Volo/Abp/Identity/Localization/ApplicationContracts");
options.Resources
.Get<IdentityResource>()
.AddVirtualJson("/Volo/Abp/Identity/Localization/ApplicationContracts");
});
services.AddAssemblyOf<AbpIdentityApplicationContractsModule>();

@ -1,21 +1,9 @@
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Identity;
using Volo.Abp.Application.Services;
using Volo.Abp.Application.Services;
namespace Volo.Abp.Identity
{
public abstract class IdentityAppServiceBase : ApplicationService
{
protected void CheckIdentityErrors(IdentityResult identityResult)
{
if (!identityResult.Succeeded)
{
//TODO: A better exception that can be shown on UI as localized?
throw new AbpException("Operation failed: " + identityResult.Errors.Select(e => $"[{e.Code}] {e.Description}").JoinAsString(", "));
}
//identityResult.CheckErrors(LocalizationManager); //TODO: Get from old Abp
}
}
}

@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;
using Volo.Abp.Authorization.Permissions;
@ -70,7 +71,7 @@ namespace Volo.Abp.Identity
{
var role = new IdentityRole(GuidGenerator.Create(), input.Name, CurrentTenant.Id);
await _roleManager.CreateAsync(role);
(await _roleManager.CreateAsync(role)).CheckErrors();
await CurrentUnitOfWork.SaveChangesAsync();
return ObjectMapper.Map<IdentityRole, IdentityRoleDto>(role);
@ -81,9 +82,9 @@ namespace Volo.Abp.Identity
{
var role = await _roleManager.GetByIdAsync(id);
await _roleManager.SetRoleNameAsync(role, input.Name);
(await _roleManager.SetRoleNameAsync(role, input.Name)).CheckErrors();
await _roleManager.UpdateAsync(role);
(await _roleManager.UpdateAsync(role)).CheckErrors();
await CurrentUnitOfWork.SaveChangesAsync();
return ObjectMapper.Map<IdentityRole, IdentityRoleDto>(role);
@ -98,7 +99,7 @@ namespace Volo.Abp.Identity
return;
}
await _roleManager.DeleteAsync(role);
(await _roleManager.DeleteAsync(role)).CheckErrors();
}
}
}

@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Authorization.Permissions;
using Volo.Abp.PermissionManagement;
@ -56,7 +57,7 @@ namespace Volo.Abp.Identity
{
var user = new IdentityUser(GuidGenerator.Create(), input.UserName, input.Email, CurrentTenant.Id);
CheckIdentityErrors(await _userManager.CreateAsync(user, input.Password));
(await _userManager.CreateAsync(user, input.Password)).CheckErrors();
await UpdateUserByInput(user, input);
await CurrentUnitOfWork.SaveChangesAsync();
@ -69,9 +70,9 @@ namespace Volo.Abp.Identity
{
var user = await _userManager.GetByIdAsync(id);
CheckIdentityErrors(await _userManager.SetUserNameAsync(user, input.UserName));
(await _userManager.SetUserNameAsync(user, input.UserName)).CheckErrors();
await UpdateUserByInput(user, input);
CheckIdentityErrors(await _userManager.UpdateAsync(user));
(await _userManager.UpdateAsync(user)).CheckErrors();
await CurrentUnitOfWork.SaveChangesAsync();
return ObjectMapper.Map<IdentityUser, IdentityUserDto>(user);
@ -86,14 +87,14 @@ namespace Volo.Abp.Identity
return;
}
CheckIdentityErrors(await _userManager.DeleteAsync(user));
(await _userManager.DeleteAsync(user)).CheckErrors();
}
[Authorize(IdentityPermissions.Users.Update)]
public async Task UpdateRolesAsync(Guid id, IdentityUserUpdateRolesDto input)
{
var user = await _userManager.GetByIdAsync(id);
CheckIdentityErrors(await _userManager.SetRolesAsync(user, input.RoleNames));
(await _userManager.SetRolesAsync(user, input.RoleNames)).CheckErrors();
await _userRepository.UpdateAsync(user);
}
@ -114,14 +115,14 @@ namespace Volo.Abp.Identity
private async Task UpdateUserByInput(IdentityUser user, IdentityUserCreateOrUpdateDtoBase input)
{
CheckIdentityErrors(await _userManager.SetEmailAsync(user, input.Email));
CheckIdentityErrors(await _userManager.SetPhoneNumberAsync(user, input.PhoneNumber));
CheckIdentityErrors(await _userManager.SetTwoFactorEnabledAsync(user, input.TwoFactorEnabled));
CheckIdentityErrors(await _userManager.SetLockoutEnabledAsync(user, input.LockoutEnabled));
(await _userManager.SetEmailAsync(user, input.Email)).CheckErrors();
(await _userManager.SetPhoneNumberAsync(user, input.PhoneNumber)).CheckErrors();
(await _userManager.SetTwoFactorEnabledAsync(user, input.TwoFactorEnabled)).CheckErrors();
(await _userManager.SetLockoutEnabledAsync(user, input.LockoutEnabled)).CheckErrors();
if (input.RoleNames != null)
{
CheckIdentityErrors(await _userManager.SetRolesAsync(user, input.RoleNames));
(await _userManager.SetRolesAsync(user, input.RoleNames)).CheckErrors();
}
}
}

@ -0,0 +1,64 @@
using System;
using System.Linq;
using System.Collections.Generic;
using System.Globalization;
using Microsoft.Extensions.Localization;
using Volo.Abp.Identity;
using Volo.Abp.Text.Formatting;
namespace Microsoft.AspNetCore.Identity
{
public static class AbpIdentityResultExtensions
{
public static void CheckErrors(this IdentityResult identityResult)
{
if (identityResult.Succeeded)
{
return;
}
if (identityResult.Errors == null)
{
throw new ArgumentException("identityResult.Errors should not be null.");
}
throw new AbpIdentityResultException(identityResult);
}
public static string LocalizeErrors(this IdentityResult identityResult, IStringLocalizer localizer)
{
if (identityResult.Succeeded)
{
throw new ArgumentException("identityResult.Succeeded should be false in order to localize errors.");
}
if (identityResult.Errors == null)
{
throw new ArgumentException("identityResult.Errors should not be null.");
}
return identityResult.Errors.Select(err => LocalizeErrorMessage(err, localizer)).JoinAsString(", ");
}
public static string LocalizeErrorMessage(this IdentityError error, IStringLocalizer localizer)
{
var key = $"Identity.{error.Code}";
var localizedString = localizer[key];
if (!localizedString.ResourceNotFound)
{
var englishLocalizedString = localizer.WithCulture(CultureInfo.GetCultureInfo("en"))[key];
if (!englishLocalizedString.ResourceNotFound)
{
if (FormattedStringValueExtracter.IsMatch(error.Description, englishLocalizedString.Value, out var values))
{
return string.Format(localizedString.Value, values.Cast<object>().ToArray());
}
}
}
return localizer["Identity.Default"];
}
}
}

@ -13,6 +13,10 @@
<RootNamespace />
</PropertyGroup>
<ItemGroup>
<EmbeddedResource Include="Volo\Abp\Identity\Localization\Domain\*.json" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Volo.Abp.Identity.Domain.Shared\Volo.Abp.Identity.Domain.Shared.csproj" />

@ -3,10 +3,13 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;
using Volo.Abp.Domain;
using Volo.Abp.Identity.Localization;
using Volo.Abp.Localization;
using Volo.Abp.Modularity;
using Volo.Abp.PermissionManagement;
using Volo.Abp.Settings;
using Volo.Abp.Users;
using Volo.Abp.VirtualFileSystem;
namespace Volo.Abp.Identity
{
@ -29,6 +32,18 @@ namespace Volo.Abp.Identity
options.DefinitionProviders.Add<AbpIdentitySettingDefinitionProvider>();
});
services.Configure<VirtualFileSystemOptions>(options =>
{
options.FileSets.AddEmbedded<AbpIdentityDomainModule>();
});
services.Configure<AbpLocalizationOptions>(options =>
{
options.Resources
.Get<IdentityResource>()
.AddVirtualJson("/Volo/Abp/Identity/Localization/Domain");
});
var identityBuilder = services.AddAbpIdentity(options =>
{
options.User.RequireUniqueEmail = true;

@ -0,0 +1,38 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using JetBrains.Annotations;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Localization;
using Volo.Abp.ExceptionHandling;
using Volo.Abp.Identity.Localization;
using Volo.Abp.Localization;
namespace Volo.Abp.Identity
{
[Serializable]
public class AbpIdentityResultException : BusinessException, ILocalizeErrorMessage
{
public IdentityResult IdentityResult { get; }
public AbpIdentityResultException([NotNull] IdentityResult identityResult)
: base(
code: $"Identity.{identityResult.Errors.First().Code}",
message: identityResult.Errors.Select(err => err.Description).JoinAsString(", "))
{
IdentityResult = Check.NotNull(identityResult, nameof(identityResult));
}
public AbpIdentityResultException(SerializationInfo serializationInfo, StreamingContext context)
: base(serializationInfo, context)
{
}
public string LocalizeMessage(LocalizationContext context)
{
return IdentityResult.LocalizeErrors(context.LocalizerFactory.Create<IdentityResource>());
}
}
}

@ -0,0 +1,7 @@
{
"culture": "en",
"texts": {
"Identity.PasswordTooShort": "Passwords must be at least {0} characters.",
"Identity.PasswordRequiresNonAlphanumeric": "Passwords must have at least one non alphanumeric character."
}
}

@ -0,0 +1,7 @@
{
"culture": "tr",
"texts": {
"Identity.PasswordTooShort": "Şifre en az {0} karakter uzunluğunda olmalı.",
"Identity.PasswordRequiresNonAlphanumeric": "Şifre en az bir sayı ya da harf olmayan karakter içermeli."
}
}

@ -0,0 +1,37 @@
using Microsoft.AspNetCore.Identity;
using Shouldly;
using Volo.Abp.Localization;
using Xunit;
namespace Volo.Abp.Identity
{
public class AbpIdentityResultException_Tests : AbpIdentityDomainTestBase
{
[Fact]
public void Should_Localize_Messages()
{
var exception = new AbpIdentityResultException(
IdentityResult.Failed(
new IdentityError
{
Code = "PasswordTooShort",
Description = "Passwords must be at least 6 characters."
},
new IdentityError
{
Code = "PasswordRequiresNonAlphanumeric",
Description = "Passwords must have at least one non alphanumeric character."
}
)
);
using (AbpCultureHelper.Use("tr"))
{
var localizeMessage = exception.LocalizeMessage(new LocalizationContext(ServiceProvider));
localizeMessage.ShouldContain("Şifre en az 6 karakter uzunluğunda olmalı.");
localizeMessage.ShouldContain("Şifre en az bir sayı ya da harf olmayan karakter içermeli.");
}
}
}
}
Loading…
Cancel
Save