Asp.Net Identity Localization PublicKeyToken

2019-01-27 13:45发布

I'm trying to get localized error messages for Swedish for Asp.Net Identity by using advice from this post: How to localize ASP.NET Identity UserName and Password error messages?

Using NuGet I downloaded the German language pack and then opened \packages\Microsoft.AspNet.Identity.Core.2.0.0\lib\net45\de\Microsoft.AspNet.Identity.Core.resources.dll in dotPeek and then exported this to a new VS project:

https://github.com/nielsbosma/AspNet.Identity.Resources.Swedish/

I've copied the generated \Microsoft.AspNet.Identity.Core.resources.dll to a new folder under \packages\Microsoft.AspNet.Identity.Core.2.0.0\lib\net45\se.

When I run my site locally I see that Microsoft.AspNet.Identity.Core.resources.dll has been copied to MySite\bin\sv\

But I can't get it to work :(

If I set in my Web.config:

<system.web>
    ...
    <globalization culture="sv-SE" uiCulture="sv" />
</system.web>

I still get english default error messages. But if I change to german that I've included from NuGet I get german error messages.

Using dotPeek I've compared my dll with the german and they are the same except my has PublicKeyToken=null and the one for german is "31bf3856ad364e35". Could this be why I can't get my dll to load? Is there anyway to set a PublicKeyToken for a dll? Any workaround?

Thanks for any pointers.

6条回答
何必那么认真
2楼-- · 2019-01-27 14:27

At the moment this is a really crappy solution, but a workaround solution none the less. To save time for those of us who do need localization and don't work at Microsoft... here's what I did as a workaround for the dutch language.

public class Demo {
    private string LocalizeIdentityError(string error, IdentityUser user)
    {
        if (error == "User already in role.") return "De gebruiker zit reeds in deze rol.";
        else if (error == "User is not in role.") return "De gebruiker zit niet in deze rol.";
        //else if (error == "Role {0} does not exist.") return "De rol bestaat nog niet";
        //else if (error == "Store does not implement IUserClaimStore&lt;TUser&gt;.") return "";
        //else if (error == "No IUserTwoFactorProvider for '{0}' is registered.") return "";
        //else if (error == "Store does not implement IUserEmailStore&lt;TUser&gt;.") return "";
        else if (error == "Incorrect password.") return "Ongeldig wachtwoord";
        //else if (error == "Store does not implement IUserLockoutStore&lt;TUser&gt;.") return "";
        //else if (error == "No IUserTokenProvider is registered.") return "";
        //else if (error == "Store does not implement IUserRoleStore&lt;TUser&gt;.") return "";
        //else if (error == "Store does not implement IUserLoginStore&lt;TUser&gt;.") return "";
        else if (error == "User name {0} is invalid, can only contain letters or digits.") return "De gebruikersnaam '"+user.UserName+"' kan alleen letters of cijfers bevatten.";
        //else if (error == "Store does not implement IUserPhoneNumberStore&lt;TUser&gt;.") return "";
        //else if (error == "Store does not implement IUserConfirmationStore&lt;TUser&gt;.") return "";
        else if (error.StartsWith("Passwords must be at least ")) return "Een wachtwoord moet minstens {0} karakters bevatten.";
        //else if (error == "{0} cannot be null or empty.") return "";
        else if (user != null && error == "Name "+user.UserName+" is already taken.") return "De gebruikersnaam '" + user.UserName + "' is reeds in gebruik.";
        else if (error == "User already has a password set.") return "Deze gebruiker heeft reeds een wachtwoord ingesteld.";
        //else if (error == "Store does not implement IUserPasswordStore&lt;TUser&gt;.") return "";
        else if (error == "Passwords must have at least one non letter or digit character.") return "Wachtwoorden moeten minstens een ander karakter dan een letter of cijfer bevatten.";
        else if (error == "UserId not found.") return "De gebruiker kon niet gevonden worden.";
        else if (error == "Invalid token.") return "Ongeldig token.";
        else if (user != null && error == "Email '" + user.Email + "' is invalid.") return "Het emailadres '" + user.Email + "' is ongeldig.";
        else if (user != null && error == "User " + user.UserName + " does not exist.") return "De gebruiker '" + user.UserName + "' bestaat niet.";
        else if (error == "Store does not implement IQueryableRoleStore&lt;TRole&gt;.") return "";
        else if (error == "Lockout is not enabled for this user.") return "Lockout is niet geactiveerd voor deze gebruiker.";
        //else if (error == "Store does not implement IUserTwoFactorStore&lt;TUser&gt;.") return "";
        else if (error == "Passwords must have at least one uppercase ('A'-'Z').") return "Wachtwoorden moeten minstens één hoofdletter bevatten. (A-Z)";
        else if (error == "Passwords must have at least one digit ('0'-'9').") return "Wachtwoorden moeten minstens één getal bevatten. (0-9)";
        else if (error == "Passwords must have at least one lowercase ('a'-'z').") return "Wachtwoorden moeten minstens één kleine letter bevatten. (a-z)";
        //else if (error == "Store does not implement IQueryableUserStore&lt;TUser&gt;.") return "";
        else if (user != null && error == "Email '" + user.Email + "' is already taken.") return "Het emailadres '" + user.Email + "' is reeds in gebruik. Probeer aan te melden.";
        //else if (error == "Store does not implement IUserSecurityStampStore&lt;TUser&gt;.") return "";
        else if (error == "A user with that external login already exists.") return "Een gebruiker met deze externe login bestaat reeds.";
        else if (error == "An unknown failure has occured.") return "Een onbekende fout is opgetreden. Probeer het later opnieuw.";

        return error;
    }
}
查看更多
该账号已被封号
3楼-- · 2019-01-27 14:27

