aboutsummaryrefslogtreecommitdiff
path: root/Timeline/Models
diff options
context:
space:
mode:
author杨宇千 <crupest@outlook.com>2019-10-24 20:15:58 +0800
committerGitHub <noreply@github.com>2019-10-24 20:15:58 +0800
commit7305358a88ffc87f51f7b78deb4f07ef99120beb (patch)
tree7ca5010a06829cc5fadea1ea17ae72d082fc344c /Timeline/Models
parent297d0c9029360f1d5334ed843b9b299356740ec1 (diff)
parenta0f3cd7599a48c14fb5492fb1c6e2dbd0a82fb45 (diff)
downloadtimeline-7305358a88ffc87f51f7b78deb4f07ef99120beb.tar.gz
timeline-7305358a88ffc87f51f7b78deb4f07ef99120beb.tar.bz2
timeline-7305358a88ffc87f51f7b78deb4f07ef99120beb.zip
Merge pull request #50 from crupest/refactor
Refactor : A Huge Step
Diffstat (limited to 'Timeline/Models')
-rw-r--r--Timeline/Models/Http/Common.cs160
-rw-r--r--Timeline/Models/Http/Token.cs14
-rw-r--r--Timeline/Models/Http/User.cs16
-rw-r--r--Timeline/Models/PutResult.cs4
-rw-r--r--Timeline/Models/UserConvert.cs67
-rw-r--r--Timeline/Models/UserDetail.cs45
-rw-r--r--Timeline/Models/UserInfo.cs4
-rw-r--r--Timeline/Models/UserUtility.cs60
-rw-r--r--Timeline/Models/Validation/UserDetailValidator.cs116
-rw-r--r--Timeline/Models/Validation/UsernameValidator.cs48
-rw-r--r--Timeline/Models/Validation/Validator.cs64
11 files changed, 279 insertions, 319 deletions
diff --git a/Timeline/Models/Http/Common.cs b/Timeline/Models/Http/Common.cs
index a72f187c..39ddddd9 100644
--- a/Timeline/Models/Http/Common.cs
+++ b/Timeline/Models/Http/Common.cs
@@ -1,76 +1,164 @@
+using Microsoft.Extensions.Localization;
+using Timeline.Helpers;
+
namespace Timeline.Models.Http
{
public class CommonResponse
{
- public static class ErrorCodes
+ internal static CommonResponse InvalidModel(string message)
+ {
+ return new CommonResponse(ErrorCodes.Http.Common.InvalidModel, message);
+ }
+
+ public CommonResponse()
{
- /// <summary>
- /// Used when the model is invaid.
- /// For example a required field is null.
- /// </summary>
- public const int InvalidModel = -100;
- public const int Header_Missing_ContentType = -111;
- public const int Header_Missing_ContentLength = -112;
- public const int Header_Zero_ContentLength = -113;
- public const int Header_BadFormat_IfNonMatch = -114;
}
- public static CommonResponse InvalidModel(string message)
+ public CommonResponse(int code, string message)
{
- return new CommonResponse(ErrorCodes.InvalidModel, message);
+ Code = code;
+ Message = message;
}
- public static CommonResponse MissingContentType()
+ public int Code { get; set; }
+ public string? Message { get; set; }
+ }
+
+ internal static class HeaderErrorResponse
+ {
+ internal static CommonResponse MissingContentType(IStringLocalizerFactory localizerFactory)
{
- return new CommonResponse(ErrorCodes.Header_Missing_ContentType, "Header Content-Type is required.");
+ var localizer = localizerFactory.Create("Models.Http.Common");
+ return new CommonResponse(ErrorCodes.Http.Common.Header.Missing_ContentType, localizer["HeaderMissingContentType"]);
}
- public static CommonResponse MissingContentLength()
+ internal static CommonResponse MissingContentLength(IStringLocalizerFactory localizerFactory)
{
- return new CommonResponse(ErrorCodes.Header_Missing_ContentLength, "Header Content-Length is missing or of bad format.");
+ var localizer = localizerFactory.Create("Models.Http.Common");
+ return new CommonResponse(ErrorCodes.Http.Common.Header.Missing_ContentLength, localizer["HeaderMissingContentLength"]);
}
- public static CommonResponse ZeroContentLength()
+ internal static CommonResponse ZeroContentLength(IStringLocalizerFactory localizerFactory)
{
- return new CommonResponse(ErrorCodes.Header_Zero_ContentLength, "Header Content-Length must not be 0.");
+ var localizer = localizerFactory.Create("Models.Http.Common");
+ return new CommonResponse(ErrorCodes.Http.Common.Header.Zero_ContentLength, localizer["HeaderZeroContentLength"]);
}
- public static CommonResponse BadIfNonMatch()
+ internal static CommonResponse BadIfNonMatch(IStringLocalizerFactory localizerFactory)
{
- return new CommonResponse(ErrorCodes.Header_BadFormat_IfNonMatch, "Header If-Non-Match is of bad format.");
+ var localizer = localizerFactory.Create("Models.Http.Common");
+ return new CommonResponse(ErrorCodes.Http.Common.Header.BadFormat_IfNonMatch, localizer["HeaderBadIfNonMatch"]);
}
+ }
- public CommonResponse()
+ internal static class ContentErrorResponse
+ {
+ internal static CommonResponse TooBig(IStringLocalizerFactory localizerFactory, string maxLength)
+ {
+ var localizer = localizerFactory.Create("Models.Http.Common");
+ return new CommonResponse(ErrorCodes.Http.Common.Content.TooBig, localizer["ContentTooBig", maxLength]);
+ }
+
+ internal static CommonResponse UnmatchedLength_Smaller(IStringLocalizerFactory localizerFactory)
+ {
+ var localizer = localizerFactory.Create("Models.Http.Common");
+ return new CommonResponse(ErrorCodes.Http.Common.Content.UnmatchedLength_Smaller, localizer["ContentUnmatchedLengthSmaller"]);
+ }
+ internal static CommonResponse UnmatchedLength_Bigger(IStringLocalizerFactory localizerFactory)
+ {
+ var localizer = localizerFactory.Create("Models.Http.Common");
+ return new CommonResponse(ErrorCodes.Http.Common.Content.UnmatchedLength_Bigger, localizer["ContentUnmatchedLengthBigger"]);
+ }
+ }
+
+
+ public class CommonDataResponse<T> : CommonResponse
+ {
+ public CommonDataResponse()
{
}
- public CommonResponse(int code, string message)
+ public CommonDataResponse(int code, string message, T data)
+ : base(code, message)
{
- Code = code;
- Message = message;
+ Data = data;
}
- public int Code { get; set; }
- public string Message { get; set; }
+ public T Data { get; set; } = default!;
}
- public static class CommonPutResponse
+ public class CommonPutResponse : CommonDataResponse<CommonPutResponse.ResponseData>
{
- public const int CreatedCode = 0;
- public const int ModifiedCode = 1;
+ public class ResponseData
+ {
+ public ResponseData(bool create)
+ {
+ Create = create;
+ }
- public static CommonResponse Created { get; } = new CommonResponse(CreatedCode, "A new item is created.");
- public static CommonResponse Modified { get; } = new CommonResponse(ModifiedCode, "An existent item is modified.");
+ public bool Create { get; set; }
+ }
+
+ public CommonPutResponse()
+ {
+
+ }
+
+ public CommonPutResponse(int code, string message, bool create)
+ : base(code, message, new ResponseData(create))
+ {
+
+ }
+
+ internal static CommonPutResponse Create(IStringLocalizerFactory localizerFactory)
+ {
+ var localizer = localizerFactory.Create("Models.Http.Common");
+ return new CommonPutResponse(0, localizer["PutCreate"], true);
+ }
+
+ internal static CommonPutResponse Modify(IStringLocalizerFactory localizerFactory)
+ {
+ var localizer = localizerFactory.Create("Models.Http.Common");
+ return new CommonPutResponse(0, localizer["PutModify"], false);
+
+ }
}
- public static class CommonDeleteResponse
+ public class CommonDeleteResponse : CommonDataResponse<CommonDeleteResponse.ResponseData>
{
- public const int DeletedCode = 0;
- public const int NotExistsCode = 1;
+ public class ResponseData
+ {
+ public ResponseData(bool delete)
+ {
+ Delete = delete;
+ }
- public static CommonResponse Deleted { get; } = new CommonResponse(DeletedCode, "An existent item is deleted.");
- public static CommonResponse NotExists { get; } = new CommonResponse(NotExistsCode, "The item does not exist.");
+ public bool Delete { get; set; }
+ }
+
+ public CommonDeleteResponse()
+ {
+
+ }
+
+ public CommonDeleteResponse(int code, string message, bool delete)
+ : base(code, message, new ResponseData(delete))
+ {
+
+ }
+
+ internal static CommonDeleteResponse Delete(IStringLocalizerFactory localizerFactory)
+ {
+ var localizer = localizerFactory.Create("Models.Http.Common");
+ return new CommonDeleteResponse(0, localizer["DeleteDelete"], true);
+ }
+
+ internal static CommonDeleteResponse NotExist(IStringLocalizerFactory localizerFactory)
+ {
+ var localizer = localizerFactory.Create("Models.Models.Http.Common");
+ return new CommonDeleteResponse(0, localizer["DeleteNotExist"], false);
+ }
}
}
diff --git a/Timeline/Models/Http/Token.cs b/Timeline/Models/Http/Token.cs
index 68a66d0a..ea8b59ed 100644
--- a/Timeline/Models/Http/Token.cs
+++ b/Timeline/Models/Http/Token.cs
@@ -5,28 +5,28 @@ namespace Timeline.Models.Http
public class CreateTokenRequest
{
[Required]
- public string Username { get; set; }
+ public string Username { get; set; } = default!;
[Required]
- public string Password { get; set; }
+ public string Password { get; set; } = default!;
// in days, optional
[Range(1, 365)]
- public int? ExpireOffset { get; set; }
+ public int? Expire { get; set; }
}
public class CreateTokenResponse
{
- public string Token { get; set; }
- public UserInfo User { get; set; }
+ public string Token { get; set; } = default!;
+ public UserInfo User { get; set; } = default!;
}
public class VerifyTokenRequest
{
[Required]
- public string Token { get; set; }
+ public string Token { get; set; } = default!;
}
public class VerifyTokenResponse
{
- public UserInfo User { get; set; }
+ public UserInfo User { get; set; } = default!;
}
}
diff --git a/Timeline/Models/Http/User.cs b/Timeline/Models/Http/User.cs
index 4308a19c..516c1329 100644
--- a/Timeline/Models/Http/User.cs
+++ b/Timeline/Models/Http/User.cs
@@ -6,31 +6,33 @@ namespace Timeline.Models.Http
public class UserPutRequest
{
[Required]
- public string Password { get; set; }
+ public string Password { get; set; } = default!;
[Required]
public bool? Administrator { get; set; }
}
public class UserPatchRequest
{
- public string Password { get; set; }
+ public string? Password { get; set; }
public bool? Administrator { get; set; }
}
public class ChangeUsernameRequest
{
[Required]
- public string OldUsername { get; set; }
+ [Username]
+ public string OldUsername { get; set; } = default!;
- [Required, ValidateWith(typeof(UsernameValidator))]
- public string NewUsername { get; set; }
+ [Required]
+ [Username]
+ public string NewUsername { get; set; } = default!;
}
public class ChangePasswordRequest
{
[Required]
- public string OldPassword { get; set; }
+ public string OldPassword { get; set; } = default!;
[Required]
- public string NewPassword { get; set; }
+ public string NewPassword { get; set; } = default!;
}
}
diff --git a/Timeline/Models/PutResult.cs b/Timeline/Models/PutResult.cs
index 544602eb..cecf86e6 100644
--- a/Timeline/Models/PutResult.cs
+++ b/Timeline/Models/PutResult.cs
@@ -8,10 +8,10 @@ namespace Timeline.Models
/// <summary>
/// Indicates the item did not exist and now is created.
/// </summary>
- Created,
+ Create,
/// <summary>
/// Indicates the item exists already and is modified.
/// </summary>
- Modified
+ Modify
}
}
diff --git a/Timeline/Models/UserConvert.cs b/Timeline/Models/UserConvert.cs
new file mode 100644
index 00000000..5b132421
--- /dev/null
+++ b/Timeline/Models/UserConvert.cs
@@ -0,0 +1,67 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Timeline.Entities;
+using Timeline.Services;
+
+namespace Timeline.Models
+{
+ public static class UserConvert
+ {
+ public static UserInfo CreateUserInfo(User user)
+ {
+ if (user == null)
+ throw new ArgumentNullException(nameof(user));
+ return new UserInfo(user.Name, UserRoleConvert.ToBool(user.RoleString));
+ }
+
+ internal static UserCache CreateUserCache(User user)
+ {
+ if (user == null)
+ throw new ArgumentNullException(nameof(user));
+ return new UserCache
+ {
+ Username = user.Name,
+ Administrator = UserRoleConvert.ToBool(user.RoleString),
+ Version = user.Version
+ };
+ }
+ }
+
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1062:Validate arguments of public methods", Justification = "No need.")]
+ public static class UserRoleConvert
+ {
+ public const string UserRole = UserRoles.User;
+ public const string AdminRole = UserRoles.Admin;
+
+ public static string[] ToArray(bool administrator)
+ {
+ return administrator ? new string[] { UserRole, AdminRole } : new string[] { UserRole };
+ }
+
+ public static string[] ToArray(string s)
+ {
+ return s.Split(',').ToArray();
+ }
+
+ public static bool ToBool(IReadOnlyCollection<string> roles)
+ {
+ return roles.Contains(AdminRole);
+ }
+
+ public static string ToString(IReadOnlyCollection<string> roles)
+ {
+ return string.Join(',', roles);
+ }
+
+ public static string ToString(bool administrator)
+ {
+ return administrator ? UserRole + "," + AdminRole : UserRole;
+ }
+
+ public static bool ToBool(string s)
+ {
+ return s.Contains("admin", StringComparison.InvariantCulture);
+ }
+ }
+}
diff --git a/Timeline/Models/UserDetail.cs b/Timeline/Models/UserDetail.cs
deleted file mode 100644
index 1a6c0c6a..00000000
--- a/Timeline/Models/UserDetail.cs
+++ /dev/null
@@ -1,45 +0,0 @@
-using System.ComponentModel.DataAnnotations;
-using Timeline.Entities;
-using Timeline.Models.Validation;
-using Newtonsoft.Json;
-
-namespace Timeline.Models
-{
- public class UserDetail
- {
- [MaxLength(10)]
- public string Nickname { get; set; }
-
- [ValidateWith(typeof(UserDetailValidators.QQValidator))]
- [JsonProperty(PropertyName = "qq")]
- public string QQ { get; set; }
-
- [ValidateWith(typeof(UserDetailValidators.EMailValidator))]
- public string Email { get; set; }
-
- [ValidateWith(typeof(UserDetailValidators.PhoneNumberValidator))]
- public string PhoneNumber { get; set; }
-
- public string Description { get; set; }
-
- private static string CoerceEmptyToNull(string value)
- {
- if (string.IsNullOrEmpty(value))
- return null;
- else
- return value;
- }
-
- public static UserDetail From(UserDetailEntity entity)
- {
- return new UserDetail
- {
- Nickname = CoerceEmptyToNull(entity.Nickname),
- QQ = CoerceEmptyToNull(entity.QQ),
- Email = CoerceEmptyToNull(entity.Email),
- PhoneNumber = CoerceEmptyToNull(entity.PhoneNumber),
- Description = CoerceEmptyToNull(entity.Description)
- };
- }
- }
-}
diff --git a/Timeline/Models/UserInfo.cs b/Timeline/Models/UserInfo.cs
index e502855b..b60bdfa2 100644
--- a/Timeline/Models/UserInfo.cs
+++ b/Timeline/Models/UserInfo.cs
@@ -12,8 +12,8 @@ namespace Timeline.Models
Administrator = administrator;
}
- public string Username { get; set; }
- public bool Administrator { get; set; }
+ public string Username { get; set; } = default!;
+ public bool Administrator { get; set; } = default!;
public override string ToString()
{
diff --git a/Timeline/Models/UserUtility.cs b/Timeline/Models/UserUtility.cs
deleted file mode 100644
index 405987b5..00000000
--- a/Timeline/Models/UserUtility.cs
+++ /dev/null
@@ -1,60 +0,0 @@
-using System;
-using System.Linq;
-using Timeline.Entities;
-using Timeline.Services;
-
-namespace Timeline.Models
-{
- public static class UserUtility
- {
- public const string UserRole = UserRoles.User;
- public const string AdminRole = UserRoles.Admin;
-
- public static string[] UserRoleArray { get; } = new string[] { UserRole };
- public static string[] AdminRoleArray { get; } = new string[] { UserRole, AdminRole };
-
- public static string[] IsAdminToRoleArray(bool isAdmin)
- {
- return isAdmin ? AdminRoleArray : UserRoleArray;
- }
-
- public static bool RoleArrayToIsAdmin(string[] roles)
- {
- return roles.Contains(AdminRole);
- }
-
- public static string[] RoleStringToRoleArray(string roleString)
- {
- return roleString.Split(',').ToArray();
- }
-
- public static string RoleArrayToRoleString(string[] roles)
- {
- return string.Join(',', roles);
- }
-
- public static string IsAdminToRoleString(bool isAdmin)
- {
- return RoleArrayToRoleString(IsAdminToRoleArray(isAdmin));
- }
-
- public static bool RoleStringToIsAdmin(string roleString)
- {
- return RoleArrayToIsAdmin(RoleStringToRoleArray(roleString));
- }
-
- public static UserInfo CreateUserInfo(User user)
- {
- if (user == null)
- throw new ArgumentNullException(nameof(user));
- return new UserInfo(user.Name, RoleStringToIsAdmin(user.RoleString));
- }
-
- internal static UserCache CreateUserCache(User user)
- {
- if (user == null)
- throw new ArgumentNullException(nameof(user));
- return new UserCache { Username = user.Name, Administrator = RoleStringToIsAdmin(user.RoleString), Version = user.Version };
- }
- }
-}
diff --git a/Timeline/Models/Validation/UserDetailValidator.cs b/Timeline/Models/Validation/UserDetailValidator.cs
deleted file mode 100644
index 19c82edb..00000000
--- a/Timeline/Models/Validation/UserDetailValidator.cs
+++ /dev/null
@@ -1,116 +0,0 @@
-using System;
-using System.Net.Mail;
-
-namespace Timeline.Models.Validation
-{
- public abstract class OptionalStringValidator : IValidator
- {
- public bool Validate(object value, out string message)
- {
- if (value == null)
- {
- message = ValidationConstants.SuccessMessage;
- return true;
- }
-
- if (value is string s)
- {
- if (s.Length == 0)
- {
- message = ValidationConstants.SuccessMessage;
- return true;
- }
- return DoValidate(s, out message);
- }
- else
- {
- message = "Value is not of type string.";
- return false;
- }
- }
-
- protected abstract bool DoValidate(string value, out string message);
- }
-
- public static class UserDetailValidators
- {
-
- public class QQValidator : OptionalStringValidator
- {
- protected override bool DoValidate(string value, out string message)
- {
- if (value.Length < 5)
- {
- message = "QQ is too short.";
- return false;
- }
-
- if (value.Length > 11)
- {
- message = "QQ is too long.";
- return false;
- }
-
- foreach (var c in value)
- {
- if (!char.IsDigit(c))
- {
- message = "QQ must only contain digit.";
- return false;
- }
- }
-
- message = ValidationConstants.SuccessMessage;
- return true;
- }
- }
-
- public class EMailValidator : OptionalStringValidator
- {
- protected override bool DoValidate(string value, out string message)
- {
- if (value.Length > 50)
- {
- message = "E-Mail is too long.";
- return false;
- }
-
- try
- {
- var _ = new MailAddress(value);
- }
- catch (FormatException)
- {
- message = "The format of E-Mail is bad.";
- return false;
- }
- message = ValidationConstants.SuccessMessage;
- return true;
- }
- }
-
- public class PhoneNumberValidator : OptionalStringValidator
- {
- protected override bool DoValidate(string value, out string message)
- {
- if (value.Length > 14)
- {
- message = "Phone number is too long.";
- return false;
- }
-
- foreach (var c in value)
- {
- if (!char.IsDigit(c))
- {
- message = "Phone number can only contain digit.";
- return false;
- }
- }
-
- message = ValidationConstants.SuccessMessage;
- return true;
- }
- }
- }
-}
diff --git a/Timeline/Models/Validation/UsernameValidator.cs b/Timeline/Models/Validation/UsernameValidator.cs
index e4891400..dc237add 100644
--- a/Timeline/Models/Validation/UsernameValidator.cs
+++ b/Timeline/Models/Validation/UsernameValidator.cs
@@ -1,45 +1,51 @@
-using System.Linq;
-using System.Text.RegularExpressions;
+using System;
+using System.Linq;
namespace Timeline.Models.Validation
{
public class UsernameValidator : Validator<string>
{
public const int MaxLength = 26;
- public const string RegexPattern = @"^[a-zA-Z0-9_][a-zA-Z0-9-_]*$";
- private readonly Regex _regex = new Regex(RegexPattern);
-
- protected override bool DoValidate(string value, out string message)
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1062:Validate arguments of public methods", Justification = "Already checked in base class.")]
+ protected override (bool, ValidationMessageGenerator) DoValidate(string value)
{
if (value.Length == 0)
{
- message = "An empty string is not permitted.";
- return false;
+ return (false, factory =>
+ factory?.Create(typeof(UsernameValidator))?["ValidationMessageEmptyString"]
+ ?? Resources.Models.Validation.UsernameValidator.InvariantValidationMessageEmptyString);
}
if (value.Length > 26)
{
- message = $"Too long, more than 26 characters is not premitted, found {value.Length}.";
- return false;
+ return (false, factory =>
+ factory?.Create(typeof(UsernameValidator))?["ValidationMessageTooLong"]
+ ?? Resources.Models.Validation.UsernameValidator.InvariantValidationMessageTooLong);
}
foreach ((char c, int i) in value.Select((c, i) => (c, i)))
- if (char.IsWhiteSpace(c))
+ {
+ if (!(char.IsLetterOrDigit(c) || c == '-' || c == '_'))
{
- message = $"A whitespace is found at {i} . Whitespace is not permited.";
- return false;
+ return (false, factory =>
+ factory?.Create(typeof(UsernameValidator))?["ValidationMessageInvalidChar"]
+ ?? Resources.Models.Validation.UsernameValidator.InvariantValidationMessageInvalidChar);
}
-
- var match = _regex.Match(value);
- if (!match.Success)
- {
- message = "Regex match failed.";
- return false;
}
- message = ValidationConstants.SuccessMessage;
- return true;
+ return (true, SuccessMessageGenerator);
+ }
+ }
+
+ [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter,
+ AllowMultiple = false)]
+ public class UsernameAttribute : ValidateWithAttribute
+ {
+ public UsernameAttribute()
+ : base(typeof(UsernameValidator))
+ {
+
}
}
}
diff --git a/Timeline/Models/Validation/Validator.cs b/Timeline/Models/Validation/Validator.cs
index a1acbed9..d2c7c377 100644
--- a/Timeline/Models/Validation/Validator.cs
+++ b/Timeline/Models/Validation/Validator.cs
@@ -1,11 +1,21 @@
-using System;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Localization;
+using System;
using System.ComponentModel.DataAnnotations;
+using Timeline.Helpers;
namespace Timeline.Models.Validation
{
/// <summary>
+ /// Generate a message from a localizer factory.
+ /// If localizerFactory is null, it should return a culture-invariant message.
+ /// </summary>
+ /// <param name="localizerFactory">The localizer factory. Could be null.</param>
+ /// <returns>The message.</returns>
+ public delegate string ValidationMessageGenerator(IStringLocalizerFactory? localizerFactory);
+
+ /// <summary>
/// A validator to validate value.
- /// See <see cref="Validate(object, out string)"/>.
/// </summary>
public interface IValidator
{
@@ -13,14 +23,8 @@ namespace Timeline.Models.Validation
/// Validate given value.
/// </summary>
/// <param name="value">The value to validate.</param>
- /// <param name="message">The validation message.</param>
- /// <returns>True if validation passed. Otherwise false.</returns>
- bool Validate(object value, out string message);
- }
-
- public static class ValidationConstants
- {
- public const string SuccessMessage = "Validation succeeded.";
+ /// <returns>Validation success or not and the message generator.</returns>
+ (bool, ValidationMessageGenerator) Validate(object? value);
}
/// <summary>
@@ -36,27 +40,32 @@ namespace Timeline.Models.Validation
/// </remarks>
public abstract class Validator<T> : IValidator
{
- public bool Validate(object value, out string message)
+ public (bool, ValidationMessageGenerator) Validate(object? value)
{
if (value == null)
{
- message = "Value is null.";
- return false;
+ return (false, factory =>
+ factory?.Create("Models.Validation.Validator")?["ValidatorMessageNull"]
+ ?? Resources.Models.Validation.Validator.InvariantValidatorMessageNull
+ );
}
if (value is T v)
{
-
- return DoValidate(v, out message);
+ return DoValidate(v);
}
else
{
- message = $"Value is not of type {typeof(T).Name}";
- return false;
+ return (false, factory =>
+ factory?.Create("Models.Validation.Validator")?["ValidatorMessageBadType", typeof(T).FullName]
+ ?? Resources.Models.Validation.Validator.InvariantValidatorMessageBadType);
}
}
- protected abstract bool DoValidate(T value, out string message);
+ protected static ValidationMessageGenerator SuccessMessageGenerator { get; } = factory =>
+ factory?.Create("Models.Validation.Validator")?["ValidatorMessageSuccess"] ?? Resources.Models.Validation.Validator.InvariantValidatorMessageSuccess;
+
+ protected abstract (bool, ValidationMessageGenerator) DoValidate(T value);
}
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter,
@@ -84,24 +93,33 @@ namespace Timeline.Models.Validation
throw new ArgumentNullException(nameof(validatorType));
if (!typeof(IValidator).IsAssignableFrom(validatorType))
- throw new ArgumentException("Given type is not assignable to IValidator.", nameof(validatorType));
+ throw new ArgumentException(
+ Resources.Models.Validation.Validator.ValidateWithAttributeNotValidator,
+ nameof(validatorType));
try
{
- _validator = Activator.CreateInstance(validatorType) as IValidator;
+ _validator = (Activator.CreateInstance(validatorType) as IValidator)!;
}
catch (Exception e)
{
- throw new ArgumentException("Failed to create a validator instance from default constructor. See inner exception.", e);
+ throw new ArgumentException(
+ Resources.Models.Validation.Validator.ValidateWithAttributeCreateFail, e);
}
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
- if (_validator.Validate(value, out var message))
+ var (result, messageGenerator) = _validator.Validate(value);
+ if (result)
+ {
return ValidationResult.Success;
+ }
else
- return new ValidationResult(string.Format("Field {0} is bad. {1}", validationContext.DisplayName, message));
+ {
+ var localizerFactory = validationContext.GetRequiredService<IStringLocalizerFactory>();
+ return new ValidationResult(messageGenerator(localizerFactory));
+ }
}
}
}