diff --git a/Source/BSN.Commons.PresentationInfrastructure/BSN.Commons.PresentationInfrastructure.csproj b/Source/BSN.Commons.PresentationInfrastructure/BSN.Commons.PresentationInfrastructure.csproj index a3c379e..5eb3b38 100644 --- a/Source/BSN.Commons.PresentationInfrastructure/BSN.Commons.PresentationInfrastructure.csproj +++ b/Source/BSN.Commons.PresentationInfrastructure/BSN.Commons.PresentationInfrastructure.csproj @@ -1,4 +1,4 @@ - + netstandard2.0 @@ -20,4 +20,8 @@ + + + + diff --git a/Source/BSN.Commons.PresentationInfrastructure/Requests/AdvancedSearchRequest.cs b/Source/BSN.Commons.PresentationInfrastructure/Requests/AdvancedSearchRequest.cs new file mode 100644 index 0000000..d6a9145 --- /dev/null +++ b/Source/BSN.Commons.PresentationInfrastructure/Requests/AdvancedSearchRequest.cs @@ -0,0 +1,36 @@ +using System.Runtime.Serialization; + +namespace BSN.Commons.Requests +{ + /// + /// Advanced search request for pagination and Sieve processing. + /// + /// + [DataContract] + public class AdvancedSearchRequest + { + /// + /// Encoded sieve filters. + /// + [DataMember(Order = 1)] + public string Filters { get; set; } + + /// + /// Encoded sieve sorts. + /// + [DataMember(Order = 2)] + public string Sorts { get; set; } + + /// + /// Page number (Start from 1). + /// + [DataMember(Order = 3)] + public uint PageNumber { get; set; } + + /// + /// Page size. + /// + [DataMember(Order = 4)] + public uint PageSize { get; set; } + } +} diff --git a/Source/BSN.Commons.PresentationInfrastructure/Requests/PrimitiveRequest.cs b/Source/BSN.Commons.PresentationInfrastructure/Requests/PrimitiveRequest.cs new file mode 100644 index 0000000..7525cda --- /dev/null +++ b/Source/BSN.Commons.PresentationInfrastructure/Requests/PrimitiveRequest.cs @@ -0,0 +1,21 @@ +using System.Runtime.Serialization; + +namespace BSN.Commons.Requests +{ + /// + /// Primitive datatype request aimed to be used on Grpc compatible services. + /// + /// + /// This Wrappers is added due to lake of Primitive type support in Grpc methods + /// + /// + [DataContract] + public class PrimitiveRequest + { + /// + /// Primitive value. + /// + [DataMember(Order = 1)] + public T Value; + } +} diff --git a/Source/BSN.Commons/BSN.Commons.csproj b/Source/BSN.Commons/BSN.Commons.csproj index 543f69a..898a108 100644 --- a/Source/BSN.Commons/BSN.Commons.csproj +++ b/Source/BSN.Commons/BSN.Commons.csproj @@ -1,4 +1,4 @@ - + netstandard2.0 @@ -17,4 +17,14 @@ https://github.com/BSVN/Commons.git + + + + + + + + + + diff --git a/Source/BSN.Commons/Dto/PagedEntityCollection.cs b/Source/BSN.Commons/Dto/PagedEntityCollection.cs index 868c184..d0f1320 100644 --- a/Source/BSN.Commons/Dto/PagedEntityCollection.cs +++ b/Source/BSN.Commons/Dto/PagedEntityCollection.cs @@ -3,9 +3,9 @@ namespace BSN.Commons { /// - /// Stores the paginated data for the given entity type. + /// Paginated collection of an entity type. /// - /// Type of Entity for which pagination is being implemented. + /// Entity type. public class PagedEntityCollection { /// diff --git a/Source/BSN.Commons/Exceptions/HttpRequestException.cs b/Source/BSN.Commons/Exceptions/HttpRequestException.cs new file mode 100644 index 0000000..09c473e --- /dev/null +++ b/Source/BSN.Commons/Exceptions/HttpRequestException.cs @@ -0,0 +1,19 @@ +using System; +using System.Net; + +namespace BSN.Commons.Exceptions +{ + public class HttpRequestException : System.Net.Http.HttpRequestException + { + public HttpStatusCode? StatusCode { get; } + + public HttpRequestException(string message) : base(message) { } + + public HttpRequestException(string message, Exception innerException) : base(message, innerException) { } + + public HttpRequestException(string message, Exception innerException, HttpStatusCode? statusCode) : base(message, innerException) + { + StatusCode = statusCode; + } + } +} diff --git a/Source/BSN.Commons/Exceptions/InterserviceCommunicationException.cs b/Source/BSN.Commons/Exceptions/InterserviceCommunicationException.cs new file mode 100644 index 0000000..3f7705c --- /dev/null +++ b/Source/BSN.Commons/Exceptions/InterserviceCommunicationException.cs @@ -0,0 +1,13 @@ +using System; + +namespace BSN.Commons.Exceptions +{ + public class InterserviceCommunicationException : Exception + { + public InterserviceCommunicationException() : base() { } + + public InterserviceCommunicationException(string message) : base(message) { } + + public InterserviceCommunicationException(string message, Exception innerException) : base(message, innerException) { } + } +} diff --git a/Source/BSN.Commons/Extensions/CsvExtensions.cs b/Source/BSN.Commons/Extensions/CsvExtensions.cs new file mode 100644 index 0000000..14042ee --- /dev/null +++ b/Source/BSN.Commons/Extensions/CsvExtensions.cs @@ -0,0 +1,80 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Text; +using System.Text.Json.Serialization; + +namespace BSN.Commons.Extensions +{ + public static class CsvExtensions + { + public static string ToCsv(this IEnumerable collection) where T : class + { + StringBuilder csvContentBuilder = new StringBuilder(); + + Type genericType = Activator.CreateInstance().GetType(); + PropertyInfo[] properties = genericType.GetProperties(); + + StringBuilder csvHeaderBuilder = new StringBuilder(); + for (int i = 0; i < properties.Length; i++) + { + if (i + 1 < properties.Length) + { + var hasNameAttribute = properties[i].GetCustomAttribute(typeof(JsonPropertyNameAttribute)) != null; + + if (hasNameAttribute) + { + var serializationName = properties[i].GetCustomAttribute().Name; + + csvContentBuilder.Append(serializationName); + } + else + { + csvContentBuilder.Append(properties[i].Name); + } + + csvContentBuilder.Append(","); + } + else + { + csvContentBuilder.Append(properties[i].Name); + } + } + + csvContentBuilder.AppendLine(csvHeaderBuilder.ToString()); + + foreach (T item in collection) + { + StringBuilder csvRecordBuilder = new StringBuilder(); + for (int i = 0; i < properties.Length; i++) + { + object value = genericType.GetProperty(properties[i].Name).GetValue(item, null); + + if (i + 1 < properties.Length) + { + if (value != null) + { + csvRecordBuilder.Append(value.ToString()); + csvRecordBuilder.Append(","); + } + else + { + csvRecordBuilder.Append(","); + } + } + else + { + if (value != null) + { + csvRecordBuilder.Append(value.ToString()); + } + } + } + + csvContentBuilder.AppendLine(csvRecordBuilder.ToString()); + } + + return csvContentBuilder.ToString(); + } + } +} diff --git a/Source/BSN.Commons/Extensions/HttpContentExtensions.cs b/Source/BSN.Commons/Extensions/HttpContentExtensions.cs new file mode 100644 index 0000000..d5a3696 --- /dev/null +++ b/Source/BSN.Commons/Extensions/HttpContentExtensions.cs @@ -0,0 +1,24 @@ +using System.Net.Http; +using System.Text.Json; +using System.Text.Json.Serialization; +using System.Threading.Tasks; + +namespace BSN.Commons.Extensions +{ + public static class HttpContentExtensions + { + public static async Task ReadAsAsyncCaseInsensitive(this HttpContent content) where T : class + { + var options = new JsonSerializerOptions() + { + PropertyNameCaseInsensitive = true, + Converters = + { + new JsonStringEnumConverter() + } + }; + + return JsonSerializer.Deserialize(await content.ReadAsStringAsync(), options); + } + } +} diff --git a/Source/BSN.Commons/Extensions/IEnumerableExtensions.cs b/Source/BSN.Commons/Extensions/IEnumerableExtensions.cs new file mode 100644 index 0000000..08b1866 --- /dev/null +++ b/Source/BSN.Commons/Extensions/IEnumerableExtensions.cs @@ -0,0 +1,25 @@ +using System.Linq; +using System.Collections.Generic; + +namespace BSN.Commons.Extensions +{ + public static class IEnumerableExtensions + { + public static bool HasDuplicates(this IEnumerable source) + { + return source.Count() != source.Distinct().Count(); + } + + public static IEnumerable FindDuplicates(this IEnumerable source) + { + return source?.GroupBy(P => P) + .Where(Q => Q.Count() > 1) + .Select(Z => Z.Key); + } + + public static bool IsNullOrEmpty(this IEnumerable source) + { + return source == null || !source.Any(); + } + } +} diff --git a/Source/BSN.Commons/Extensions/ObjectExtensions.cs b/Source/BSN.Commons/Extensions/ObjectExtensions.cs new file mode 100644 index 0000000..ba1fc9c --- /dev/null +++ b/Source/BSN.Commons/Extensions/ObjectExtensions.cs @@ -0,0 +1,12 @@ +using System.Text.Json; + +namespace BSN.Commons.Extensions +{ + public static class ObjectExtensions + { + public static string SerializeToJson(this object @object) + { + return JsonSerializer.Serialize(@object); + } + } +} diff --git a/Source/BSN.Commons/JsonConverters/JsonDateWithoutTimeConverter.cs b/Source/BSN.Commons/JsonConverters/JsonDateWithoutTimeConverter.cs new file mode 100644 index 0000000..d0b5433 --- /dev/null +++ b/Source/BSN.Commons/JsonConverters/JsonDateWithoutTimeConverter.cs @@ -0,0 +1,20 @@ +using System; +using System.Globalization; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace BSN.Commons.JsonConverters +{ + public class JsonDateWithoutTimeConverter : JsonConverter + { + public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return DateTime.SpecifyKind(DateTime.ParseExact(reader.GetString(), "yyyy-MM-dd", CultureInfo.InvariantCulture), DateTimeKind.Utc); + } + + public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options) + { + writer.WriteStringValue(value.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture)); + } + } +} diff --git a/Source/BSN.Commons/JsonConverters/JsonE164PhoneNumberCollectionConverter.cs b/Source/BSN.Commons/JsonConverters/JsonE164PhoneNumberCollectionConverter.cs new file mode 100644 index 0000000..37372a1 --- /dev/null +++ b/Source/BSN.Commons/JsonConverters/JsonE164PhoneNumberCollectionConverter.cs @@ -0,0 +1,25 @@ +using System; +using System.Linq; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace BSN.Commons.JsonConverters +{ + public class JsonE164PhoneNumberCollectionConverter : JsonConverter + { + public override string[] Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + var list = JsonSerializer.Deserialize(ref reader, options); + + if (list == null) + return null; + + return list.Select(P => JsonE164PhoneNumberConverter.Deserialize(P)).ToArray(); + } + + public override void Write(Utf8JsonWriter writer, string[] value, JsonSerializerOptions options) + { + JsonSerializer.Serialize(writer, value.Select(P => JsonE164PhoneNumberConverter.Serialize(P)), options); + } + } +} diff --git a/Source/BSN.Commons/JsonConverters/JsonE164PhoneNumberConverter.cs b/Source/BSN.Commons/JsonConverters/JsonE164PhoneNumberConverter.cs new file mode 100644 index 0000000..d6fb2e6 --- /dev/null +++ b/Source/BSN.Commons/JsonConverters/JsonE164PhoneNumberConverter.cs @@ -0,0 +1,37 @@ +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace BSN.Commons.JsonConverters +{ + public class JsonE164PhoneNumberConverter : JsonConverter + { + protected const string InternationalPrefixSymbol = "+"; + + public override string Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return Deserialize(reader.GetString()); + } + + public override void Write(Utf8JsonWriter writer, string value, JsonSerializerOptions options) + { + writer.WriteStringValue(Serialize(value)); + } + + public static string Serialize(string value) + { + if (value == null) + return null; + + if (string.IsNullOrWhiteSpace(value)) + return string.Empty; + + return value.StartsWith(InternationalPrefixSymbol) ? value : $"{InternationalPrefixSymbol}{value}"; + } + + public static string Deserialize(string value) + { + return (value ?? string.Empty).StartsWith(InternationalPrefixSymbol) ? value.Substring(1) : value; + } + } +} diff --git a/Source/BSN.Commons/JsonConverters/JsonForceDefaultConverter.cs b/Source/BSN.Commons/JsonConverters/JsonForceDefaultConverter.cs new file mode 100644 index 0000000..5fa9f4a --- /dev/null +++ b/Source/BSN.Commons/JsonConverters/JsonForceDefaultConverter.cs @@ -0,0 +1,19 @@ +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace BSN.Commons.JsonConverters +{ + public class JsonForceDefaultConverter : JsonConverter + { + public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return JsonSerializer.Deserialize(ref reader); + } + + public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) + { + JsonSerializer.Serialize(writer, value); + } + } +} diff --git a/Source/BSN.Commons/JsonConverters/JsonIPAddressConverter.cs b/Source/BSN.Commons/JsonConverters/JsonIPAddressConverter.cs new file mode 100644 index 0000000..507ab17 --- /dev/null +++ b/Source/BSN.Commons/JsonConverters/JsonIPAddressConverter.cs @@ -0,0 +1,20 @@ +using System; +using System.Net; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace BSN.Commons.JsonConverters +{ + public class JsonIPAddressConverter : JsonConverter + { + public override IPAddress Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return IPAddress.Parse(reader.GetString()); + } + + public override void Write(Utf8JsonWriter writer, IPAddress value, JsonSerializerOptions options) + { + writer.WriteStringValue(value.ToString()); + } + } +} diff --git a/Source/BSN.Commons/JsonConverters/JsonTimeSpanConverter.cs b/Source/BSN.Commons/JsonConverters/JsonTimeSpanConverter.cs new file mode 100644 index 0000000..89e0fb3 --- /dev/null +++ b/Source/BSN.Commons/JsonConverters/JsonTimeSpanConverter.cs @@ -0,0 +1,19 @@ +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace BSN.Commons.JsonConverters +{ + public class JsonTimeSpanConverter : JsonConverter + { + public override TimeSpan Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return TimeSpan.Parse(reader.GetString()); + } + + public override void Write(Utf8JsonWriter writer, TimeSpan value, JsonSerializerOptions options) + { + writer.WriteStringValue(value.ToString()); + } + } +} diff --git a/Source/BSN.Commons/Utilities/DateTimeUtilities.cs b/Source/BSN.Commons/Utilities/DateTimeUtilities.cs new file mode 100644 index 0000000..dde7e32 --- /dev/null +++ b/Source/BSN.Commons/Utilities/DateTimeUtilities.cs @@ -0,0 +1,28 @@ +using System; +using System.Globalization; + +namespace BSN.Commons.Utilities +{ + // [Todo] R.Noei: Refactor this class to use much less codes for this conversion. + public static class DateTimeUtilities + { + /// + /// Convert Georgian Datetime to Shamsi datetime string. + /// + /// Georgian DateTime + /// Formatted string of Shamsi DateTime. + public static string GetPersianDate(this DateTime dateTime) + { + PersianCalendar persianCalendar = new PersianCalendar(); + + string year = persianCalendar.GetYear(dateTime).ToString(); + string month = persianCalendar.GetMonth(dateTime) < 10 ? $"0{persianCalendar.GetMonth(dateTime)}": persianCalendar.GetMonth(dateTime).ToString(); + string day = persianCalendar.GetDayOfMonth(dateTime) < 10 ? $"0{persianCalendar.GetDayOfMonth(dateTime)}" : persianCalendar.GetDayOfMonth(dateTime).ToString(); + string hour = persianCalendar.GetHour(dateTime) < 10 ? $"0{persianCalendar.GetHour(dateTime)}" : persianCalendar.GetHour(dateTime).ToString(); + string minutes = persianCalendar.GetMinute(dateTime) < 10 ? $"0{persianCalendar.GetMinute(dateTime)}" : persianCalendar.GetMinute(dateTime).ToString(); + string seconds = persianCalendar.GetSecond(dateTime) < 10 ? $"0{persianCalendar.GetSecond(dateTime)}" : persianCalendar.GetSecond(dateTime).ToString(); + + return $"{year}/{month}/{day} {hour}:{minutes}:{seconds}"; + } + } +} diff --git a/Source/BSN.Commons/Utilities/PhoneNumberUtilities.cs b/Source/BSN.Commons/Utilities/PhoneNumberUtilities.cs new file mode 100644 index 0000000..6041689 --- /dev/null +++ b/Source/BSN.Commons/Utilities/PhoneNumberUtilities.cs @@ -0,0 +1,62 @@ +using BSN.Commons.Extensions; +using System.Collections.Generic; +using System.Text.RegularExpressions; + +namespace BSN.Commons.Utilities +{ + public static class PhoneNumberUtilities + { + public const string InternationalPrefixSymbol = "+"; + + public static bool IsValidPhoneNumber(string value) + { + if (value == null) + return false; + + return Regex.IsMatch(value, PHONE_NUMBER_PATTERN); + } + + public static bool IsValidVirtualNumber(string value) + { + if (value == null) + return false; + + return Regex.IsMatch(value, VIRTUAL_NUMBER_PATTERN); + } + + /// + /// Ascertains if a given string is a valid ICCID, which in our domain is the SerialNumber property. + /// More information available at https://en.wikipedia.org/wiki/SIM_card#ICCID. + /// + public static bool IsValidIntegratedCircuitCardIdentifier(string value) + { + if (value == null) + return false; + + return Regex.IsMatch(value, INTEGRATED_CIRCUIT_CARD_IDENTIFIER_PATTERN); + } + + public static bool IsValidPhoneNumberPrefixes(List values) + { + if (values.IsNullOrEmpty()) + return false; + + return values.TrueForAll(P => Regex.IsMatch(P, PHONE_NUMBER_PREFIX)); + } + + public static bool IsValidDisplayName(string value) + { + if (value == null) + return true; + + return value.Length <= DISPLAY_NAME_MAXIMUM_LENGTH; + } + + + private const string PHONE_NUMBER_PATTERN = "^98[0-9]{3,10}$"; + private const string VIRTUAL_NUMBER_PATTERN = "^98[0-9]{10}$"; + private const string PHONE_NUMBER_PREFIX = "^98[0-9]{1,10}$"; + private const string INTEGRATED_CIRCUIT_CARD_IDENTIFIER_PATTERN = "^[0-9]{1,22}$"; + private const int DISPLAY_NAME_MAXIMUM_LENGTH = 16; + } +}