Yet another workaround for Password field could be to implement RegisterViewModel.Password with a CustomValidationAttribute:

public class RegisterViewModel
{
    [CustomValidation(typeof(CustomValidations), "ValidatePassword")]
    public string Password { get; set; }
}

And to have CustomValidations.ValidatePassword method which would mimic the password validation rules that you've set up for PasswordValidator. I.e.:

public static class CustomValidations
{
    public static ValidationResult ValidatePassword(string password)
    {
        // Implement validation logic here, e.g. require numbers,
        // uppercase etc. and create localized ValidationResult.
        return new ValidationResult(Resources.PasswordValidation.NoNumbers);
    }
}

Here you can obviously localize your error messages per you personal preference using standard resx resource files. So, to sum it up, you would simply prohibit any invalid passwords from ever reaching the PasswordValidator.

For Email field this would get a little bit ugly as it would require a roundtrip to DB to verify uniqueness etc., but it should be doable.

Extra bonus for using this solution is that the errors produced would be "per field", i.e. you would not have to display all the validation errors using @Html.ValidationSummary but could do:

@Html.ValidationMessageFor(m => m.Password, null, new { @class = "text-danger" })
查看更多
劫难
4楼-- · 2019-01-27 14:33

Here is the en-US Resources.resx file key and value list. These are the values that require localization. Source code at http://aspnetidentity.codeplex.com

Asp.Net Identity {name}Validator.cs ErrorMessage Resources.resx Localization See: http://aspnetidentity.codeplex.com/discussions/638351

