From 42215e7d28d6144e5a19f77ddc060c42b7afdad5 Mon Sep 17 00:00:00 2001 From: crupest Date: Thu, 23 Jan 2020 20:51:02 +0800 Subject: ... --- Timeline.Tests/Controllers/TokenControllerTest.cs | 35 +++++++++--- Timeline.Tests/Controllers/UserControllerTest.cs | 21 +++++-- Timeline.Tests/Helpers/MockUser.cs | 10 ++-- Timeline/Auth/MyAuthenticationHandler.cs | 10 ++-- Timeline/Controllers/TokenController.cs | 60 ++++++++++---------- Timeline/Controllers/UserController.cs | 11 ++-- Timeline/Entities/UserAvatarEntity.cs | 4 ++ Timeline/Entities/UserDetailEntity.cs | 4 ++ Timeline/GlobalSuppressions.cs | 6 +- Timeline/Services/JwtBadVersionException.cs | 36 ------------ Timeline/Services/UserService.cs | 67 +++++++++++------------ Timeline/Services/UsernameBadFormatException.cs | 11 ++-- Timeline/Startup.cs | 3 +- 13 files changed, 142 insertions(+), 136 deletions(-) delete mode 100644 Timeline/Services/JwtBadVersionException.cs diff --git a/Timeline.Tests/Controllers/TokenControllerTest.cs b/Timeline.Tests/Controllers/TokenControllerTest.cs index 740d8377..61fbe950 100644 --- a/Timeline.Tests/Controllers/TokenControllerTest.cs +++ b/Timeline.Tests/Controllers/TokenControllerTest.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; using Timeline.Controllers; +using Timeline.Models; using Timeline.Models.Http; using Timeline.Services; using Timeline.Tests.Helpers; @@ -15,7 +16,7 @@ namespace Timeline.Tests.Controllers { public class TokenControllerTest : IDisposable { - private readonly Mock _mockUserService = new Mock(); + private readonly Mock _mockUserService = new Mock(); private readonly TestClock _mockClock = new TestClock(); @@ -38,12 +39,18 @@ namespace Timeline.Tests.Controllers { var mockCurrentTime = DateTime.Now; _mockClock.MockCurrentTime = mockCurrentTime; - var createResult = new CreateTokenResult + var mockCreateResult = new UserTokenCreateResult { Token = "mocktokenaaaaa", - User = MockUser.User.Info + User = new UserInfo + { + Id = 1, + Username = MockUser.User.Username, + Administrator = MockUser.User.Administrator, + Version = 1 + } }; - _mockUserService.Setup(s => s.CreateToken("u", "p", expire == null ? null : (DateTime?)mockCurrentTime.AddDays(expire.Value))).ReturnsAsync(createResult); + _mockUserService.Setup(s => s.CreateToken("u", "p", expire == null ? null : (DateTime?)mockCurrentTime.AddDays(expire.Value))).ReturnsAsync(mockCreateResult); var action = await _controller.Create(new CreateTokenRequest { Username = "u", @@ -51,7 +58,11 @@ namespace Timeline.Tests.Controllers Expire = expire }); action.Result.Should().BeAssignableTo() - .Which.Value.Should().BeEquivalentTo(createResult); + .Which.Value.Should().BeEquivalentTo(new CreateTokenResponse + { + Token = mockCreateResult.Token, + User = MockUser.User.Info + }); } [Fact] @@ -88,7 +99,13 @@ namespace Timeline.Tests.Controllers public async Task Verify_Ok() { const string token = "aaaaaaaaaaaaaa"; - _mockUserService.Setup(s => s.VerifyToken(token)).ReturnsAsync(MockUser.User.Info); + _mockUserService.Setup(s => s.VerifyToken(token)).ReturnsAsync(new UserInfo + { + Id = 1, + Username = MockUser.User.Username, + Administrator = MockUser.User.Administrator, + Version = 1 + }); var action = await _controller.Verify(new VerifyTokenRequest { Token = token }); action.Result.Should().BeAssignableTo() .Which.Value.Should().BeAssignableTo() @@ -97,9 +114,9 @@ namespace Timeline.Tests.Controllers public static IEnumerable Verify_BadRequest_Data() { - yield return new object[] { new JwtUserTokenBadFormatException(JwtUserTokenBadFormatException.ErrorCodes.Expired), ErrorCodes.TokenController.Verify_TimeExpired }; - yield return new object[] { new JwtUserTokenBadFormatException(JwtUserTokenBadFormatException.ErrorCodes.IdClaimBadFormat), ErrorCodes.TokenController.Verify_BadFormat }; - yield return new object[] { new JwtUserTokenBadFormatException(JwtUserTokenBadFormatException.ErrorCodes.OldVersion), ErrorCodes.TokenController.Verify_OldVersion }; + yield return new object[] { new UserTokenTimeExpireException(), ErrorCodes.TokenController.Verify_TimeExpired }; + yield return new object[] { new UserTokenBadVersionException(), ErrorCodes.TokenController.Verify_OldVersion }; + yield return new object[] { new UserTokenBadFormatException(), ErrorCodes.TokenController.Verify_BadFormat }; yield return new object[] { new UserNotExistException(), ErrorCodes.TokenController.Verify_UserNotExist }; } diff --git a/Timeline.Tests/Controllers/UserControllerTest.cs b/Timeline.Tests/Controllers/UserControllerTest.cs index 262dbe11..a1035675 100644 --- a/Timeline.Tests/Controllers/UserControllerTest.cs +++ b/Timeline.Tests/Controllers/UserControllerTest.cs @@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging.Abstractions; using Moq; using System; +using System.Collections.Generic; using System.Linq; using System.Security.Claims; using System.Threading.Tasks; @@ -35,18 +36,28 @@ namespace Timeline.Tests.Controllers [Fact] public async Task GetList_Success() { - var array = MockUser.UserInfoList.ToArray(); - _mockUserService.Setup(s => s.ListUsers()).ReturnsAsync(array); + var mockUserList = new UserInfo[] { + new UserInfo { Id = 1, Username = "aaa", Administrator = true, Version = 1 }, + new UserInfo { Id = 2, Username = "bbb", Administrator = false, Version = 1 } + }; + _mockUserService.Setup(s => s.ListUsers()).ReturnsAsync(mockUserList); var action = await _controller.List(); action.Result.Should().BeAssignableTo() - .Which.Value.Should().BeEquivalentTo(array); + .Which.Value.Should().BeEquivalentTo( + mockUserList.Select(u => new User { Username = u.Username, Administrator = u.Administrator }).ToArray()); } [Fact] public async Task Get_Success() { const string username = "aaa"; - _mockUserService.Setup(s => s.GetUserByUsername(username)).ReturnsAsync(MockUser.User.Info); + _mockUserService.Setup(s => s.GetUserByUsername(username)).ReturnsAsync(new UserInfo + { + Id = 1, + Username = MockUser.User.Username, + Administrator = MockUser.User.Administrator, + Version = 1 + }); var action = await _controller.Get(username); action.Result.Should().BeAssignableTo() .Which.Value.Should().BeEquivalentTo(MockUser.User.Info); @@ -56,7 +67,7 @@ namespace Timeline.Tests.Controllers public async Task Get_NotFound() { const string username = "aaa"; - _mockUserService.Setup(s => s.GetUserByUsername(username)).Returns(Task.FromResult(null)); + _mockUserService.Setup(s => s.GetUserByUsername(username)).ThrowsAsync(new UserNotExistException()); var action = await _controller.Get(username); action.Result.Should().BeAssignableTo() .Which.Value.Should().BeAssignableTo() diff --git a/Timeline.Tests/Helpers/MockUser.cs b/Timeline.Tests/Helpers/MockUser.cs index 8d738525..49576842 100644 --- a/Timeline.Tests/Helpers/MockUser.cs +++ b/Timeline.Tests/Helpers/MockUser.cs @@ -1,17 +1,17 @@ using System.Collections.Generic; -using Timeline.Models; - +using Timeline.Models.Http; + namespace Timeline.Tests.Helpers { public class MockUser { public MockUser(string username, string password, bool administrator) { - Info = new UserInfo(username, administrator); + Info = new User { Username = username, Administrator = administrator }; Password = password; } - public UserInfo Info { get; set; } + public User Info { get; set; } public string Username => Info.Username; public string Password { get; set; } public bool Administrator => Info.Administrator; @@ -19,6 +19,6 @@ namespace Timeline.Tests.Helpers public static MockUser User { get; } = new MockUser("user", "userpassword", false); public static MockUser Admin { get; } = new MockUser("admin", "adminpassword", true); - public static IReadOnlyList UserInfoList { get; } = new List { User.Info, Admin.Info }; + public static IReadOnlyList UserInfoList { get; } = new List { User.Info, Admin.Info }; } } diff --git a/Timeline/Auth/MyAuthenticationHandler.cs b/Timeline/Auth/MyAuthenticationHandler.cs index f5dcd697..5bae5117 100644 --- a/Timeline/Auth/MyAuthenticationHandler.cs +++ b/Timeline/Auth/MyAuthenticationHandler.cs @@ -3,6 +3,7 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Microsoft.Net.Http.Headers; using System; +using System.Globalization; using System.Linq; using System.Security.Claims; using System.Text.Encodings.Web; @@ -30,13 +31,13 @@ namespace Timeline.Auth public class MyAuthenticationHandler : AuthenticationHandler { private readonly ILogger _logger; - private readonly IUserService _userService; + private readonly IUserTokenManager _userTokenManager; - public MyAuthenticationHandler(IOptionsMonitor options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock, IUserService userService) + public MyAuthenticationHandler(IOptionsMonitor options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock, IUserTokenManager userTokenManager) : base(options, logger, encoder, clock) { _logger = logger.CreateLogger(); - _userService = userService; + _userTokenManager = userTokenManager; } // return null if no token is found @@ -78,9 +79,10 @@ namespace Timeline.Auth try { - var userInfo = await _userService.VerifyToken(token); + var userInfo = await _userTokenManager.VerifyToken(token); var identity = new ClaimsIdentity(AuthenticationConstants.Scheme); + identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, userInfo.Id.ToString(CultureInfo.InvariantCulture), ClaimValueTypes.Integer64)); identity.AddClaim(new Claim(identity.NameClaimType, userInfo.Username, ClaimValueTypes.String)); identity.AddClaims(UserRoleConvert.ToArray(userInfo.Administrator).Select(role => new Claim(identity.RoleClaimType, role, ClaimValueTypes.String))); diff --git a/Timeline/Controllers/TokenController.cs b/Timeline/Controllers/TokenController.cs index 851c7606..a96b6fa9 100644 --- a/Timeline/Controllers/TokenController.cs +++ b/Timeline/Controllers/TokenController.cs @@ -1,11 +1,11 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; -using Microsoft.IdentityModel.Tokens; using System; using System.Globalization; using System.Threading.Tasks; using Timeline.Helpers; +using Timeline.Models; using Timeline.Models.Http; using Timeline.Services; using static Timeline.Resources.Controllers.TokenController; @@ -16,13 +16,22 @@ namespace Timeline.Controllers [ApiController] public class TokenController : Controller { - private readonly IUserService _userService; + private readonly IUserTokenManager _userTokenManager; private readonly ILogger _logger; private readonly IClock _clock; - public TokenController(IUserService userService, ILogger logger, IClock clock) + private static User CreateUserFromUserInfo(UserInfo userInfo) { - _userService = userService; + return new User + { + Username = userInfo.Username, + Administrator = userInfo.Administrator + }; + } + + public TokenController(IUserTokenManager userTokenManager, ILogger logger, IClock clock) + { + _userTokenManager = userTokenManager; _logger = logger; _clock = clock; } @@ -47,7 +56,7 @@ namespace Timeline.Controllers if (request.Expire != null) expireTime = _clock.GetCurrentTime().AddDays(request.Expire.Value); - var result = await _userService.CreateToken(request.Username, request.Password, expireTime); + var result = await _userTokenManager.CreateToken(request.Username, request.Password, expireTime); _logger.LogInformation(Log.Format(LogCreateSuccess, ("Username", request.Username), @@ -56,7 +65,7 @@ namespace Timeline.Controllers return Ok(new CreateTokenResponse { Token = result.Token, - User = result.User + User = CreateUserFromUserInfo(result.User) }); } catch (UserNotExistException e) @@ -86,36 +95,29 @@ namespace Timeline.Controllers try { - var result = await _userService.VerifyToken(request.Token); + var result = await _userTokenManager.VerifyToken(request.Token); _logger.LogInformation(Log.Format(LogVerifySuccess, ("Username", result.Username), ("Token", request.Token))); return Ok(new VerifyTokenResponse { - User = result + User = CreateUserFromUserInfo(result) }); } - catch (JwtUserTokenBadFormatException e) + catch (UserTokenTimeExpireException e) { - if (e.ErrorCode == JwtUserTokenBadFormatException.ErrorCodes.Expired) - { - var innerException = e.InnerException as SecurityTokenExpiredException; - LogFailure(LogVerifyExpire, e, ("Expires", innerException?.Expires), - ("Current Time", _clock.GetCurrentTime())); - return BadRequest(ErrorResponse.TokenController.Verify_TimeExpired()); - } - else if (e.ErrorCode == JwtUserTokenBadFormatException.ErrorCodes.OldVersion) - { - var innerException = e.InnerException as JwtBadVersionException; - LogFailure(LogVerifyOldVersion, e, - ("Token Version", innerException?.TokenVersion), - ("Required Version", innerException?.RequiredVersion)); - return BadRequest(ErrorResponse.TokenController.Verify_OldVersion()); - } - else - { - LogFailure(LogVerifyBadFormat, e); - return BadRequest(ErrorResponse.TokenController.Verify_BadFormat()); - } + LogFailure(LogVerifyExpire, e, ("Expire Time", e.ExpireTime), ("Verify Time", e.VerifyTime)); + return BadRequest(ErrorResponse.TokenController.Verify_TimeExpired()); + } + catch (UserTokenBadVersionException e) + { + LogFailure(LogVerifyOldVersion, e, ("Token Version", e.TokenVersion), ("Required Version", e.RequiredVersion)); + return BadRequest(ErrorResponse.TokenController.Verify_OldVersion()); + + } + catch (UserTokenBadFormatException e) + { + LogFailure(LogVerifyBadFormat, e); + return BadRequest(ErrorResponse.TokenController.Verify_BadFormat()); } catch (UserNotExistException e) { diff --git a/Timeline/Controllers/UserController.cs b/Timeline/Controllers/UserController.cs index 65ee3a0f..5f1b7bd7 100644 --- a/Timeline/Controllers/UserController.cs +++ b/Timeline/Controllers/UserController.cs @@ -35,13 +35,16 @@ namespace Timeline.Controllers [HttpGet("users/{username}"), AdminAuthorize] public async Task> Get([FromRoute][Username] string username) { - var user = await _userService.GetUserByUsername(username); - if (user == null) + try + { + var user = await _userService.GetUserByUsername(username); + return Ok(user); + } + catch (UserNotExistException e) { - _logger.LogInformation(Log.Format(LogGetUserNotExist, ("Username", username))); + _logger.LogInformation(e, Log.Format(LogGetUserNotExist, ("Username", username))); return NotFound(ErrorResponse.UserCommon.NotExist()); } - return Ok(user); } [HttpPut("users/{username}"), AdminAuthorize] diff --git a/Timeline/Entities/UserAvatarEntity.cs b/Timeline/Entities/UserAvatarEntity.cs index eed819bc..6cecce1a 100644 --- a/Timeline/Entities/UserAvatarEntity.cs +++ b/Timeline/Entities/UserAvatarEntity.cs @@ -23,6 +23,10 @@ namespace Timeline.Entities [Column("last_modified"), Required] public DateTime LastModified { get; set; } + [Column("user"), Required] public long UserId { get; set; } + + [ForeignKey(nameof(UserId))] + public UserEntity User { get; set; } = default!; } } diff --git a/Timeline/Entities/UserDetailEntity.cs b/Timeline/Entities/UserDetailEntity.cs index 7a525294..1d9957f9 100644 --- a/Timeline/Entities/UserDetailEntity.cs +++ b/Timeline/Entities/UserDetailEntity.cs @@ -12,6 +12,10 @@ namespace Timeline.Entities [Column("nickname"), MaxLength(26)] public string? Nickname { get; set; } + [Column("user")] public long UserId { get; set; } + + [ForeignKey(nameof(UserId))] + public UserEntity User { get; set; } = default!; } } diff --git a/Timeline/GlobalSuppressions.cs b/Timeline/GlobalSuppressions.cs index 076c2005..c0754071 100644 --- a/Timeline/GlobalSuppressions.cs +++ b/Timeline/GlobalSuppressions.cs @@ -7,6 +7,6 @@ [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1034:Nested types should not be visible", Justification = "This is not bad.")] [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1062:Validate arguments of public methods", Justification = "No need to check the null because it's ASP.Net's duty.", Scope = "namespaceanddescendants", Target = "Timeline.Controllers")] [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1062:Validate arguments of public methods", Justification = "Migrations code are auto generated.", Scope = "namespaceanddescendants", Target = "Timeline.Migrations")] -[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "Error code constant identifiers.", Scope = "type", Target = "Timeline.ErrorCodes")] -[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1716:Identifiers should not match keywords", Justification = "Error code constant identifiers.", Scope = "type", Target = "Timeline.ErrorCodes")] -[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1724:Type names should not match namespaces", Justification = "Error code constant identifiers.", Scope = "type", Target = "Timeline.ErrorCodes")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "Generated error response identifiers.", Scope = "type", Target = "Timeline.Models.Http.ErrorResponse")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1724:Type names should not match namespaces", Justification = "Generated error response identifiers.", Scope = "type", Target = "Timeline.Models.Http.ErrorResponse")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Globalization", "CA1305:Specify IFormatProvider", Justification = "Generated error response.", Scope = "type", Target = "Timeline.Models.Http.ErrorResponse")] diff --git a/Timeline/Services/JwtBadVersionException.cs b/Timeline/Services/JwtBadVersionException.cs deleted file mode 100644 index 4ce17710..00000000 --- a/Timeline/Services/JwtBadVersionException.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System; -using Timeline.Helpers; - -namespace Timeline.Services -{ - [Serializable] - public class JwtBadVersionException : Exception - { - public JwtBadVersionException() : base(Resources.Services.Exception.JwtBadVersionException) { } - public JwtBadVersionException(string message) : base(message) { } - public JwtBadVersionException(string message, Exception inner) : base(message, inner) { } - - public JwtBadVersionException(long tokenVersion, long requiredVersion) - : base(Log.Format(Resources.Services.Exception.JwtBadVersionException, - ("Token Version", tokenVersion), - ("Required Version", requiredVersion))) - { - TokenVersion = tokenVersion; - RequiredVersion = requiredVersion; - } - - protected JwtBadVersionException( - System.Runtime.Serialization.SerializationInfo info, - System.Runtime.Serialization.StreamingContext context) : base(info, context) { } - - /// - /// The version in the token. - /// - public long? TokenVersion { get; set; } - - /// - /// The version required. - /// - public long? RequiredVersion { get; set; } - } -} diff --git a/Timeline/Services/UserService.cs b/Timeline/Services/UserService.cs index db2350a2..104db1b0 100644 --- a/Timeline/Services/UserService.cs +++ b/Timeline/Services/UserService.cs @@ -139,19 +139,30 @@ namespace Timeline.Services _logger.LogInformation(Log.Format(Resources.Services.UserService.LogCacheRemove, ("Key", key))); } - private void CheckUsernameFormat(string username, string? additionalMessage = null) + private void CheckUsernameFormat(string username, string? message = null) { - var (result, message) = _usernameValidator.Validate(username); + var (result, validationMessage) = _usernameValidator.Validate(username); if (!result) { - if (additionalMessage == null) - throw new UsernameBadFormatException(username, message); + if (message == null) + throw new UsernameBadFormatException(username, validationMessage); else - throw new UsernameBadFormatException(username, additionalMessage + message); + throw new UsernameBadFormatException(username, validationMessage, message); } } - public async Task CheckCredential(string username, string password) + private static UserInfo CreateUserInfoFromEntity(UserEntity user) + { + return new UserInfo + { + Id = user.Id, + Username = user.Name, + Administrator = UserRoleConvert.ToBool(user.RoleString), + Version = user.Version + }; + } + + public async Task VerifyCredential(string username, string password) { if (username == null) throw new ArgumentNullException(nameof(username)); @@ -169,30 +180,13 @@ namespace Timeline.Services if (!_passwordService.VerifyPassword(user.EncryptedPassword, password)) throw new BadPasswordException(password); - var token = _jwtService.GenerateJwtToken(new TokenInfo - { - Id = user.Id, - Version = user.Version - }, expires); - - return new CreateTokenResult - { - Token = token, - User = UserConvert.CreateUserInfo(user) - }; + return CreateUserInfoFromEntity(user); } - public async Task VerifyToken(string token) + public async Task GetUserById(long id) { - if (token == null) - throw new ArgumentNullException(nameof(token)); - - TokenInfo tokenInfo; - tokenInfo = _jwtService.VerifyJwtToken(token); - - var id = tokenInfo.Id; var key = GenerateCacheKeyByUserId(id); - if (!_memoryCache.TryGetValue(key, out var cache)) + if (!_memoryCache.TryGetValue(key, out var cache)) { // no cache, check the database var user = await _databaseContext.Users.Where(u => u.Id == id).SingleOrDefaultAsync(); @@ -201,34 +195,35 @@ namespace Timeline.Services throw new UserNotExistException(id); // create cache - cache = UserConvert.CreateUserCache(user); + cache = CreateUserInfoFromEntity(user); _memoryCache.CreateEntry(key).SetValue(cache); _logger.LogInformation(Log.Format(Resources.Services.UserService.LogCacheCreate, ("Key", key))); } - if (tokenInfo.Version != cache.Version) - throw new JwtUserTokenBadFormatException(new JwtBadVersionException(tokenInfo.Version, cache.Version), JwtUserTokenBadFormatException.ErrorCodes.OldVersion); - - return cache.ToUserInfo(); + return cache; } public async Task GetUserByUsername(string username) { if (username == null) throw new ArgumentNullException(nameof(username)); + CheckUsernameFormat(username); - return await _databaseContext.Users + var entity = await _databaseContext.Users .Where(user => user.Name == username) - .Select(user => UserConvert.CreateUserInfo(user)) .SingleOrDefaultAsync(); + + if (entity == null) + throw new UserNotExistException(username); + + return CreateUserInfoFromEntity(entity); } public async Task ListUsers() { - return await _databaseContext.Users - .Select(user => UserConvert.CreateUserInfo(user)) - .ToArrayAsync(); + var entities = await _databaseContext.Users.ToArrayAsync(); + return entities.Select(user => CreateUserInfoFromEntity(user)).ToArray(); } public async Task PutUser(string username, string password, bool administrator) diff --git a/Timeline/Services/UsernameBadFormatException.cs b/Timeline/Services/UsernameBadFormatException.cs index 991be7df..ad0350b5 100644 --- a/Timeline/Services/UsernameBadFormatException.cs +++ b/Timeline/Services/UsernameBadFormatException.cs @@ -9,11 +9,12 @@ namespace Timeline.Services public class UsernameBadFormatException : Exception { public UsernameBadFormatException() : base(Resources.Services.Exception.UsernameBadFormatException) { } - public UsernameBadFormatException(string username) : this() { Username = username; } - public UsernameBadFormatException(string username, Exception inner) : base(Resources.Services.Exception.UsernameBadFormatException, inner) { Username = username; } + public UsernameBadFormatException(string message) : base(message) { } + public UsernameBadFormatException(string message, Exception inner) : base(message, inner) { } - public UsernameBadFormatException(string username, string message) : base(message) { Username = username; } - public UsernameBadFormatException(string username, string message, Exception inner) : base(message, inner) { Username = username; } + public UsernameBadFormatException(string username, string validationMessage) : this() { Username = username; ValidationMessage = validationMessage; } + + public UsernameBadFormatException(string username, string validationMessage, string message) : this(message) { Username = username; ValidationMessage = validationMessage; } protected UsernameBadFormatException( System.Runtime.Serialization.SerializationInfo info, @@ -23,5 +24,7 @@ namespace Timeline.Services /// Username of bad format. /// public string Username { get; private set; } = ""; + + public string ValidationMessage { get; private set; } = ""; } } diff --git a/Timeline/Startup.cs b/Timeline/Startup.cs index c1e73182..379ce6ea 100644 --- a/Timeline/Startup.cs +++ b/Timeline/Startup.cs @@ -84,7 +84,8 @@ namespace Timeline }); services.AddScoped(); - services.AddScoped(); + services.AddScoped(); + services.AddScoped(); services.AddTransient(); services.AddTransient(); services.AddUserAvatarService(); -- cgit v1.2.3