commit c743026b3c21d08fedd10428b94f1052cacd9151 Author: Hadi Khazaee Asl Date: Thu Jan 25 04:39:39 2024 +0330 Initial Commit ... diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1bd9d0b --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +# +# DotNet ... +bin +obj + +# +# Natural Docs ... +Documentation/* diff --git a/Attributes/RequireXPoweredAttribute.cs b/Attributes/RequireXPoweredAttribute.cs new file mode 100644 index 0000000..7933663 --- /dev/null +++ b/Attributes/RequireXPoweredAttribute.cs @@ -0,0 +1,38 @@ +using System.Linq; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; +using xCommons.Constants; +using xCommons.Extensions; + +namespace xCommons.Attributes { + public partial class RequireXPoweredAttribute : AuthorizeAttribute, IAuthorizationFilter { + + private readonly bool ignoreAnonymous; + + public RequireXPoweredAttribute (bool IgnoreAnonymous = true) { + ignoreAnonymous = IgnoreAnonymous; + } + + public void OnAuthorization (AuthorizationFilterContext context) { + // + var config = context.GetXAppConfiguration (); + + // + var isExistsXPowered = context.HttpContext.Request.Headers + .Any (h => h.Key + .ToNormalString () == XAuthorization.XPoweredBy.ToNormalString () && + h.Value == config.XPoweredValue); + + // + var isAnonymous = context.IsAnonymousAllowed (); + var handler = ignoreAnonymous ? isAnonymous ? false : true : true; + + // + if (handler && + !isExistsXPowered) { + context.Result = new UnauthorizedResult (); + } + } + } +} \ No newline at end of file diff --git a/Authorization/RequiredRolesHandler.cs b/Authorization/RequiredRolesHandler.cs new file mode 100644 index 0000000..09ac49e --- /dev/null +++ b/Authorization/RequiredRolesHandler.cs @@ -0,0 +1,31 @@ +using System.Linq; +using System.Threading.Tasks; +using IdentityModel; +using Microsoft.AspNetCore.Authorization; + +namespace xCommons.Authorization { + public partial class RequiredRolesHandler : AuthorizationHandler { + protected override Task HandleRequirementAsync ( + AuthorizationHandlerContext context, + RequiredRolesRequirement requirement + ) { + // + var conatinsRole = context.User.HasClaim (c => c.Type == JwtClaimTypes.Role); + if (!conatinsRole) { + return Task.CompletedTask; + } + + // + var roleClaim = context.User.Claims.FirstOrDefault (c => c.Type == JwtClaimTypes.Role); + if (roleClaim == null || + !requirement.Roles.Contains (roleClaim.Value) + ) { + return Task.CompletedTask; + } + + // + context.Succeed (requirement); + return Task.CompletedTask; + } + } +} \ No newline at end of file diff --git a/Authorization/RequiredRolesRequirement.cs b/Authorization/RequiredRolesRequirement.cs new file mode 100644 index 0000000..b26b144 --- /dev/null +++ b/Authorization/RequiredRolesRequirement.cs @@ -0,0 +1,10 @@ +using Microsoft.AspNetCore.Authorization; + +namespace xCommons.Authorization { + public partial class RequiredRolesRequirement : IAuthorizationRequirement { + public string[] Roles { get; } + public RequiredRolesRequirement (params string[] Roles) { + this.Roles = Roles; + } + } +} \ No newline at end of file diff --git a/Configurations/XAppConfiguration.cs b/Configurations/XAppConfiguration.cs new file mode 100644 index 0000000..b8b4c58 --- /dev/null +++ b/Configurations/XAppConfiguration.cs @@ -0,0 +1,27 @@ +using System.Collections.Generic; + +namespace xCommons.Configurations { + /// + /// represent an xBackend Application + /// + public partial class XAppConfiguration { + public string Name { get; set; } + public string Version { get; set; } + public string XPoweredValue { get; set; } + public IEnumerable AllowedOrigins { get; set; } + public string WelcomeMessage { get; set; } + public string DefaultLanguage { get; set; } + public XMessageResourceTitles MessageResourceTitles { get; set; } + } + + public partial class XMessageResourceTitles { + public string Terms { get; set; } + public string Invite { get; set; } + public string RegistrationApprove { get; set; } + public string Registered { get; set; } + public string VerificationCode { get; set; } + public string ChangePassword { get; set; } + public string PasswordChanged { get; set; } + public string NewDeviceLoggedIn { get; set; } + } +} \ No newline at end of file diff --git a/Configurations/XSwaggerConfiguration.cs b/Configurations/XSwaggerConfiguration.cs new file mode 100644 index 0000000..f5ed5cf --- /dev/null +++ b/Configurations/XSwaggerConfiguration.cs @@ -0,0 +1,22 @@ +namespace xCommons.Configurations { + public partial class XSwaggerConfiguration { + public string Title { get; set; } = ""; + public string Version { get; set; } = "V1.0"; + public string Description { get; set; } = ""; + public string TermsOfServiceUrl { get; set; } = ""; + + public XSwaggerContact Contact { get; set; } = null; + public XSwaggerLicence License { get; set; } = null; + } + + public partial class XSwaggerContact { + public string Name { get; set; } + public string Email { get; set; } + public string Url { get; set; } + } + + public partial class XSwaggerLicence { + public string Name { get; set; } + public string Url { get; set; } + } +} \ No newline at end of file diff --git a/Constants/CommonConstants.cs b/Constants/CommonConstants.cs new file mode 100644 index 0000000..8f9280a --- /dev/null +++ b/Constants/CommonConstants.cs @@ -0,0 +1,5 @@ +namespace xCommons.Constants { + public partial class CommonConstants { + public static char DEFAULT_LIST_SEPERATOR = ','; + } +} \ No newline at end of file diff --git a/Constants/ConfigurationNodeNames.cs b/Constants/ConfigurationNodeNames.cs new file mode 100644 index 0000000..6aaa330 --- /dev/null +++ b/Constants/ConfigurationNodeNames.cs @@ -0,0 +1,7 @@ +namespace xCommons.Constants { + public partial struct ConfigurationNodeNames { + public const string CERTIFICATES_NODE = "Certificates"; + public const string SWAGGER_NODE = "SwaggerConfiguration"; + public const string MESSAGE_RESOURCE_TITLES_NODE = "MessageResourceTitles"; + } +} \ No newline at end of file diff --git a/Constants/XAuthentication.cs b/Constants/XAuthentication.cs new file mode 100644 index 0000000..9fff644 --- /dev/null +++ b/Constants/XAuthentication.cs @@ -0,0 +1,9 @@ +namespace xCommons.Constants { + public partial struct XAuthentication { + public const string JWT = "jwt"; + public const string BEARER = "Bearer"; + public const string TOKEN = "token"; + public const string INTROSPECTION = "introspection"; + public const string IDENTITY_SERVER_LOCAL_API = "IdentityServerAccessToken"; + } +} \ No newline at end of file diff --git a/Constants/XAuthorization.cs b/Constants/XAuthorization.cs new file mode 100644 index 0000000..e064168 --- /dev/null +++ b/Constants/XAuthorization.cs @@ -0,0 +1,13 @@ +namespace xCommons.Constants { + public partial struct XAuthorization { + public const string TokenIdentifier = "Bearer "; + public const string Header = "Authorization"; + public const string AccessToken = "access_token"; + public const string RefreshToken = "refresh_token"; + public const string ExpiresAt = "expires_at"; + public const string AccessTokenExpired = "AccessToken-Expired"; + public const string RevisionChecksum = "revision_checksum"; + public const string XPoweredBy = "X-PoweredBy"; + public const int ThresholdBeforeTokenExpiration = 600; + } +} \ No newline at end of file diff --git a/Constants/XCustomClaims.cs b/Constants/XCustomClaims.cs new file mode 100644 index 0000000..418a0b3 --- /dev/null +++ b/Constants/XCustomClaims.cs @@ -0,0 +1,24 @@ +namespace xCommons.Constants { + public partial struct XCustomClaims { + public const string IsEnabled = "is_enabled"; + public const string IsBanned = "is_banned"; + public const string Issuer = "iss"; + public const string ExpiredOn = "exp"; + public const string Audience = "aud"; + public const string ClientId = "client_id"; + public const string AuthenticatedOn = "auth_time"; + public const string UserId = "sub"; + public const string UserName = "unique_name"; + public const string FirstName = "given_name"; + public const string LastName = "family_name"; + public const string Gender = "gender"; + public const string Picture = "picture"; + public const string BrithDate = "birthdate"; + public const string Email = "email"; + public const string EmailVerified = "email_verified"; + public const string PhoneNumber = "phone_number"; + public const string PhoneNumberVerified = "phone_number_verified"; + public const string Role = "role"; + public const string Scope = "scope"; + } +} \ No newline at end of file diff --git a/Constants/XDeviceType.cs b/Constants/XDeviceType.cs new file mode 100644 index 0000000..e1a4a40 --- /dev/null +++ b/Constants/XDeviceType.cs @@ -0,0 +1,11 @@ +namespace xCommons.Constants { + /// + /// a collection of available Client Devices + /// + public enum XDeviceType { + Unknown, + Mobile, + Tablet, + Desktop + } +} \ No newline at end of file diff --git a/Constants/XFileType.cs b/Constants/XFileType.cs new file mode 100644 index 0000000..db33494 --- /dev/null +++ b/Constants/XFileType.cs @@ -0,0 +1,10 @@ +namespace xCommons.Constants { + public enum XFileType { + Image, + ProfileImasge, + CoverImage, + Audio, + Video, + Document + } +} \ No newline at end of file diff --git a/Constants/XPolicy.cs b/Constants/XPolicy.cs new file mode 100644 index 0000000..8a036d4 --- /dev/null +++ b/Constants/XPolicy.cs @@ -0,0 +1,5 @@ +namespace xCommons.Constants { + public partial struct XPolicy { + public const string AllowedOrigins = "AllowedOrigins"; + } +} \ No newline at end of file diff --git a/Controllers/XBaseController.cs b/Controllers/XBaseController.cs new file mode 100644 index 0000000..3d7d8eb --- /dev/null +++ b/Controllers/XBaseController.cs @@ -0,0 +1,139 @@ +using System; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using xCommons.Attributes; +using xCommons.Configurations; +using xCommons.Extensions; +using xCommons.Providers; +using xExceptions.Constants; + +namespace xCommons.Controllers { + /// + /// Base Controller + /// + [Route ("[controller]")] + [Produces ("application/json")] + public abstract partial class XBaseController : ControllerBase { + public ILogger Logger { get; } + public XAppConfiguration AppConfiguration { get; } + public XValidationProvider ValidationProvider { get; } + + public XBaseController ( + ILogger logger, + XAppConfiguration appConfiguration, + XValidationProvider validationProvider + ) { + Logger = logger; + AppConfiguration = appConfiguration; + ValidationProvider = validationProvider; + } + + /// + /// Check the Controller Up and Running + /// + /// string message + [HttpGet] + [Route ("Test/Hi")] + [AllowAnonymous] + public virtual IActionResult Hi () { + // + var controllerName = GetControllerName (); + var message = $"{controllerName} Controller is Up and running ..."; + + // + return Ok (message); + } + + /// + /// Check Controller ByPass XPowered Filter + /// + /// string message + [HttpGet] + [AllowAnonymous] + [RequireXPowered (false)] + [Route ("Test/PassRequireXPowered")] + public virtual IActionResult PassRequireXPowered () { + // + var controllerName = GetControllerName (); + var message = $"{controllerName} Controller bypass RequiredXPowered and Up and running ..."; + + // + return Ok (message); + } + + // + #region Non Actions ... + /// + /// Convert an Exception to Propper Error Result + /// + /// + /// + [NonAction] + public ActionResult GetExceptionActionResult (Exception ex) { + // + var exception = GetExceptionResult (ex); + var error = ex.Message.ToXError (); + + // + Logger.LogError ($"exception: {exception}, error: {error}"); + + // + try { + var xError = exception.Message.ToXError (); + var xException = (XException) xError.Id; + + // + switch (xException) { + // + case XException.NotFound: + return NotFound (error); + + // + case XException.NotAuthorized: + return Unauthorized (); + } + } catch { } + + // + return BadRequest (error); + } + + /// + /// Return Structured Exception + /// + /// + /// + [NonAction] + public Exception GetExceptionResult (Exception ex) { + // + // Log Exception Message ... + Logger.LogError (ex.Message); + + // + // if Happens Exception is Know Error + // catch it and return it in BadRequest Container ... + var isEr = ex.Message.IsXError (); + + // + if (isEr) { + return ex; + } + + // + // if catched Exception Contains Unknown Error + // return an Unknown Error ... + return XException.Unknown.ToException (); + } + + /// + /// Retrieve ControllerName + /// + /// + [NonAction] + public string GetControllerName () { + return this.ControllerContext.RouteData.Values["controller"].ToString (); + } + #endregion + } +} \ No newline at end of file diff --git a/DI/XDIHelperExtension.cs b/DI/XDIHelperExtension.cs new file mode 100644 index 0000000..ea61bd0 --- /dev/null +++ b/DI/XDIHelperExtension.cs @@ -0,0 +1,12 @@ +using Microsoft.Extensions.DependencyInjection; +using xCommons.Providers; + +public static partial class XDIHelperExtension { + /// + /// Register XCommons Module Services + /// + /// + public static void AddXCommons (this IServiceCollection services) { + services.AddSingleton (); + } +} \ No newline at end of file diff --git a/Extensions/CertificateExtensions.cs b/Extensions/CertificateExtensions.cs new file mode 100644 index 0000000..e8e9287 --- /dev/null +++ b/Extensions/CertificateExtensions.cs @@ -0,0 +1,23 @@ +using Microsoft.Extensions.Configuration; +using xCommons.Constants; +using xCommons.Models; + +namespace xCommons.Extensions { + public static partial class CertificateExtensions { + /// + /// Retrieve Specific Certificate From app Settings + /// + /// + /// + /// + public static XCertificate GetCertificate (this IConfiguration configuration, string name) { + // + var certificatesSection = configuration.GetSection (ConfigurationNodeNames.CERTIFICATES_NODE); + var certificateSection = certificatesSection.GetSection (name); + var certificate = certificateSection.Get (); + + // + return certificate; + } + } +} \ No newline at end of file diff --git a/Extensions/CommonExtensions.cs b/Extensions/CommonExtensions.cs new file mode 100644 index 0000000..807e131 --- /dev/null +++ b/Extensions/CommonExtensions.cs @@ -0,0 +1,1282 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.Serialization; +using System.Security.Cryptography; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using System.Web; +using System.Xml; +using AutoMapper; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Newtonsoft.Json.Serialization; +using xCommons.Constants; +using xCommons.Helpers; +using xExceptions.Attributes; +using xExceptions.Constants; + +namespace xCommons.Extensions { + public static partial class CommonExtensions { + /// + /// Conert XMLNode to String ... + /// + /// + /// + /// + public static string ToString (this XmlNode node, int indentation = 2) { + // + using (var sw = new StringWriter ()) { + // + using (var xw = new XmlTextWriter (sw)) { + xw.Formatting = System.Xml.Formatting.Indented; + xw.Indentation = indentation; + node.WriteContentTo (xw); + } + + // + return sw.ToString (); + } + } + + /// + /// Converts a Json String to Dictionary of String Mappings + /// + /// Json String + /// + public static Dictionary JSONToDictionary (this string source) { + // + try { + return JsonConvert.DeserializeObject> (source); + } catch { + return null; + } + } + + /// + /// Convert properties of specific Class instance to Dictionary ... + /// + /// class instance + /// + public static Dictionary ToDictionary (this T source) where T : class { + // + var result = new Dictionary (); + + // + if (source.IsNull ()) { + return result; + } + + // + PropertyInfo[] propertyInfos = source.GetType ().GetProperties (); + if (!propertyInfos.HasChild ()) { + return result; + } + + // + foreach (PropertyInfo propertyInfo in propertyInfos) { + result.Add ( + propertyInfo.Name, + propertyInfo + .GetValue (source, null) + .ToString () + ); + } + + // + return result; + } + + /// + /// run specific task and retrieve result ... + /// + /// + public static void RunTask (this Task source) { + source. + GetAwaiter () + .GetResult (); + } + + /// + /// run specific task and retrieve result ... + /// + /// + public static T RunTask (this Task source) { + return source. + GetAwaiter () + .GetResult (); + } + + /// + /// Convert XML String to Json Object ... + /// + /// + /// + public static string ToJSON (this string source) { + // + var result = string.Empty; + try { + // + result = XMLToJSONHelper.ToJSON (source); + + // + // Handle Removing Additional Values ... + result = result.Replace ("s:", "").Replace ("a:", ""); + } catch { } + + // + return result; + } + + /// + /// Convert object to Json ... + /// + /// + /// + public static string ToJSON (this T value) + where T : class { + // + if (value.IsNull ()) { + return string.Empty; + } + + // + var setting = new JsonSerializerSettings { + ReferenceLoopHandling = ReferenceLoopHandling.Ignore, + PreserveReferencesHandling = PreserveReferencesHandling.None + }; + + // + return JsonConvert.SerializeObject (value, Newtonsoft.Json.Formatting.None, setting); + } + + /// + /// Convert to Json With Support of Camel Casing + /// + /// + /// + /// + public static string ToJSON (this T value, bool camelCase = false) { + // + if (value.IsNull ()) { + return string.Empty; + } + + // + var setting = new JsonSerializerSettings { + ReferenceLoopHandling = ReferenceLoopHandling.Ignore, + PreserveReferencesHandling = PreserveReferencesHandling.None, + }; + + // + if (camelCase) { + setting.ContractResolver = new CamelCasePropertyNamesContractResolver (); + } + + // + return JsonConvert.SerializeObject (value, Newtonsoft.Json.Formatting.None, setting); + } + + /// + /// Map to Dynamic Object ... + /// + /// + /// + public static dynamic ToDynamicObject (this string source) { + return JsonConvert.DeserializeObject (source); + } + + /// + /// Map to Dynamic Object ... + /// + /// + /// + public static dynamic ToDynamicObject (this object source) { + // + var dMapConfig = new MapperConfiguration (b => { }); + var dMapper = dMapConfig.CreateMapper (); + + // + return dMapper.Map (source); + } + + /// + /// Parse From Dynamic Object ... + /// + /// + /// + /// + public static T FromDynamicObject (this object source) + where T : class { + // + var dMapConfig = new MapperConfiguration (b => { }); + var dMapper = dMapConfig.CreateMapper (); + dynamic dSource = source; + + // + return dMapper.Map (dSource); + } + + /// + /// Convert Type of Object to Safe Type + /// + /// + /// + /// + public static T ConvertTo (this object source) { + // + T result = default (T); + + // + try { + // + T item = (T) Convert.ChangeType (source, typeof (T)); + + // + result = item; + } catch { } + + // + return result; + } + + /// + /// Convert an Instance to another Instance + /// using AutoMapper dynamic Convertion + /// + /// + /// + /// + public static T MapConvert (this T source) + where T : class { + // + if (source.IsNull ()) { + return null; + } + + // + var dSource = (object) source.ToDynamicObject (); + return dSource.FromDynamicObject (); + } + + /// + /// Convert Using Auto Mapper ... + /// + /// + /// + /// + /// + public static T MapConvert (this E source) + where T : class + where E : class { + // + if (source.IsNull ()) { + return null; + } + + // + var dMapConfig = new MapperConfiguration (b => { + b.CreateMap () + .ReverseMap (); + }); + var dMapper = dMapConfig.CreateMapper (); + + // + var dSource = (object) dMapper.Map (source); + var result = dMapper.Map (dSource); + + // + return result; + } + + /// + /// Convert Using Auto Mapper ... + /// + /// + /// + /// + /// + public static IEnumerable MapConvert (this IEnumerable source) { + // + if (source.IsNull ()) { + return null; + } + + // + var dMapConfig = new MapperConfiguration (b => { + b.CreateMap () + .ReverseMap (); + }); + var dMapper = dMapConfig.CreateMapper (); + + // + var dSource = (object) dMapper.Map (source); + var result = dMapper.Map> (dSource); + + // + return result; + } + + /// + /// Convert Using Auto Mapper ... + /// + /// + /// + /// + /// + public static ICollection MapConvert (this ICollection source) { + // + if (source.IsNull ()) { + return null; + } + + // + var dMapConfig = new MapperConfiguration (b => { + b.CreateMap () + .ReverseMap (); + }); + var dMapper = dMapConfig.CreateMapper (); + + // + var dSource = (object) dMapper.Map (source); + var result = dMapper.Map> (dSource); + + // + return result; + } + + /// + /// Convert Using Auto Mapper ... + /// + /// + /// + /// + /// + public static IList MapConvert (this IList source) { + // + if (source.IsNull ()) { + return null; + } + + // + var dMapConfig = new MapperConfiguration (b => { + b.CreateMap () + .ReverseMap (); + }); + var dMapper = dMapConfig.CreateMapper (); + + // + var dSource = (object) dMapper.Map (source); + var result = dMapper.Map> (dSource); + + // + return result; + } + + /// + /// Convert Using Auto Mapper ... + /// + /// + /// + /// + /// + /// + public static IDictionary MapConvert (this IDictionary source) { + // + if (source.IsNull ()) { + return null; + } + + // + var dMapConfig = new MapperConfiguration (b => { + b.CreateMap () + .ReverseMap (); + }); + var dMapper = dMapConfig.CreateMapper (); + + // + var dSource = (object) dMapper.Map (source); + var result = dMapper.Map> (dSource); + + // + return result; + } + + /// + /// Convert a Type to Static Type + /// + /// + /// + /// + public static T ToStatic (this object source) { + // + var entity = Activator.CreateInstance (); + + // + var properties = source as IDictionary; + + // + if (properties == null) { + return entity; + } + + // + foreach (var entry in properties) { + // + var propertyInfo = entity.GetType ().GetProperty (entry.Key); + if (propertyInfo != null) { + propertyInfo.SetValue (entity, entry.Value, null); + } + } + + // + return entity; + } + + /// + /// Convert From Json String ... + /// + /// + /// + /// + public static T FromJSON (this string value) { + return JsonConvert.DeserializeObject (value); + } + + /// + /// Convert From Json String ... + /// + /// + /// + /// + public static object FromJSON ( + this string value, + Type type + ) { + return JsonConvert + .DeserializeObject (value, type); + } + + /// + /// Search a Json String for Specific Node ... + /// + /// + /// + /// + public static List FindTokens (this JToken containerToken, string name) { + List matches = new List (); + FindTokens (containerToken, name, matches); + return matches; + } + + /// + /// Convert to MD5 Bytes ... + /// + /// + /// + public static byte[] ToMd5Bytes (this string source) { + return new MD5CryptoServiceProvider ().ComputeHash (Encoding.UTF8.GetBytes (source)); + } + + /// + /// Convert to MD5 String ... + /// + /// + /// + public static string ToMd5String (this string source) { + // + // string representation (similar to UNIX format) + return BitConverter.ToString (source.ToMd5Bytes ()) + // without dashes + .Replace ("-", string.Empty) + // make lowercase + .ToLower (); + } + + /// + /// Check a String is MD5 String or not ... + /// + /// + /// + public static bool IsMD5String (this string source) { + // + if (String.IsNullOrEmpty (source)) { + return false; + } + + // + return Regex.IsMatch (source, "^[0-9a-fA-F]{32}$", RegexOptions.Compiled); + } + + /// + /// Convert an string to Byte array Representation ... + /// + /// + /// + public static byte[] ToBytes (this string source) { + return Encoding.UTF8.GetBytes (source); + } + + /// + /// Check object is Null ... + /// + /// + /// + public static bool IsNull (this object source) { + return source == null; + } + + /// + /// Check collection Has Childs ... + /// + /// + /// + /// + public static bool HasChild (this IList source) { + return !source.IsNull () && source.Count () > 0; + } + + /// + /// Check collection Has Childs ... + /// + /// + /// + /// + public static bool HasChild (this ICollection source) { + return !source.IsNull () && source.Count () > 0; + } + + /// + /// Check collection Has Childs ... + /// + /// + /// + /// + public static bool HasChild (this IEnumerable source) { + return !source.IsNull () && source.Count () > 0; + } + + /// + /// Update a Collection item ... + /// + /// + /// + /// + /// + /// + public static IList Update ( + this IList source, + T sourceItem, + T updateWith + ) { + // + var result = new List (); + + // + // Validate Args ... + if (!source.HasChild () || + updateWith.IsNull () || + sourceItem.IsNull () + ) { + return result; + } + + // + // Extract Item from source ... + result = source + .Except (new List () { sourceItem }) + .ToList (); + result.Add (updateWith); + + // + return source; + } + + /// + /// Update a Collection item ... + /// + /// + /// + /// + /// + /// + public static ICollection Update ( + this ICollection source, + T sourceItem, + T updateWith + ) { + // + var result = new List (); + + // + // Validate Args ... + if (!source.HasChild () || + updateWith.IsNull () || + sourceItem.IsNull () + ) { + return result; + } + + // + // Extract Item from source ... + result = source + .Except (new List () { sourceItem }) + .ToList (); + result.Add (updateWith); + + // + return source; + } + + /// + /// Update a Collection item ... + /// + /// + /// + /// + /// + /// + public static IEnumerable Update ( + this IEnumerable source, + T sourceItem, + T updateWith + ) { + // + var result = new List (); + + // + // Validate Args ... + if (!source.HasChild () || + updateWith.IsNull () || + sourceItem.IsNull () + ) { + return result; + } + + // + // Extract Item from source ... + result = source + .Except (new List () { sourceItem }) + .ToList (); + result.Add (updateWith); + + // + return source; + } + + /// + /// Select and Retrieve Nth Childs of given List ... + /// + /// + /// + /// + /// + /// + public static IEnumerable GetNthItems (this IList source, int n, bool throwException = false) { + // + var result = new List (); + if (n < 1 || n > source.Count || source.IsNull () || !source.HasChild ()) { + if (throwException) { + throw XException.InvalidArgs.ToException (); + } else { + return result; + } + } + + // + var index = 0; + foreach (var item in source) { + // + result.Add (item); + index++; + + // + if (index >= n) { + break; + } + } + + // + return result; + } + + /// + /// Select and Retrieve Nth Childs of given List ... + /// + /// + /// + /// + /// + /// + public static IEnumerable GetNthItems (this ICollection source, int n, bool throwException = false) { + return source.ToList ().GetNthItems ( + n: n, + throwException: throwException + ); + } + + /// + /// Select and Retrieve Nth Childs of given List ... + /// + /// + /// + /// + /// + /// + public static IEnumerable GetNthItems (this IEnumerable source, int n, bool throwException = false) { + return source.ToList ().GetNthItems ( + n: n, + throwException: throwException + ); + } + + /// + /// Check an string is Null or Empty ... + /// + /// + /// + public static bool IsNullOrEmpty (this string sourse) { + return string.IsNullOrEmpty (sourse); + } + + /// + /// Get string Value Attribute of an Enum ... + /// + /// + /// + public static string GetStringValue (this Enum source) { + var type = source.GetType (); + var fieldInfo = type.GetField (source.ToString ()); + var attributes = fieldInfo.GetCustomAttributes (typeof (StringValueAttribute), false) as StringValueAttribute[]; + + return attributes != null && attributes.Length > 0 ? + attributes[0].StringValue : null; + } + + /// + /// Extracts digit's of an string ... + /// + /// + /// + public static string GetDigits (this string source) { + try { + // + Regex regexObj = new Regex (@"[^\d]"); + return regexObj.Replace (source, ""); + } catch { + return null; + } + } + + /// + /// Check an string is contains only digits ... + /// + /// + /// + public static bool IsDigits (this string source) { + return (source.GetDigits () != null && + source.GetDigits ().ToString () == source); + } + + /// + /// Convert to normal string, Trim and ToLower ... + /// + /// + /// + public static string ToNormalString (this string source) { + // + if (source.IsNullOrEmpty ()) { + return string.Empty; + } + + // + return source.Trim ().ToLowerInvariant (); + } + + /// + /// Capitalize an string ... + /// + /// + /// + public static string Capitalize (this string source) { + // + if (source.IsNullOrEmpty ()) { + return string.Empty; + } + + // + var result = ""; + if (source.Length > 1) { + // + var firstCharr = source.Substring (0, 1); + var otherParts = source.Substring (1, source.Length - 1); + + // + result = firstCharr.ToUpperInvariant () + otherParts; + } else { + result = source.ToUpperInvariant (); + } + + // + return result; + } + + /// + /// Check an string is a Valid Representation of PhoneNumber ... + /// + /// + /// + public static bool IsValidMobileNumber (this string source) { + // + // Validating Mobile Numbers ... + var phoneNumberUtil = PhoneNumbers.PhoneNumberUtil.GetInstance (); + try { + // + var mPhoneNumber = phoneNumberUtil.Parse (source, null); + if (phoneNumberUtil.IsValidNumber (mPhoneNumber)) { + // + return true; + } + + // + return false; + } catch { + + // + return false; + } + } + + /// + /// Check an string is Valid Representation of Email Address ... + /// + /// + /// + public static bool IsValidEmail (this string source) { + try { + var addr = new System.Net.Mail.MailAddress (source); + return addr.Address == source; + } catch { + return false; + } + } + + /// + /// Check an string is Valid Representation of URL Address ... + /// + /// + /// + public static bool IsValidUrl (this string url) { + return Uri.IsWellFormedUriString (url, UriKind.RelativeOrAbsolute); + } + + /// + /// check a URL has / at the end, if not add one ... + /// + /// + /// + public static string NormalizeUrl (this string url) { + return url.EndsWith ("/") ? url : url + "/"; + } + + /// + /// Check an string is GUID or not ... + /// + /// + /// + public static bool IsGuid (this string source) { + // + // Validate Args ... + if (source.IsNullOrEmpty ()) { + return false; + } + + // + Guid guid; + var result = Guid.TryParse (source, out guid); + + // + return result; + } + + /// + /// Check Specific Guid string is Default Guid or not ... + /// + /// + /// + public static bool IsDefaultGuid (this string guid) { + // + if (!guid.IsGuid ()) { + return false; + } + + // + var empty = Guid.Empty.ToString (); + return guid == empty; + } + + /// + /// Check Specific Guid is Default Guid or not ... + /// + /// + /// + public static bool IsDefaultGuid (this Guid guid) { + // + var empty = Guid.Empty; + return guid == empty; + } + + /// + /// Parse an String to related Enumerable Items ... + /// + /// + /// + /// + /// + public static IEnumerable ParseListString ( + this string source, + char? separator = null + ) { + // + if (!separator.HasValue) { + separator = CommonConstants.DEFAULT_LIST_SEPERATOR; + } + + // + var list = source.Split (separator.Value); + var result = new Collection (); + + // + foreach (var s in list) { + // + try { + // + T item = (T) Convert.ChangeType (s, typeof (T)); + + // + result.Add (item); + } catch { } + } + + // + return result; + } + + /// + /// Convert a List to List String ... + /// + /// + /// + /// + /// + public static string ToListString ( + this IEnumerable source, + char? separator = null + ) { + // + if (!separator.HasValue) { + separator = CommonConstants.DEFAULT_LIST_SEPERATOR; + } + + // + if (!source.HasChild ()) { + return string.Empty; + } + + // + string result = String.Join ( + separator + .ToString (), + source + ); + + // + return result; + } + + /// + /// get default value of a Type + /// + /// + /// + /// + public static T Default (this T source) + where T : class { + return default (T); + } + + /// + /// Check if an object is Null or Default Value + /// + /// + /// + /// + public static bool IsNullOrDefault (this T source) + where T : class { + return Equals (source, default (T)); + } + + /// + /// convert a timestamp to ISO String representation ... + /// + /// + /// + public static string ISODateString (this long timestamp) { + // + var time = timestamp.FromTimestamp (); + var result = time.ToString ("o"); + + // + return result; + } + + /// + /// convert timestamp to DateTime ... + /// + /// + /// + public static DateTime FromTimestamp (this long timestamp) { + // + var result = DateTimeOffset.FromUnixTimeMilliseconds (timestamp); + return result.DateTime; + } + + /// + /// Check a Date Time is Expired or not + /// + /// + /// + public static bool IsExPired (this DateTime source) { + // + if (source == null) { + return true; + } + + // + var currentDate = DateTime.UtcNow; + var isExpired = currentDate >= source; + + // + return isExpired; + } + + /// + /// Determines a File Type is Image Kind or not + /// + /// + /// + public static bool IsImageKind (this XFileType source) { + // + var imageKind = new Collection () { + XFileType.Image, + XFileType.CoverImage, + XFileType.ProfileImasge + }; + + // + return imageKind.Contains (source); + } + + /// + /// Encode String as Url + /// + /// + /// + public static string ToUrlEncoded (this string source) { + return HttpUtility.UrlEncode (source); + } + + /// + /// Decode Url String + /// + /// + /// + public static string ToUrlDecoded (this string source) { + return HttpUtility.UrlDecode (source); + } + + /// + /// Convert an Object to string for use in HttpParams + /// + /// + /// + /// + public static string ToHttpParamString (this object value, CultureInfo cultureInfo) { + // + if (value is Enum) { + // + string name = Enum.GetName (value.GetType (), value); + if (name != null) { + // + var field = IntrospectionExtensions + .GetTypeInfo (value.GetType ()) + .GetDeclaredField (name); + + // + if (field != null) { + // + var attribute = CustomAttributeExtensions + .GetCustomAttribute (field, typeof (EnumMemberAttribute)) + as EnumMemberAttribute; + + // + if (attribute != null) { + return attribute.Value != null ? attribute.Value : name; + } + } + } + } else if (value is bool) { + return System.Convert.ToString (value, cultureInfo).ToLowerInvariant (); + } else if (value is byte[]) { + return System.Convert.ToBase64String ((byte[]) value); + } else if (value != null && value.GetType ().IsArray) { + // + var array = Enumerable.OfType ((Array) value); + return string.Join (",", Enumerable + .Select ( + array, + o => o.ToHttpParamString (cultureInfo))); + } + + // + return System.Convert.ToString (value, cultureInfo); + } + + /// + /// ReShape an Object as HttpParam String + /// + /// + /// + public static string AsHttpParamString (this object value) { + return System.Uri + .EscapeDataString (value + .ToHttpParamString (CultureInfo.InvariantCulture)); + } + + /// + /// Calculating Date Difference Provider String + /// and Generate Date based on UTC Time in Milliseconds like unix Date ... + /// + /// + /// + public static long GenerateDateTimeOffset (this string offsetProvider) { + // + if (offsetProvider.IsNullOrEmpty ()) { + return 0; + } + + // + var mExpireValue = 0; + var mExpireValueStr = offsetProvider.GetDigits (); + var mExpireUnit = offsetProvider.Replace (mExpireValueStr, ""); + if (mExpireValueStr.IsNullOrEmpty () || mExpireUnit.IsNullOrEmpty ()) { + return 0; + } + + // + var isParsed = int.TryParse (mExpireValueStr, out mExpireValue); + if (!isParsed) { + return 0; + } + + // + DateTime? date = null; + switch (mExpireUnit) { + case "d": + date = DateTime.UtcNow.AddDays (mExpireValue); + break; + + case "h": + date = DateTime.UtcNow.AddHours (mExpireValue); + break; + + case "m": + date = DateTime.UtcNow.AddMinutes (mExpireValue); + break; + + case "s": + date = DateTime.UtcNow.AddSeconds (mExpireValue); + break; + + case "ms": + default: + date = DateTime.UtcNow.AddMilliseconds (mExpireValue); + break; + } + + // + var result = !date.HasValue ? 0 : new DateTimeOffset (date.Value) + .ToUnixTimeMilliseconds (); + + // + return result; + } + + // + #region Int Id Extensions ... + public static bool IsValidIntId (this int source) { + return source > 0; + } + + public static void ValidateIntId (this int source) { + // + if (!source.IsValidIntId ()) { + XException.InvalidArgs.Throw (); + } + } + #endregion + + // + #region StringExtensions for Identity Server ... + public static string EnsureTrailingSlash (this string input) { + if (!input.EndsWith ("/")) { + return input + "/"; + } + + return input; + } + + public static bool IsMissing (this string value) { + return string.IsNullOrWhiteSpace (value); + } + + public static bool IsPresent (this string value) { + return !string.IsNullOrWhiteSpace (value); + } + + public static string Sha256 (this string input) { + if (input.IsMissing ()) return string.Empty; + + using (var sha = SHA256.Create ()) { + var bytes = Encoding.UTF8.GetBytes (input); + var hash = sha.ComputeHash (bytes); + + return Convert.ToBase64String (hash); + } + } + #endregion + + // + #region Private ... + private static void FindTokens (JToken containerToken, string name, List matches) { + if (containerToken.Type == JTokenType.Object) { + foreach (JProperty child in containerToken.Children ()) { + if (child.Name == name) { + matches.Add (child.Value); + } + FindTokens (child.Value, name, matches); + } + } else if (containerToken.Type == JTokenType.Array) { + foreach (JToken child in containerToken.Children ()) { + FindTokens (child, name, matches); + } + } + } + #endregion + } +} \ No newline at end of file diff --git a/Extensions/DIExtensions.cs b/Extensions/DIExtensions.cs new file mode 100644 index 0000000..ab802b8 --- /dev/null +++ b/Extensions/DIExtensions.cs @@ -0,0 +1,319 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.OpenApi.Models; +using Swashbuckle.AspNetCore.SwaggerGen; +using Swashbuckle.AspNetCore.SwaggerUI; +using xCommons.Configurations; +using xCommons.Constants; +using xCommons.Filters; + +namespace xCommons.Extensions { + public static partial class DIExtensions { + + /// + /// Inject Specific Registered Service from IServiceCollection + /// + /// + /// + /// + public static T GetRegisteredService (this IServiceCollection source) { + // + var serviceProvider = source.BuildServiceProvider (); + return serviceProvider.GetService (); + } + + /// + /// Register App Configuration as a Service + /// + /// + /// + public static void AddXAppConfiguration (this IServiceCollection services, IConfiguration configuration) { + // + var appConfiguration = configuration.GetXAppConfiguration (); + services.AddSingleton (appConfiguration); + } + + /// + /// Add Required OperationFilters and Authorization Fields to Swagger Options + /// + /// + /// + /// + /// + public static void AddXSwaggerGenOptions ( + this SwaggerGenOptions source, + string xmlFilePath = null, + bool addRequiredXPoweredFilter = true, + bool addXTokenAuthorization = true + ) { + // + if (source.IsNull ()) { + source = new SwaggerGenOptions (); + } + + // + // Add Xml File Path ... + if (!xmlFilePath.IsNullOrEmpty ()) { + source.IncludeXmlComments (xmlFilePath); + } + + // + // Handle RequireXPowered Filter ... + if (addRequiredXPoweredFilter) { + source.OperationFilter (); + } + + // + // Handle XToken Authorizations ... + if (addXTokenAuthorization) { + // + #region Bearer AccessToken ... + // + source.AddSecurityDefinition (nameof (XAuthorization.AccessToken), new OpenApiSecurityScheme { + In = ParameterLocation.Header, + Description = "Bearer token", + Type = SecuritySchemeType.ApiKey, + Name = XAuthorization.Header + }); + + // + source.AddSecurityRequirement (new OpenApiSecurityRequirement { + { + new OpenApiSecurityScheme { + Reference = new OpenApiReference { + Id = nameof (XAuthorization.AccessToken), + Type = ReferenceType.SecurityScheme + } + }, Array.Empty () + } + }); + #endregion + + // + #region RefreshToken ... + // + source.AddSecurityDefinition (nameof (XAuthorization.RefreshToken), new OpenApiSecurityScheme { + In = ParameterLocation.Header, + Description = "refresh token", + Type = SecuritySchemeType.ApiKey, + Name = nameof (XAuthorization.RefreshToken) + }); + + // + source.AddSecurityRequirement (new OpenApiSecurityRequirement { + { + new OpenApiSecurityScheme { + Reference = new OpenApiReference { + Id = nameof (XAuthorization.RefreshToken), + Type = ReferenceType.SecurityScheme + } + }, Array.Empty () + } + }); + #endregion + + // + #region ExpiresAt ... + // + source.AddSecurityDefinition (nameof (XAuthorization.ExpiresAt), new OpenApiSecurityScheme { + In = ParameterLocation.Header, + Description = "expires at", + Type = SecuritySchemeType.ApiKey, + Name = nameof (XAuthorization.ExpiresAt) + }); + + // + source.AddSecurityRequirement (new OpenApiSecurityRequirement { + { + new OpenApiSecurityScheme { + Reference = new OpenApiReference { + Id = nameof (XAuthorization.ExpiresAt), + Type = ReferenceType.SecurityScheme + } + }, Array.Empty () + } + }); + #endregion + } + } + + /// + /// Register Swagger + /// + /// + /// + /// + /// + /// + /// + public static void AddXSwagger ( + this IServiceCollection source, + IConfiguration configuration, + SwaggerGenOptions settings = null, + string xmlFilePath = null, + bool addRequiredXPoweredFilter = true, + bool addXTokenAuthorization = true + ) { + // + var xSwaggerConfig = configuration.GetXSwaggerConfiguration (); + if (xSwaggerConfig.IsNull ()) { + xSwaggerConfig = new XSwaggerConfiguration (); + } + + // + #region Prepare SwaggerGenOptions ... + if (settings.IsNull ()) { + settings = new SwaggerGenOptions (); + } + settings.AddXSwaggerGenOptions ( + xmlFilePath: xmlFilePath, + addRequiredXPoweredFilter: addRequiredXPoweredFilter, + addXTokenAuthorization: addXTokenAuthorization + ); + #endregion + + // + #region Prepare Swagger Doc ... + // + // Api Document Section ... + var apiDoc = new OpenApiInfo { + Title = xSwaggerConfig.Title, + Version = xSwaggerConfig.Version, + Description = xSwaggerConfig.Description, + }; + + // + // Api Document TermsOfUse URL ... + if (!xSwaggerConfig.TermsOfServiceUrl.IsNullOrEmpty ()) { + apiDoc.TermsOfService = new Uri (xSwaggerConfig.TermsOfServiceUrl); + } + + // + // Contact Section ... + if (!xSwaggerConfig.Contact.IsNull ()) { + // + var apiContact = new OpenApiContact { + Name = xSwaggerConfig.Contact.Name, + Email = xSwaggerConfig.Contact.Email + }; + + // + // Contact URL ... + if (!xSwaggerConfig.Contact.Url.IsNullOrEmpty ()) { + apiContact.Url = new Uri (xSwaggerConfig.Contact.Url); + } + + // + apiDoc.Contact = apiContact; + } + + // + // License Section ... + if (!xSwaggerConfig.License.IsNull ()) { + // + var apiLicense = new OpenApiLicense { + Name = xSwaggerConfig.License.Name, + }; + + // + // License URL ... + if (!xSwaggerConfig.License.Url.IsNullOrEmpty ()) { + apiLicense.Url = new Uri (xSwaggerConfig.License.Url); + } + + // + apiDoc.License = apiLicense; + } + #endregion + + // + // Register Swagger ... + source.AddSwaggerGen ( + opt => { + // + opt.UpdateData (settings); + + // + opt.SwaggerDoc ("v1", apiDoc); + } + ); + } + + /// + /// Use Sagger + /// + /// + /// + public static void UseXSwagger ( + this IApplicationBuilder source, + SwaggerUIOptions options = null + ) { + // + source.UseSwagger (); + + // + if (options.IsNull ()) { + source.UseSwaggerUI (); + } else { + source.UseSwaggerUI (options: options); + } + } + + /// + /// Add Cross Origin Resource Sharings + /// + /// + /// + public static void AddXCors ( + this IServiceCollection source, + IEnumerable allowedOrigins + ) { + // + if (!allowedOrigins.HasChild ()) { + // + Console.WriteLine ($"XCommons: there is no provided allowedOrigins, start app without any specific cors, this may be unsecure ..."); + + // + source.AddCors (options => { + options.AddPolicy (XPolicy.AllowedOrigins, + builder => { + builder + .AllowAnyOrigin () + .AllowAnyHeader () + .AllowAnyMethod () + .WithExposedHeaders ("*"); + }); + }); + + // + return; + } + + // + source.AddCors (options => { + options.AddPolicy (XPolicy.AllowedOrigins, + builder => { + builder + .WithOrigins (allowedOrigins.ToArray ()) + .AllowAnyHeader () + .AllowAnyMethod () + .AllowCredentials () + .WithExposedHeaders ("*"); + }); + }); + } + + /// + /// Use X Registered Cross Origin Resource Sharings + /// + /// + public static void UseXCors (this IApplicationBuilder sources) { + // + sources.UseCors (XPolicy.AllowedOrigins); + } + } +} \ No newline at end of file diff --git a/Extensions/EnumExtensions.cs b/Extensions/EnumExtensions.cs new file mode 100644 index 0000000..b408af3 --- /dev/null +++ b/Extensions/EnumExtensions.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; + +namespace xCommons.Extensions { + public static partial class EnumExtensions { + public static string GetKey (this Enum source) { + return Enum.GetName (source.GetType (), source); + } + + public static T GetValue (this string source) { + return (T) Enum.Parse (typeof (T), source); + } + + public static void Iterate (this IEnumerator source, Action action) { + // + while (source.MoveNext ()) { + // + var current = source.Current; + action (current); + } + } + } +} \ No newline at end of file diff --git a/Extensions/ExceptionExtensions.cs b/Extensions/ExceptionExtensions.cs new file mode 100644 index 0000000..d700c35 --- /dev/null +++ b/Extensions/ExceptionExtensions.cs @@ -0,0 +1,170 @@ +using System; +using xExceptions.Constants; +using xExceptions.Models; + +namespace xCommons.Extensions { + /// + /// this is an extension pack for Exceptions + /// + public static partial class ExceptionExtensions { + + /// + /// determines an string contains an XError content or not + /// + /// + /// + public static bool IsXError (this string source) { + // + if (source.IsNullOrEmpty ()) { + return false; + } + + // + var xError = source.ToXError (); + if (xError != null) { + return true; + } + + // + // If Convert Proccess fails ... + var normalString = source.ToNormalString (); + + // + // Return result based on string values ... + return normalString.Contains ("id") && normalString.Contains ("message"); + } + + /// + /// deserialize an string to to XError class instance + /// + /// + /// + public static XError ToXError (this string source) { + // + if (source.IsNullOrEmpty ()) { + return null; + } + + // + try { + // + var err = source.FromJSON (); + + // + // if err is null we have to manually get value + if (err == null) { + // + var normalString = source.ToNormalString (); + var parts = normalString.Split (','); + if (parts.Length > 2) { + return null; + } + + // + var idContainer = parts[0]; + var messageContainer = parts[1]; + } + + // + return err; + } catch { + return null; + } + } + + public static XError ToXError (this XException source) { + // + var errorId = (int) source; + var errorMessage = source.GetStringValue (); + + // + return new XError { + Id = errorId, + Message = errorMessage + }; + } + + /// + /// Convert an XError instance to an Exception + /// + /// + /// + public static Exception ToException (this XError source) { + // + var jsonStr = source.ToJSON (); + return new Exception (jsonStr); + } + + public static Exception ToException (this XException source) { + return source + .ToXError () + .ToException (); + } + + /// + /// Convert an Exception to corresponding XError Object + /// + /// + /// + public static XError FromException (this Exception source) { + // + if (source == null || source.Message.IsNullOrEmpty ()) { + return null; + } + + // + return source.Message.ToXError (); + } + + /// + /// Add Content to Contentable XException member and return XError Object + /// + /// + /// + /// + public static XError AddContentToError (this XException exception, string value) { + // + var xError = exception.ToXError (); + xError.Message = string.Format (xError.Message, value); + + // + return xError; + } + + /// + /// Add Content to Contentable XException member and return Exception Object + /// + /// + /// + /// + public static Exception AddContentToException (this XException exception, string value) { + // + var xError = exception.ToXError (); + xError.Message = string.Format (xError.Message, value); + + // + return xError.ToException (); + } + + /// + /// Throw Specific Exception ... + /// + /// + /// + public static void Throw ( + this XException source, + string content = null + ) { + // + Exception exception = null; + if (content.IsNullOrEmpty ()) { + exception = source.ToException (); + } else { + exception = source.AddContentToException (content); + } + + // + throw exception; + } + } +} \ No newline at end of file diff --git a/Extensions/FilterExtensions.cs b/Extensions/FilterExtensions.cs new file mode 100644 index 0000000..781d06d --- /dev/null +++ b/Extensions/FilterExtensions.cs @@ -0,0 +1,34 @@ +using System.Linq; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc.Authorization; +using Microsoft.AspNetCore.Mvc.Filters; +using xCommons.Configurations; + +namespace xCommons.Extensions { + public static partial class FilterExtensions { + public static XAppConfiguration GetXAppConfiguration (this ActionExecutingContext source) { + // + var services = source.HttpContext.RequestServices; + return (XAppConfiguration) services.GetService (typeof (XAppConfiguration)); + } + + public static XAppConfiguration GetXAppConfiguration (this AuthorizationFilterContext source) { + // + var services = source.HttpContext.RequestServices; + return (XAppConfiguration) services.GetService (typeof (XAppConfiguration)); + } + + public static bool IsAnonymousAllowed (this AuthorizationFilterContext source) { + // + var result = source.Filters + .Any (f => f.GetType () == typeof (AllowAnonymousFilter)) || + source.ActionDescriptor.FilterDescriptors + .Any (f => f.GetType () == typeof (AllowAnonymousFilter)) || + source.ActionDescriptor.EndpointMetadata + .Any (f => f.GetType () == typeof (AllowAnonymousAttribute)); + + // + return result; + } + } +} \ No newline at end of file diff --git a/Extensions/IConfigurationExtensions.cs b/Extensions/IConfigurationExtensions.cs new file mode 100644 index 0000000..978e16e --- /dev/null +++ b/Extensions/IConfigurationExtensions.cs @@ -0,0 +1,48 @@ +using System.Collections.Generic; +using Microsoft.Extensions.Configuration; +using xCommons.Configurations; +using xCommons.Constants; + +namespace xCommons.Extensions { + public static partial class IConfigurationExtensions { + /// + /// Retrieve AppConfiguration from Configurations + /// + /// + /// + public static XAppConfiguration GetXAppConfiguration (this IConfiguration source) { + // + var xMessageResourceTitlesConfigSection = source + .GetSection (ConfigurationNodeNames.MESSAGE_RESOURCE_TITLES_NODE); + + // + var result = new XAppConfiguration { + Name = source[nameof (XAppConfiguration.Name)], + Version = source[nameof (XAppConfiguration.Version)], + XPoweredValue = source[nameof (XAppConfiguration.XPoweredValue)], + WelcomeMessage = source[nameof (XAppConfiguration.WelcomeMessage)], + AllowedOrigins = source + .GetSection (nameof (XAppConfiguration.AllowedOrigins)) + .Get> (), + DefaultLanguage = source[nameof (XAppConfiguration.DefaultLanguage)], + MessageResourceTitles = xMessageResourceTitlesConfigSection + .Get () + }; + + // + return result; + } + + /// + /// Retrieve Swagger Configurations + /// + /// + /// + public static XSwaggerConfiguration GetXSwaggerConfiguration (this IConfiguration source) { + // + var xSwaggerConfigSection = source + .GetSection (ConfigurationNodeNames.SWAGGER_NODE); + return xSwaggerConfigSection.Get (); + } + } +} \ No newline at end of file diff --git a/Extensions/ModelExtensions.cs b/Extensions/ModelExtensions.cs new file mode 100644 index 0000000..97e8090 --- /dev/null +++ b/Extensions/ModelExtensions.cs @@ -0,0 +1,283 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Linq.Expressions; + +namespace xCommons.Extensions { + public static partial class ModelExtensions { + /// + /// Update an Instance of a Typed Object with Data Provided by another Instance of Typed Object + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + public static T UpdateData ( + this T source, + D updateWith, + ICollection propertyWhiteList = null, + ICollection propertyBlackList = null, + ICollection>> propertyValueProviders = null, + bool updateWithNullOrEmptyValues = false, + bool throwExceptionOnFails = false + ) + where T : class + where D : class { + // + var hasWhiteList = propertyWhiteList.HasChild (); + var hasBlackList = propertyBlackList.HasChild (); + var hasValueProvider = propertyValueProviders.HasChild>> (); + + // + var fullPropertyList = updateWith.GetType ().GetProperties ().Select (prop => prop.Name); + var mustSetProps = (hasWhiteList ? propertyWhiteList : fullPropertyList); + + // + if (hasBlackList) { + mustSetProps = mustSetProps.Where (propName => !propertyBlackList.Contains (propName)); + } + + // + mustSetProps.ToList ().ForEach (propName => { + // + try { + // + var sourceProp = source.GetType ().GetProperty (propName); + var sourceValue = sourceProp.GetValue (source); + var sourceValueType = sourceValue.IsNull () ? null : sourceValue.GetType (); + + // + var updateWithProp = updateWith.GetType ().GetProperty (propName); + var updateWithValue = updateWithProp.GetValue (updateWith); + var updateWithValueType = updateWithValue.IsNull () ? null : updateWithValue.GetType (); + + // + var valueProviderValue = hasValueProvider ? propertyValueProviders + .FirstOrDefault (p => p.Key == propName).Value : null; + var updateVal = !valueProviderValue.IsNull () ? + valueProviderValue.Invoke (updateWith) : + (updateWithValue.IsNull () && !updateWithNullOrEmptyValues) ? + sourceValue : updateWithValue; + var updateValType = updateVal.IsNull () ? null : updateVal.GetType (); + + // + Type t = Nullable.GetUnderlyingType (sourceProp.PropertyType) ?? sourceProp.PropertyType; + object safeValue = + (updateVal == null) ? + null : + !updateValType.IsCollectionType () ? + Convert.ChangeType (updateVal, t) : + updateVal; + + // + sourceProp.SetValue (source, safeValue); + } catch (Exception ex) { + // + if (throwExceptionOnFails) { + throw ex; + } + } + }); + + // + return source; + } + + + /// + /// Check two Instance of Typed Object Values are Same or not + /// + /// + /// + /// + /// + /// + /// + /// /// + /// + /// + public static bool IsSameContent ( + this T source, + D dest, + ICollection propertyWhiteList = null, + ICollection propertyBlackList = null, + ICollection>> propertyValueCheckers = null, + bool throwExceptionOnFails = false + ) + where T : class + where D : class { + // + var hasWhiteList = propertyWhiteList.HasChild (); + var hasBlackList = propertyBlackList.HasChild (); + var hasCheckerProvider = propertyValueCheckers.HasChild>> (); + + // + var fullPropertyList = dest.GetType ().GetProperties ().Select (prop => prop.Name); + var mustCheckProps = (hasWhiteList ? propertyWhiteList : fullPropertyList); + if (hasBlackList) { + mustCheckProps = mustCheckProps.Where (propName => !propertyBlackList.Contains (propName)); + } + + // + var result = true; + mustCheckProps.ToList ().ForEach (propName => { + // + try { + // + var sourceProp = source.GetType ().GetProperty (propName); + var destProp = dest.GetType ().GetProperty (propName); + + // + // Check Types ... + var isSameType = true; + + // + // Check + var isSameValue = false; + var mustDoCustomCheck = hasCheckerProvider && propertyValueCheckers.Any (p => p.Key == propName); + if (mustDoCustomCheck) { + // + // Check Values ... + var valueChecker = propertyValueCheckers.FirstOrDefault (p => p.Key == propName).Value; + isSameValue = valueChecker.Invoke (source, dest); + } else { + // // + // isSameType = (sourceProp.PropertyType == destProp.PropertyType && + // sourceProp.ReflectedType == destProp.ReflectedType); + + // + // Check Values ... + isSameValue = Equals (sourceProp.GetValue (source), destProp.GetValue (dest)); + } + + // + if (!isSameType || !isSameValue) { + result = false; + } + } catch (Exception ex) { + // + if (throwExceptionOnFails) { + throw ex; + } + } + }); + + // + return result; + } + + /// + /// Check specific type is Collection Type or not + /// + /// + /// + public static bool IsCollectionType (this Type source) { + // + if (source.IsNull () || !source.IsGenericType) { + return false; + } + + // + var genericTypeDefinition = source.GetGenericTypeDefinition (); + var result = genericTypeDefinition == typeof (List<>) || + genericTypeDefinition == typeof (HashSet<>) || + genericTypeDefinition == typeof (Collection<>) || + genericTypeDefinition == typeof (ICollection<>) || + genericTypeDefinition == typeof (IEnumerable<>); + + // + return result; + } + + /// + /// Retrieve a Property Value in Generic + /// + /// + /// + /// + public static string GetPropValues (this T item) + where T : class { + // + var props = item.GetType ().GetProperties (); + var vals = props.Select (p => p.GetValue (item)); + + // + return vals.ToJSON (); + } + + /// + /// Check a Property Value Contains specific value + /// + /// + /// + /// + /// + public static bool PropValuesContains (this T item, string value) + where T : class { + // + return item.GetPropValues () + .ToNormalString () + .Contains (value + .ToNormalString ()); + } + + /// + /// Generate Default Column Map + /// + /// + /// + /// + public static IDictionary>> GetDefaultColumnsMap (this T item) + where T : class { + // + if (item.IsNull ()) { + return null; + } + + // + var result = new Dictionary>> (); + var props = item.GetType ().GetProperties (); + + // + foreach (var prop in props) { + result.Add (prop.Name, t => t.GetType ().GetProperty (prop.Name).GetValue (t, null)); + } + + // + return result; + } + + /// + /// Generate Default Column Map + /// + /// + /// + /// + public static IDictionary GetPropValuesDictionary (this T item) + where T : class { + // + if (item.IsNull ()) { + return null; + } + + // + var result = new Dictionary (); + var props = item.GetType ().GetProperties (); + + // + foreach (var prop in props) { + result.Add (prop.Name, prop.GetValue (item).ToJSON ()); + } + + // + return result; + } + } +} \ No newline at end of file diff --git a/Extensions/ServiceProviderExtensions.cs b/Extensions/ServiceProviderExtensions.cs new file mode 100644 index 0000000..0c5b06a --- /dev/null +++ b/Extensions/ServiceProviderExtensions.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using Microsoft.Extensions.DependencyInjection; +using xCommons.Helpers; + +namespace xCommons.Extensions { + public static class ServiceProviderExtensions { + /// + /// Get all registered + /// + /// + /// + public static Dictionary GetAllServiceDescriptors (this IServiceProvider provider) { + // + if (provider is ServiceProvider serviceProvider) { + // + var result = new Dictionary (); + + // + var engine = serviceProvider.GetFieldValue ("_engine"); + var callSiteFactory = engine.GetPropertyValue ("CallSiteFactory"); + var descriptorLookup = callSiteFactory.GetFieldValue ("_descriptorLookup"); + if (descriptorLookup is IDictionary dictionary) { + foreach (DictionaryEntry entry in dictionary) { + result.Add ((Type) entry.Key, (ServiceDescriptor) entry.Value.GetPropertyValue ("Last")); + } + } + + // + return result; + } + + // + throw new NotSupportedException ($"Type '{provider.GetType()}' is not supported!"); + } + } +} \ No newline at end of file diff --git a/Extensions/XHttpContextExtensions.cs b/Extensions/XHttpContextExtensions.cs new file mode 100644 index 0000000..5c0cb98 --- /dev/null +++ b/Extensions/XHttpContextExtensions.cs @@ -0,0 +1,22 @@ +using System.IO; +using Microsoft.AspNetCore.Http; + +namespace xCommons.Extensions { + public static partial class XHttpContextExtensions { + /// + /// Convert IFormFile to Bytes Array + /// + /// + /// + public static byte[] ToByteArray (this IFormFile source) { + // + byte[] data; + using (var br = new BinaryReader (source.OpenReadStream ())) { + data = br.ReadBytes ((int) source.OpenReadStream ().Length); + } + + // + return data; + } + } +} \ No newline at end of file diff --git a/Extensions/XMiddlewareExtensions.cs b/Extensions/XMiddlewareExtensions.cs new file mode 100644 index 0000000..4d4e3e6 --- /dev/null +++ b/Extensions/XMiddlewareExtensions.cs @@ -0,0 +1,17 @@ +using System; +using Microsoft.AspNetCore.Builder; +using xCommons.Middlewares; + +namespace xCommons.Extensions +{ + public static class XMiddlewareExtensions + { + /// + /// Use XRequestLogger Middleware for log Requests ... + /// + /// + public static void UseXRequestLogger(this IApplicationBuilder app) { + app.UseMiddleware(); + } + } +} diff --git a/Filters/RequireXPoweredOperationFilter.cs b/Filters/RequireXPoweredOperationFilter.cs new file mode 100644 index 0000000..fd3430c --- /dev/null +++ b/Filters/RequireXPoweredOperationFilter.cs @@ -0,0 +1,39 @@ +using System.Collections.Generic; +using Microsoft.OpenApi.Any; +using Microsoft.OpenApi.Models; +using Swashbuckle.AspNetCore.SwaggerGen; +using xCommons.Configurations; +using xCommons.Constants; +using xCommons.Extensions; + +namespace xCommons.Filters { + /// + /// RequireXPowered for NGSwag + /// + public partial class RequireXPoweredOperationFilter : IOperationFilter { + private readonly XAppConfiguration config; + + public RequireXPoweredOperationFilter (XAppConfiguration config) { + this.config = config; + } + + public void Apply (OpenApiOperation operation, OperationFilterContext context) { + // + if (operation.Parameters.IsNull ()) { + operation.Parameters = new List (); + } + + // + operation.Parameters.Add (new OpenApiParameter { + In = ParameterLocation.Header, + Name = XAuthorization.XPoweredBy, + Description = "a Basic Authorization Filter", + Required = false, + Schema = new OpenApiSchema { + Type = "String", + Default = new OpenApiString (config.XPoweredValue) + } + }); + } + } +} \ No newline at end of file diff --git a/Helpers/ObjectHelper.cs b/Helpers/ObjectHelper.cs new file mode 100644 index 0000000..5a4341a --- /dev/null +++ b/Helpers/ObjectHelper.cs @@ -0,0 +1,99 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace xCommons.Helpers { + public partial class ObjectHelper { + /// + /// Convert Enum Keys to Array + /// + /// + /// + public static Array GetKeys () { + // + ValidateEnumbType (); + var type = typeof (T); + + // + var result = Enum.GetNames (type); + + // + return result; + } + + /// + /// Convert EnumValues to Array + /// + /// + /// + public static Array GetValues () { + // + ValidateEnumbType (); + var type = typeof (T); + + // + var result = Enum.GetValues (type); + + // + return result; + } + + /// + /// Convert an Enum to IEnumerable of it's Childs + /// + /// + /// + public static IEnumerable ToEnumerableValues () { + // + ValidateEnumbType (); + var type = typeof (T); + + // + var result = GetValues () + .Cast (); + + // + return result; + } + + /// + /// Retrieve Keys of an Enumerable + /// + /// + /// + public static IEnumerable ToEnumerableKeys () { + // + ValidateEnumbType (); + var type = typeof (T); + + // + var result = GetKeys () + .Cast (); + + // + return result; + } + + public static IDictionary ToDictionary () { + // + ValidateEnumbType (); + + // + var result = new Dictionary (); + foreach (var name in Enum.GetNames (typeof (T))) { + result.Add (name, (int) Enum.Parse (typeof (T), name)); + } + + // + return result; + } + + private static void ValidateEnumbType () { + // + var type = typeof (T); + if (!type.IsEnum) { + throw new InvalidCastException (); + } + } + } +} \ No newline at end of file diff --git a/Helpers/XDateHelper.cs b/Helpers/XDateHelper.cs new file mode 100644 index 0000000..6307551 --- /dev/null +++ b/Helpers/XDateHelper.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace xCommons.Helpers { + public partial class XDateHelper { + /// + /// retrieve utc now timestam unix like representation ... + /// + /// + public static long UtcNowTimespan () { + return DateTimeOffset.UtcNow.ToUnixTimeMilliseconds (); + } + } +} \ No newline at end of file diff --git a/Helpers/XMLToJSONHelper.cs b/Helpers/XMLToJSONHelper.cs new file mode 100644 index 0000000..c848994 --- /dev/null +++ b/Helpers/XMLToJSONHelper.cs @@ -0,0 +1,178 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Xml; + +namespace xCommons.Helpers { + public partial class XMLToJSONHelper { + // + #region Actions ... + public static string ToJSON (string xml) { + // + XmlDocument doc = new XmlDocument (); + doc.LoadXml (xml); + + // + return XmlToJSON (doc); + } + + public static string XmlToJSON (XmlDocument xmlDoc) { + // + StringBuilder sbJSON = new StringBuilder (); + + // + sbJSON.Append ("{ "); + XmlToJSONnode (sbJSON, xmlDoc.DocumentElement, true); + sbJSON.Append ("}"); + + // + return sbJSON.ToString (); + } + #endregion + + // + #region Private ... + // + private static void XmlToJSONnode (StringBuilder sbJSON, XmlElement node, bool showNodeName) { + // + if (showNodeName) { + sbJSON.Append ("\"" + SafeJSON (node.Name) + "\": "); + } + + // + sbJSON.Append ("{"); + + // + // Build a sorted list of key-value pairs + // where key is case-sensitive nodeName + // value is an ArrayList of string or XmlElement + // so that we know whether the nodeName is an array or not. + SortedList childNodeNames = new SortedList (); + + // + // Add in all node attributes + if (node.Attributes != null) + foreach (XmlAttribute attr in node.Attributes) + StoreChildNode (childNodeNames, attr.Name, attr.InnerText); + + // + // Add in all nodes + foreach (XmlNode cnode in node.ChildNodes) { + // + if (cnode is XmlText) { + StoreChildNode (childNodeNames, "value", cnode.InnerText); + } else if (cnode is XmlElement) { + StoreChildNode (childNodeNames, cnode.Name, cnode); + } + } + + // + // Now output all stored info + foreach (string childname in childNodeNames.Keys) { + // + List alChild = (List) childNodeNames[childname]; + if (alChild.Count == 1) { + OutputNode (childname, alChild[0], sbJSON, true); + } else { + // + sbJSON.Append (" \"" + SafeJSON (childname) + "\": [ "); + + // + foreach (object Child in alChild) { + OutputNode (childname, Child, sbJSON, false); + } + + // + sbJSON.Remove (sbJSON.Length - 2, 2); + sbJSON.Append (" ], "); + } + } + + // + sbJSON.Remove (sbJSON.Length - 2, 2); + sbJSON.Append (" }"); + } + + // + // StoreChildNode: Store data associated with each nodeName + // so that we know whether the nodeName is an array or not. + private static void StoreChildNode (SortedList childNodeNames, string nodeName, object nodeValue) { + // + // Pre-process contraction of XmlElement-s + if (nodeValue is XmlElement) { + // + // Convert into "aa":null + // xx into "aa":"xx" + XmlNode cnode = (XmlNode) nodeValue; + if (cnode.Attributes.Count == 0) { + // + XmlNodeList children = cnode.ChildNodes; + if (children.Count == 0) { + nodeValue = null; + } else if (children.Count == 1 && (children[0] is XmlText)) { + nodeValue = ((XmlText) (children[0])).InnerText; + } + } + } + + // + // Add nodeValue to ArrayList associated with each nodeName + // If nodeName doesn't exist then add it + List ValuesAL; + + // + if (childNodeNames.ContainsKey (nodeName)) { + ValuesAL = (List) childNodeNames[nodeName]; + } else { + // + ValuesAL = new List (); + childNodeNames[nodeName] = ValuesAL; + } + + // + ValuesAL.Add (nodeValue); + } + + // + private static void OutputNode (string childname, object alChild, StringBuilder sbJSON, bool showNodeName) { + if (alChild == null) { + if (showNodeName) + sbJSON.Append ("\"" + SafeJSON (childname) + "\": "); + sbJSON.Append ("null"); + } else if (alChild is string) { + if (showNodeName) + sbJSON.Append ("\"" + SafeJSON (childname) + "\": "); + string sChild = (string) alChild; + sChild = sChild.Trim (); + sbJSON.Append ("\"" + SafeJSON (sChild) + "\""); + } else + XmlToJSONnode (sbJSON, (XmlElement) alChild, showNodeName); + sbJSON.Append (", "); + } + + // + // Make a string safe for JSON + private static string SafeJSON (string sIn) { + // + StringBuilder sbOut = new StringBuilder (sIn.Length); + foreach (char ch in sIn) { + // + if (Char.IsControl (ch) || ch == '\'') { + // + int ich = (int) ch; + sbOut.Append (@"\u" + ich.ToString ("x4")); + continue; + } else if (ch == '\"' || ch == '\\' || ch == '/') { + sbOut.Append ('\\'); + } + + // + sbOut.Append (ch); + } + + // + return sbOut.ToString (); + } + #endregion + } +} \ No newline at end of file diff --git a/Helpers/XNetworkHelper.cs b/Helpers/XNetworkHelper.cs new file mode 100644 index 0000000..7daeff0 --- /dev/null +++ b/Helpers/XNetworkHelper.cs @@ -0,0 +1,81 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.NetworkInformation; +using System.Net.Sockets; +using System.Threading.Tasks; +using xCommons.Models; + +namespace xCommons.Helpers { + public static class XNetworkHelper { + /// + /// Retrieve Application Host's Machin IP Addresses ... + /// + /// string + public static string GetIp () { + // + var name = Dns.GetHostName (); // get container id + var result = Dns.GetHostEntry (name) + .AddressList + .FirstOrDefault ( + x => x.AddressFamily == AddressFamily.InterNetwork + ) + .MapToIPv4 () + .ToString (); + + // + return result; + } + + /// + /// Retrieve All Available IP's in Application Host's Machin ... + /// + /// a Collection of XNetworkInfo class instances + public static IEnumerable GetNetworkInfos () { + // + var result = System.Net.NetworkInformation.NetworkInterface + .GetAllNetworkInterfaces () + .Where ( + ni => + !ni.IsReceiveOnly && + ni.OperationalStatus == OperationalStatus.Up && + ni.NetworkInterfaceType != NetworkInterfaceType.Loopback + ) + .SelectMany (ni => + ni + .GetIPProperties () + .GatewayAddresses + .Select (ga => + new { + Name = ni.Name, + Description = ni.Description, + Gateway = ga.Address + .MapToIPv4 () + .ToString (), + NI = ni + } + ) + ) + .SelectMany (data => + data.NI + .GetIPProperties () + .UnicastAddresses + .Select (ip => + new XNetworkInfo { + Name = data.Name, + Gateway = data.Gateway, + Description = data.Description, + NetworkInterface = data.NI, + IP = ip.Address + .MapToIPv4 () + .ToString () + } + ) + ); + + // + return result; + } + } +} \ No newline at end of file diff --git a/Helpers/XReflectionHelper.cs b/Helpers/XReflectionHelper.cs new file mode 100644 index 0000000..21b88ae --- /dev/null +++ b/Helpers/XReflectionHelper.cs @@ -0,0 +1,70 @@ +using System; +using System.Reflection; + +namespace xCommons.Helpers { + public static class XReflectionHelper { + public static object GetFieldValue (this object obj, string fieldName) { + if (obj == null) + throw new ArgumentNullException (nameof (obj)); + Type objType = obj.GetType (); + var fieldInfo = GetFieldInfo (objType, fieldName); + if (fieldInfo == null) + throw new ArgumentOutOfRangeException (fieldName, + $"Couldn't find field {fieldName} in type {objType.FullName}"); + return fieldInfo.GetValue (obj); + } + + public static void SetFieldValue (this object obj, string fieldName, object val) { + if (obj == null) + throw new ArgumentNullException (nameof (obj)); + Type objType = obj.GetType (); + var fieldInfo = GetFieldInfo (objType, fieldName); + if (fieldInfo == null) + throw new ArgumentOutOfRangeException (fieldName, + $"Couldn't find field {fieldName} in type {objType.FullName}"); + fieldInfo.SetValue (obj, val); + } + + private static FieldInfo GetFieldInfo (Type type, string fieldName) { + FieldInfo fieldInfo = null; + do { + fieldInfo = type.GetField (fieldName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + type = type.BaseType; + } while (fieldInfo == null && type != null); + + return fieldInfo; + } + + public static object GetPropertyValue (this object obj, string propertyName) { + if (obj == null) + throw new ArgumentNullException (nameof (obj)); + Type objType = obj.GetType (); + var propertyInfo = GetPropertyInfo (objType, propertyName); + if (propertyInfo == null) + throw new ArgumentOutOfRangeException (propertyName, + $"Couldn't find property {propertyName} in type {objType.FullName}"); + return propertyInfo.GetValue (obj, null); + } + + public static void SetPropertyValue (this object obj, string propertyName, object val) { + if (obj == null) + throw new ArgumentNullException (nameof (obj)); + Type objType = obj.GetType (); + var propertyInfo = GetPropertyInfo (objType, propertyName); + if (propertyInfo == null) + throw new ArgumentOutOfRangeException (propertyName, + $"Couldn't find property {propertyName} in type {objType.FullName}"); + propertyInfo.SetValue (obj, val, null); + } + + private static PropertyInfo GetPropertyInfo (Type type, string propertyName) { + PropertyInfo propertyInfo = null; + do { + propertyInfo = type.GetProperty (propertyName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + type = type.BaseType; + } while (propertyInfo == null && type != null); + + return propertyInfo; + } + } +} \ No newline at end of file diff --git a/Interfaces/.gitkeep b/Interfaces/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/Middlewares/XRequestLogger.cs b/Middlewares/XRequestLogger.cs new file mode 100644 index 0000000..5edb67b --- /dev/null +++ b/Middlewares/XRequestLogger.cs @@ -0,0 +1,144 @@ +using System; +using System.IO; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; +using xCommons.Extensions; +using xCommons.Models; + +namespace xCommons.Middlewares { + public partial class XRequestLogger { + + private readonly ILogger logger; + private readonly RequestDelegate next; + + public XRequestLogger ( + RequestDelegate next, + ILoggerFactory loggerFactory + ) { + // + this.next = next; + this.logger = loggerFactory + .CreateLogger (); + } + + public async Task InvokeAsync (HttpContext context) { + // + var logModel = new XLoggerModel (); + + // + logModel.Path = context.Request.Path; + logModel.Schema = context.Request.Scheme; + logModel.Method = context.Request.Method; + logModel.Headers = context.Request.Headers; + logModel.Host = context.Request.Host.ToString (); + logModel.QueryString = context.Request.QueryString.ToString (); + logModel.UserID = context.User.Identity.IsAuthenticated ? context.User.Identity.Name : "Not Authenticated"; + + // + logModel.Connection = new XLoggerHttpConnection { + Id = context.Connection.Id, + RemoteIpAddress = context.Connection.RemoteIpAddress.ToString (), + RemotePort = context.Connection.RemotePort.ToString (), + LocalIpAddress = context.Connection.LocalIpAddress.ToString (), + LocalPort = context.Connection.LocalPort.ToString (), + }; + + // + // Check if a request is a Post Call so read data from Body ... + if (context.Request.Method.ToNormalString () == "post") { + // + context.Request.EnableBuffering (); + var request = await new StreamReader (context.Request.Body) + .ReadToEndAsync (); + + // + context.Request.Body.Position = 0; + logModel.Request = request; + } + + // + logModel.RequestedOn = DateTime.UtcNow; + + // + // Do Request ... + await next.Invoke (context); + + // + using (Stream originalRequest = context.Response.Body) { + try { + using (var memStream = new MemoryStream ()) { + // + context.Response.Body = memStream; + + // + // All the Request processing as described above + // happens from here. + // Response handling starts from here + // set the pointer to the beginning of the + // memory stream to read + memStream.Position = 0; + + // + // read the memory stream till the end + var response = await new StreamReader (memStream) + .ReadToEndAsync (); + + // + logModel.Response = response; + logModel.RespondedOn = DateTime.UtcNow; + logModel.StatusCode = context.Response.StatusCode; + logModel.IsSucceed = context.Response.StatusCode >= 200 && context.Response.StatusCode <= 299; + + // + // since we have read till the end of the stream, + // reset it onto the first position + memStream.Position = 0; + + // + // now copy the content of the temporary memory + // stream we have passed to the actual response body + // which will carry the response out. + await memStream.CopyToAsync (originalRequest); + } + } catch (Exception ex) { + // + // Log Exception ... + logger.LogError ($"Error on Logging Request/Response ... {ex.ToJSON()}"); + } finally { + // + // assign the response body to the actual context + context.Response.Body = originalRequest; + } + } + + // + // Log Response ... + logger.LogCritical ($"Http Response/Response Information:{Environment.NewLine}" + + $"Schema:{logModel.Schema} " + Environment.NewLine + + $"Host: {logModel.Host} " + Environment.NewLine + + $"Path: {logModel.Path} " + Environment.NewLine + + $"Method: {logModel.Method} " + Environment.NewLine + + $"UserID: {logModel.UserID} " + Environment.NewLine + + $"QueryString: {context.Request.QueryString} " + Environment.NewLine + + $"Headers: {logModel.Headers.ToJSON()}" + Environment.NewLine + + $"" + Environment.NewLine + + $"Http Connection: " + Environment.NewLine + + $"Id: {logModel.Connection.Id}" + Environment.NewLine + + $"RemoteIpAddress: {logModel.Connection.RemoteIpAddress}" + Environment.NewLine + + $"RemotePort: {logModel.Connection.RemotePort}" + Environment.NewLine + + $"LocalIpAddress: {logModel.Connection.LocalIpAddress}" + Environment.NewLine + + $"LocalPort: {logModel.Connection.LocalPort}" + Environment.NewLine + + $"" + Environment.NewLine + + $"Request Body: {logModel.Request}" + Environment.NewLine + + $"RequestedOn: {logModel.RequestedOn}" + Environment.NewLine + + $"" + Environment.NewLine + + $"IsSucceed: {logModel.IsSucceed}" + Environment.NewLine + + $"StatucCode: {logModel.StatusCode}" + Environment.NewLine + + $"Response Body: {logModel.Response}" + Environment.NewLine + + $"RespondedOn: {logModel.RespondedOn}" + Environment.NewLine + + $"" + Environment.NewLine + ); + } + } +} \ No newline at end of file diff --git a/Models/XCertificate.cs b/Models/XCertificate.cs new file mode 100644 index 0000000..1202c4b --- /dev/null +++ b/Models/XCertificate.cs @@ -0,0 +1,6 @@ +namespace xCommons.Models { + public partial class XCertificate { + public string Path { get; set; } + public string Secret { get; set; } + } +} \ No newline at end of file diff --git a/Models/XLoggerModel.cs b/Models/XLoggerModel.cs new file mode 100644 index 0000000..06be5f8 --- /dev/null +++ b/Models/XLoggerModel.cs @@ -0,0 +1,29 @@ +using System; +using Microsoft.AspNetCore.Http; + +namespace xCommons.Models { + public class XLoggerModel { + public string Schema { get; set; } + public string Host { get; set; } + public string Path { get; set; } + public string Method { get; set; } + public string UserID { get; set; } + public string QueryString { get; set; } + public IHeaderDictionary Headers { get; set; } + public string Request { get; set; } + public int StatusCode { get; set; } + public bool IsSucceed { get; set; } + public string Response { get; set; } + public DateTime RequestedOn { get; set; } + public DateTime RespondedOn { get; set; } + public XLoggerHttpConnection Connection { get; set; } + } + + public class XLoggerHttpConnection { + public string Id { get; set; } + public string RemoteIpAddress { get; set; } + public string RemotePort { get; set; } + public string LocalIpAddress { get; set; } + public string LocalPort { get; set; } + } +} \ No newline at end of file diff --git a/Models/XNetworkInfo.cs b/Models/XNetworkInfo.cs new file mode 100644 index 0000000..1c88000 --- /dev/null +++ b/Models/XNetworkInfo.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.NetworkInformation; +using System.Threading.Tasks; + +namespace xCommons.Models { + public class XNetworkInfo { + /// + /// Network Name ... + /// + /// + public string Name { get; set; } + + /// + /// Network Description ... + /// + /// + public string Description { get; set; } + + /// + /// IP Address which Exists on Current Network ... + /// + /// + public string IP { get; set; } + + /// + /// Current Networks Gateway Address ... + /// + /// + public string Gateway { get; set; } + + /// + /// Network Interface Object ... + /// + /// + public NetworkInterface NetworkInterface { get; set; } + } +} \ No newline at end of file diff --git a/Providers/XValidationProvider.cs b/Providers/XValidationProvider.cs new file mode 100644 index 0000000..d9c7c5e --- /dev/null +++ b/Providers/XValidationProvider.cs @@ -0,0 +1,405 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using xCommons.Extensions; +using xExceptions.Constants; + +namespace xCommons.Providers { + public partial class XValidationProvider { + private ICollection validationGroup; + + // + #region Simple Validators ... + /// + /// given objects must not null + /// + /// + public void NotNull ( + params object[] values) { + // + foreach (var o in values) { + // + if (o == null) { + XException.InvalidArgs.Throw (); + } + } + } + + /// + /// given strings must not empty or null + /// + /// + public void NotEmpty ( + params string[] values) { + // + foreach (var s in values) { + // + if (s.IsNullOrEmpty ()) { + XException.InvalidArgs.Throw (); + } + } + } + + /// + /// Check a numeric value is bigger than 0 + /// + /// + public void NotZero ( + params int[] values) { + // + foreach (var s in values) { + // + if (s == 0) { + XException.InvalidArgs.Throw (); + } + } + } + + /// + /// Check a numeric value is bigger than 0 + /// + /// + public void NotZero ( + params long[] values) { + // + foreach (var s in values) { + // + if (s == 0) { + XException.InvalidArgs.Throw (); + } + } + } + + /// + /// Check a numeric value is bigger than 0 + /// + /// + public void NotZero ( + params decimal[] values) { + // + foreach (var s in values) { + // + if (s == 0) { + XException.InvalidArgs.Throw (); + } + } + } + + /// + /// Check a Value is a Valid Guid ... + /// + /// + public void IsGuid (params string[] values) { + // + foreach (var s in values) { + // + if (!s.IsGuid () || s.IsDefaultGuid ()) { + XException.InvalidArgs.Throw (); + } + } + } + + /// + /// Check a Value is a Valid Guid ... + /// + /// + public void IsGuid (params Guid[] values) { + // + foreach (var s in values) { + // + if (s.IsNull () || s.IsDefaultGuid ()) { + XException.InvalidArgs.Throw (); + } + } + } + + /// + /// Check a Value is an MD5 ... + /// + /// + public void IsMD5 (params string[] values) { + // + foreach (var s in values) { + // + if (!s.IsMD5String ()) { + XException.InvalidArgs.Throw (); + } + } + } + + /// + /// non zero childs + /// + /// + public void NotZeroChilds ( + params IEnumerable[] values) { + // + foreach (var s in values) { + // + if (s == null || + s.Count () == 0) { + XException.InvalidArgs.Throw (); + } + } + } + + /// + /// non zero childs + /// + /// + public void NotZeroChilds ( + params ICollection[] values) { + // + foreach (var s in values) { + // + if (s == null || + s.Count == 0) { + XException.InvalidArgs.Throw (); + } + } + } + + /// + /// given string must be a valid URL ... + /// + /// + /// + public void Url ( + string url, + Exception exception = null + ) { + // + NotEmpty (url); + + // + if (exception == null) { + exception = XException.InavlidUrl.ToException (); + } + + // + if (!url.IsValidUrl ()) { + throw exception; + } + } + + /// + /// given string must be a valid Email Address ... + /// + /// + /// + public void EmailAddress ( + string emailAdress, + Exception exception = null + ) { + // + NotEmpty (emailAdress); + + // + if (exception == null) { + exception = XException.InvalidEmailAddress.ToException (); + } + + // + if (!emailAdress.IsValidEmail ()) { + throw exception; + } + } + + /// + /// given string must be a valid Mobile Number ... + /// + /// + /// + public void MobileNumber ( + string mobileNumber, + Exception exception = null + ) { + // + NotEmpty (mobileNumber); + + // + if (exception == null) { + exception = XException.InvalidMobileNumber.ToException (); + } + + // + if (!mobileNumber.IsValidMobileNumber ()) { + throw exception; + } + } + #endregion + + // + #region Group Validatos ... + public XValidationProvider GroupValidationBuilder () { + // + validationGroup = new List (); + + // + return this; + } + + public XValidationProvider AddNotEmpty (params string[] values) { + // + this.validationGroup.Add (() => + this.NotEmpty (values) + ); + + // + return this; + } + + public XValidationProvider AddNotNull (params object[] values) { + // + this.validationGroup.Add (() => + this.NotNull (values) + ); + + // + return this; + } + + public XValidationProvider AddNotZero (params int[] values) { + // + this.validationGroup.Add (() => + this.NotZero (values) + ); + + // + return this; + } + + public XValidationProvider AddNotZero (params long[] values) { + // + this.validationGroup.Add (() => + this.NotZero (values) + ); + + // + return this; + } + + public XValidationProvider AddNotZero (params decimal[] values) { + // + this.validationGroup.Add (() => + this.NotZero (values) + ); + + // + return this; + } + + public XValidationProvider AddIsGuid (params string[] values) { + // + this.validationGroup.Add (() => + this.IsGuid (values) + ); + + // + return this; + } + + public XValidationProvider AddIsGuid (params Guid[] values) { + // + this.validationGroup.Add (() => + this.IsGuid (values) + ); + + // + return this; + } + + public XValidationProvider AddIsMD5 (params string[] values) { + // + this.validationGroup.Add (() => + this.IsMD5 (values) + ); + + // + return this; + } + + public XValidationProvider AddNotZeroChilds (params IEnumerable[] values) { + // + this.validationGroup.Add (() => + this.NotZeroChilds (values) + ); + + // + return this; + } + + public XValidationProvider AddNotZeroChilds (params ICollection[] values) { + // + this.validationGroup.Add (() => + this.NotZeroChilds (values) + ); + + // + return this; + } + + public XValidationProvider AddEmailAddress ( + string emailAdress, + Exception exception = null) { + // + this.validationGroup.Add (() => + this.EmailAddress (emailAdress, exception) + ); + + // + return this; + } + + public XValidationProvider AddMobileNumber ( + string mobileNumber, + Exception exception = null) { + // + this.validationGroup.Add (() => + this.MobileNumber (mobileNumber, exception) + ); + + // + return this; + } + + public XValidationProvider AddUrl ( + string url, + Exception exception = null) { + // + this.validationGroup.Add (() => + this.Url (url, exception) + ); + + // + return this; + } + + public void ValidateGroup () { + // + // Run each Tasks ... + foreach (var action in validationGroup) { + action.Invoke (); + } + + // + // Clear Task List ... + validationGroup.Clear (); + } + + public async Task ValidateGroupAsync () { + // + // Run each Tasks ... + foreach (var action in validationGroup) { + await Task.Run (action); + } + + // + // Clear Task List ... + validationGroup.Clear (); + } + #endregion + + } +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..e3494f8 --- /dev/null +++ b/README.md @@ -0,0 +1,148 @@ +# xCommons + +it is a Part of xDashboard on SaherElm IT Center which provides: + +- Common Configuration Models. +- Common Usefull extra Extensions. +- Data Validation Provider. +- Provided features such as Enums, Models, Attributes, Filters, Authorization Policies, Middlewares and Helpers. +- etc. + +this module has no dependency and itself uses as a base dependency for all XProject modules/ + +for configure and use this Module refer to DI.XDIHelperExtension.cs file. + +## Implementing + +after adding this package as a dependency to your project you can do following: + +### 1. Register it on DI + +```c# +public void ConfigureServices (IServiceCollection services) { + ... + // + // Register Validation Provider and all XCommons Module Services ... + services.AddXCommons (); + ... +} +``` + +this will provide XValidationHelper service which accessible through DI. + +### 2. Register Application Configuration on DI + +since many of features in this Framework works by accessing the configuration of class application. you had to register it on DI. + +configure you app in **appSettings.json**: + +```javascript +{ + ... + "Version": "0.1", + "Name": "SaherElm Sample App", + "DefaultLanguage": "fa-IR", + "XPoweredValue": "SaherElmITCenter", + "WelcomeMessage": "Welcome to Application", + "AllowedOrigins": [ + "http://localhost:4200", + "https://localhost:4200" + ], + ... +}, +``` + +then register your configuration on DI in **Startup** file: + +```c# +public void ConfigureServices (IServiceCollection services) { + ... + // + // Register App Configuration ... + services.AddXAppConfiguration (Configuration); + var appConfiguration = services.GetRegisteredService (); + ... +} +``` + +### 3. Register Available Cross Origin Resource sharing (Cors) + +for determining which addresses can access your application and enabling core mechanism you can simply do it by follow this structure ... + +**NOTE**: notice that you have configured allowed origins in **appSettings.json** before on AppConfiguration section. + +```c# +public void ConfigureServices (IServiceCollection services) { + ... + // + // Register Allowed Origins ... + services.AddXCors (appConfiguration.AllowedOrigins); + ... +} +``` + +the you can enable cors handling like this: + +```c# +public void Configure (IApplicationBuilder app, IWebHostEnvironment env) { + ... + // + // Using Cors ... + app.UseXCors (); + ... +} +``` + +**Note**: remember **AllowedOrigins** can provided using **appSettings.json** file dynamically; + +### 4. Enable Swagger + +also you can add swagger documentation to your project: + +```c# +public void ConfigureServices (IServiceCollection services) { + ... + // + // Register Api Versioning ... + services.AddApiVersioning (opt => { + // + // Set Default Api Version ... + opt.DefaultApiVersion = new ApiVersion (1, 0); + + // + // Set Routing to Default API Version, if Version unspecified ... + opt.AssumeDefaultVersionWhenUnspecified = true; + + // + // Report All Available Api Versions on Response ... + opt.ReportApiVersions = true; + }); + + // + // Register Swagger ... + var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml"; + var xmlPath = Path.Combine (AppContext.BaseDirectory, xmlFile); + services.AddXSwagger (Configuration, xmlFilePath : xmlPath); + ... +} +``` + +the you can enable swagger middleware like this: + +```c# +public void Configure (IApplicationBuilder app, IWebHostEnvironment env) { + ... + // + // Use Swagger Middleware ... + app.UseXSwagger (); + ... +} +``` + +## Maintainer + +Hadi Khazaee asl + +[https://www.saherelm.ir](https://www.saherelm.ir) + +[hadi_khazaee_asl@yahoo.com](mailto:hadi_khazaee_asl@yahoo.com) diff --git a/nuget.config b/nuget.config new file mode 100644 index 0000000..87b6eb0 --- /dev/null +++ b/nuget.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/xCommons.csproj b/xCommons.csproj new file mode 100644 index 0000000..87703df --- /dev/null +++ b/xCommons.csproj @@ -0,0 +1,63 @@ + + + + + netstandard2.0 + xDashboard.xCommons + 1.0.0 + Hadi Khazaee Asl + SaherElm IT Center + + provide all commonly used actions/extensions and classes which required to xDashboard project. + + + + icon.png + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + true + $(NoWarn);1591 + + + \ No newline at end of file