*DefaultError= An unknown failure has occured.
DuplicateEmail= Email '{0}' is already taken.
DuplicateName= Name {0} is already taken.
ExternalLoginExists= A user with that external login already exists.
InvalidEmail= Email '{0}' is invalid.
InvalidToken= Invalid token.
InvalidUserName= User name {0} is invalid, can only contain letters or digits.
LockoutNotEnabled= Lockout is not enabled for this user.
NoTokenProvider= No IUserTokenProvider is registered.
NoTwoFactorProvider= No IUserTwoFactorProvider for '{0}' is registered.
PasswordMismatch= Incorrect password.
PasswordRequireDigit= Passwords must have at least one digit ('0'-'9').
PasswordRequireLower= Passwords must have at least one lowercase ('a'-'z').
PasswordRequireNonLetterOrDigit= Passwords must have at least one non letter or digit character.
PasswordRequireUpper= Passwords must have at least one uppercase ('A'-'Z').
PasswordTooShort= Passwords must be at least {0} characters.
PropertyTooShort= {0} cannot be null or empty.
RoleNotFound= Role {0} does not exist.
StoreNotIQueryableRoleStore= Store does not implement IQueryableRoleStore<TRole>.
StoreNotIQueryableUserStore= Store does not implement IQueryableUserStore<TUser>.
StoreNotIUserClaimStore= Store does not implement IUserClaimStore<TUser>.
StoreNotIUserConfirmationStore= Store does not implement IUserConfirmationStore<TUser>.
StoreNotIUserEmailStore= Store does not implement IUserEmailStore<TUser>.
StoreNotIUserLockoutStore= Store does not implement IUserLockoutStore<TUser>.
StoreNotIUserLoginStore= Store does not implement IUserLoginStore<TUser>.
StoreNotIUserPasswordStore= Store does not implement IUserPasswordStore<TUser>.
StoreNotIUserPhoneNumberStore= Store does not implement IUserPhoneNumberStore<TUser>.
StoreNotIUserRoleStore= Store does not implement IUserRoleStore<TUser>.
StoreNotIUserSecurityStampStore= Store does not implement IUserSecurityStampStore<TUser>.
StoreNotIUserTwoFactorStore= Store does not implement IUserTwoFactorStore<TUser>.
UserAlreadyHasPassword= User already has a password set.
UserAlreadyInRole= User already in role.
UserIdNotFound= UserId not found.
UserNameNotFound= User {0} does not exist.
UserNotInRole= User is not in role.*
查看更多
forever°为你锁心
5楼-- · 2019-01-27 14:41

Not unless you have the private key that Microsoft uses to sign dlls.

Updated: as a workaround until we add support for plugging in your own resources, you can probably just wrap all the default identity result error messages with an explicit switch for now, there should only be about 10-20 user facing errors.

Something like:

public static string Localize(string error) {
     switch (error) {
          case "<english error>": return "<localized version";
     }
}
查看更多
放我归山
6楼-- · 2019-01-27 14:43

Inspired by Peter's Answer I came up with crappy but quick solution as well.

I needed to localize errors in default AccountController template so I wrote my own AddLocalizedErrors method. I'm using Resources to localize errors.

    //Original method
    private void AddErrors(IdentityResult result)
    {
        foreach (var error in result.Errors)
        {
            ModelState.AddModelError("", error);
        }
    }
    //My method
    private void AddLocalizedErrors(IdentityResult result, ApplicationUser user)
    {
        foreach (var error in result.Errors)
        {
            var localizedError = error;
            string userName = "";
            string email = "";
            if (user != null) 
            {
                userName = user.UserName;
                email = user.Email;
            }
            //password errors
            localizedError = localizedError.Replace("Passwords must have at least one uppercase ('A'-'Z').", AspNetValidationMessages.password_uppercase);
            localizedError = localizedError.Replace("Passwords must have at least one digit ('0'-'9').", AspNetValidationMessages.password_digit);
            localizedError = localizedError.Replace("Passwords must have at least one lowercase ('a'-'z').", AspNetValidationMessages.password_lowercase);
            localizedError = localizedError.Replace("Passwords must have at least one non letter or digit character.", AspNetValidationMessages.password_nonletter_nondigit);
            localizedError = localizedError.Replace("Passwords must have at least one non letter or digit character.", AspNetValidationMessages.password_nonletter_nondigit); 
            localizedError = localizedError.Replace("Passwords must have at least one non letter or digit character.", AspNetValidationMessages.password_nonletter_nondigit); 
            //register errors
            localizedError = localizedError.Replace("Name "+userName+" is already taken.", AspNetValidationMessages.name_taken.Replace("{0}", userName));
            localizedError = localizedError.Replace("Email '" + email + "' is already taken.", AspNetValidationMessages.email_taken.Replace("{0}", email)); 

            ModelState.AddModelError("", localizedError);
        }
    }

