From ec74a3c491d361bbaf9354b7f17be750b7b8823c Mon Sep 17 00:00:00 2001 From: 杨宇千 Date: Sat, 17 Aug 2019 19:25:08 +0800 Subject: Add validation. --- Timeline/Models/Validation/UsernameValidator.cs | 45 ++++++++++ Timeline/Models/Validation/Validator.cs | 107 ++++++++++++++++++++++++ Timeline/Services/UserService.cs | 52 +----------- 3 files changed, 153 insertions(+), 51 deletions(-) create mode 100644 Timeline/Models/Validation/UsernameValidator.cs create mode 100644 Timeline/Models/Validation/Validator.cs diff --git a/Timeline/Models/Validation/UsernameValidator.cs b/Timeline/Models/Validation/UsernameValidator.cs new file mode 100644 index 00000000..e4891400 --- /dev/null +++ b/Timeline/Models/Validation/UsernameValidator.cs @@ -0,0 +1,45 @@ +using System.Linq; +using System.Text.RegularExpressions; + +namespace Timeline.Models.Validation +{ + public class UsernameValidator : Validator + { + 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) + { + if (value.Length == 0) + { + message = "An empty string is not permitted."; + return false; + } + + if (value.Length > 26) + { + message = $"Too long, more than 26 characters is not premitted, found {value.Length}."; + return false; + } + + foreach ((char c, int i) in value.Select((c, i) => (c, i))) + if (char.IsWhiteSpace(c)) + { + message = $"A whitespace is found at {i} . Whitespace is not permited."; + return false; + } + + var match = _regex.Match(value); + if (!match.Success) + { + message = "Regex match failed."; + return false; + } + + message = ValidationConstants.SuccessMessage; + return true; + } + } +} diff --git a/Timeline/Models/Validation/Validator.cs b/Timeline/Models/Validation/Validator.cs new file mode 100644 index 00000000..a1acbed9 --- /dev/null +++ b/Timeline/Models/Validation/Validator.cs @@ -0,0 +1,107 @@ +using System; +using System.ComponentModel.DataAnnotations; + +namespace Timeline.Models.Validation +{ + /// + /// A validator to validate value. + /// See . + /// + public interface IValidator + { + /// + /// Validate given value. + /// + /// The value to validate. + /// The validation message. + /// True if validation passed. Otherwise false. + bool Validate(object value, out string message); + } + + public static class ValidationConstants + { + public const string SuccessMessage = "Validation succeeded."; + } + + /// + /// Convenient base class for validator. + /// + /// The type of accepted value. + /// + /// Subclass should override to do the real validation. + /// This class will check the nullity and type of value. If value is null or not of type + /// it will return false and not call . + /// + /// If you want some other behaviours, write the validator from scratch. + /// + public abstract class Validator : IValidator + { + public bool Validate(object value, out string message) + { + if (value == null) + { + message = "Value is null."; + return false; + } + + if (value is T v) + { + + return DoValidate(v, out message); + } + else + { + message = $"Value is not of type {typeof(T).Name}"; + return false; + } + } + + protected abstract bool DoValidate(T value, out string message); + } + + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, + AllowMultiple = false)] + public class ValidateWithAttribute : ValidationAttribute + { + private readonly IValidator _validator; + + /// + /// Create with a given validator. + /// + /// The validator used to validate. + public ValidateWithAttribute(IValidator validator) + { + _validator = validator ?? throw new ArgumentNullException(nameof(validator)); + } + + /// + /// Create the validator with default constructor. + /// + /// The type of the validator. + public ValidateWithAttribute(Type validatorType) + { + if (validatorType == null) + throw new ArgumentNullException(nameof(validatorType)); + + if (!typeof(IValidator).IsAssignableFrom(validatorType)) + throw new ArgumentException("Given type is not assignable to IValidator.", nameof(validatorType)); + + try + { + _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); + } + } + + protected override ValidationResult IsValid(object value, ValidationContext validationContext) + { + if (_validator.Validate(value, out var message)) + return ValidationResult.Success; + else + return new ValidationResult(string.Format("Field {0} is bad. {1}", validationContext.DisplayName, message)); + } + } +} diff --git a/Timeline/Services/UserService.cs b/Timeline/Services/UserService.cs index 0993d3dc..27145683 100644 --- a/Timeline/Services/UserService.cs +++ b/Timeline/Services/UserService.cs @@ -3,10 +3,10 @@ using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Logging; using System; using System.Linq; -using System.Text.RegularExpressions; using System.Threading.Tasks; using Timeline.Entities; using Timeline.Models; +using Timeline.Models.Validation; using static Timeline.Helpers.MyLogHelper; using static Timeline.Models.UserUtility; @@ -121,56 +121,6 @@ namespace Timeline.Services public string Username { get; private set; } } - public class UsernameValidator - { - public const int MaxLength = 26; - public const string RegexPattern = @"^[a-zA-Z0-9_][a-zA-Z0-9-_]*$"; - - private readonly Regex _regex = new Regex(RegexPattern); - - /// - /// Validate a username. - /// - /// The username. Can't be null. - /// Set as error message if there is error. Or null if no error. - /// True if validation passed. Otherwise false. - /// Thrown when is null. - public bool Validate(string username, out string message) - { - if (username == null) - throw new ArgumentNullException(nameof(username)); - - if (username.Length == 0) - { - message = "An empty string is not permitted."; - return false; - } - - if (username.Length > 26) - { - message = $"Too long, more than 26 characters is not premitted, found {username.Length}."; - return false; - } - - foreach ((char c, int i) in username.Select((c, i) => (c, i))) - if (char.IsWhiteSpace(c)) - { - message = $"A whitespace is found at {i} . Whitespace is not permited."; - return false; - } - - var match = _regex.Match(username); - if (!match.Success) - { - message = "Regex match failed."; - return false; - } - - message = null; - return true; - } - } - public interface IUserService { /// -- cgit v1.2.3