I am using string.Replace() because for example the password errors are just joined strings of a single password requirements.

When it comes to collection of roles or so I should be more creative. Probably using string.Contains().

查看更多
我想做一个坏孩纸
7楼-- · 2019-01-27 14:45

Another option is to inherit from Microsoft.AspNet.Identity.PasswordValidator then override ValidateAsync.

You can then use your own resource files for localizaion. The original resource file for English can be found using DotPeek or similar, then you can use that for the English as a template for your own translation in other languages.

My resx files:

MyLocalization/IdentityResource.resx (same as in Microsoft.AspNet.Identity.Core.Resources)

MyLocalization/IdentityResource.nb-no.resx (my Norwegian translations)

 var manager = new ApplicationUserManager(new UserStore<ApplicationUser>    (context.Get<ApplicationDbContext>()));

 // Configure validation logic for passwords
 manager.PasswordValidator = new MyCustomPasswordValidator(System.Threading.Thread.CurrentThread.CurrentUICulture)

In MyCustomPasswordValidator.cs: (please also notice the bugfix I also had to do in ValidateAsync)

using Resources = MyLocalization.IdentityResource;

public class MyCustomPasswordValidator : Microsoft.AspNet.Identity.PasswordValidator
{
    private readonly CultureInfo _currentUIculture;

    public MyCustomPasswordValidator(CultureInfo currentUIculture)
    {
        _currentUIculture = currentUIculture;
    }

    /// <summary>
    /// Ensures that the string is of the required length and meets the configured requirements
    /// 
    /// </summary>
    /// <param name="item"/>
    /// <returns/>
    public override Task<IdentityResult> ValidateAsync(string item)
    {
        //BUG: CurrentUICulture is not set correctly https://aspnetidentity.codeplex.com/workitem/2060
        System.Threading.Thread.CurrentThread.CurrentUICulture = _currentUIculture;
        if (item == null)
            throw new ArgumentNullException("item");
        List<string> list = new List<string>();
        if (string.IsNullOrWhiteSpace(item) || item.Length < this.RequiredLength)
            list.Add(string.Format((IFormatProvider)CultureInfo.CurrentCulture, Resources.PasswordTooShort, new object[1]
    {
      (object) this.RequiredLength
    }));
        if (this.RequireNonLetterOrDigit && Enumerable.All<char>((IEnumerable<char>)item, new Func<char, bool>(this.IsLetterOrDigit)))
            list.Add(Resources.PasswordRequireNonLetterOrDigit);
        if (this.RequireDigit && Enumerable.All<char>((IEnumerable<char>)item, (Func<char, bool>)(c => !this.IsDigit(c))))
            list.Add(Resources.PasswordRequireDigit);
        if (this.RequireLowercase && Enumerable.All<char>((IEnumerable<char>)item, (Func<char, bool>)(c => !this.IsLower(c))))
            list.Add(Resources.PasswordRequireLower);
        if (this.RequireUppercase && Enumerable.All<char>((IEnumerable<char>)item, (Func<char, bool>)(c => !this.IsUpper(c))))
            list.Add(Resources.PasswordRequireUpper);
        if (list.Count == 0)
            return Task.FromResult<IdentityResult>(IdentityResult.Success);
        return Task.FromResult<IdentityResult>(IdentityResult.Failed(new string[1]
  {
    string.Join(" ", (IEnumerable<string>) list)
  }));
    }

}

`

查看更多
登录 后发表回答