ASP.NET Core搭建多层网站架构【2-公共基础库】

2021-04-23 22:27

阅读:372

标签:fun   问题   off   用户   write   match   hashcode   isp   lambda表达式   

2020/01/28, ASP.NET Core 3.1, VS2019, Snowflake雪花算法ID, Enum枚举方法扩展, Lambda方法扩展, Json方法封装

摘要:基于ASP.NET Core 3.1 WebApi搭建后端多层网站架构【2-公共基础库】
Snowflake雪花算法ID、Enum枚举方法扩展、Lambda方法扩展、Json方法封装

文章目录
此分支项目代码
本章节介绍了MS.Common类库中一些常用公共方法,可以自行添加自己积累的一些库

添加包引用

MS.Common类库中添加包引用:

  

其中Microsoft.AspNetCore.Cryptography.KeyDerivation是为了支持PBKDF2加密方式,这个后文会用到

枚举扩展方法

MS.Common类库中新建Extensions文件夹,在其中添加EnumExtension.cs类:

using System;
using System.ComponentModel;
using System.Reflection;

namespace MS.Common.Extensions
{
    public static class EnumExtension
    {
        /// 
        /// 根据名称拿到枚举
        /// 
        /// 
        /// 
        /// 
        public static T GetEnum(this string itemName)
        {
            return (T)Enum.Parse(typeof(T), itemName);
        }
        /// 
        /// 根据枚举值拿到枚举
        /// 
        /// 
        /// 
        /// 
        public static T GetEnum(this int itemValue)
        {
            return (T)Enum.Parse(typeof(T), Enum.GetName(typeof(T), itemValue));
        }
        /// 
        /// 根据枚举值拿到枚举名称
        /// 
        /// 
        /// 
        /// 
        public static string GetEnumName(this int itemValue)
        {
            return Enum.GetName(typeof(T), itemValue);
        }
        /// 
        /// 根据名称拿到枚举值
        /// 
        /// 
        /// 
        /// 
        public static int GetEnumValue(this string itemName)
        {
            return itemName.GetEnum().GetHashCode();
        }
        /// 
        /// 枚举获取描述
        /// 
        /// 
        /// 
        public static string GetDescription(this Enum item)
        {
            Type type = item.GetType();
            MemberInfo[] memInfo = type.GetMember(item.ToString());
            if (memInfo != null && memInfo.Length > 0)
            {
                object[] attrs = memInfo[0].GetCustomAttributes(typeof(DescriptionAttribute), false);
                if (attrs != null && attrs.Length > 0)
                    return ((DescriptionAttribute)attrs[0]).Description;
            }
            return item.ToString();//如果不存在描述,则返回枚举名称
        } 
    }
}

Lambda表达式扩展方法

在Extensions中继续添加LambdaExtension.cs类:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;

namespace MS.Common.Extensions
{
    //add by yzh 2019/04/26 -用于lambda表达式拼接
    public class ParameterRebinder : ExpressionVisitor
    {
        private readonly Dictionary map;
        public ParameterRebinder(Dictionary map)
        {
            this.map = map ?? new Dictionary();
        }
        public static Expression ReplaceParameters(Dictionary map, Expression exp)
        {
            return new ParameterRebinder(map).Visit(exp);
        }
        protected override Expression VisitParameter(ParameterExpression p)
        {
            ParameterExpression replacement;
            if (map.TryGetValue(p, out replacement))
            {
                p = replacement;
            }
            return base.VisitParameter(p);
        }
    }
    public static class LambdaExtension
    {
        public static Expression Compose(this Expression first, Expression second, Func merge)
        {
            var map = first.Parameters.Select((f, i) => new { f, s = second.Parameters[i] }).ToDictionary(p => p.s, p => p.f);
            var secondBody = ParameterRebinder.ReplaceParameters(map, second.Body);
            return Expression.Lambda(merge(first.Body, secondBody), first.Parameters);
        }
        public static Expression> And(this Expression> first, Expression> second)
        {
            return first.Compose(second, Expression.And);
        }
        public static Expression> Or(this Expression> first, Expression> second)
        {
            return first.Compose(second, Expression.Or);
        }
    }
}

原生的Lambda表达式不支持动态拼接表达式条件,有了这个扩展方法,就弥补了这个缺点。

Json扩展方法

在Extensions中添加JsonExtension.cs类:

using Newtonsoft.Json;
using Newtonsoft.Json.Converters;

namespace MS.Common.Extensions
{
    public static class JsonExtension
    {
        public static JsonSerializerSettings jsonSetting = new JsonSerializerSettings
        {
            ReferenceLoopHandling = ReferenceLoopHandling.Ignore
        };
        /// 
        /// 序列化对象,默认禁止循环引用
        /// 
        /// 
        /// 
        public static string ToJsonString(this object data)
        {
            return JsonConvert.SerializeObject(data, jsonSetting);
        }
        /// 
        /// 序列化对象
        /// 
        /// 
        /// 
        /// 
        public static string ToJsonString(this object data, IsoDateTimeConverter timeConverter)
        {
            return JsonConvert.SerializeObject(data, timeConverter);
        }

        /// 
        /// 反序列化字符串
        /// 
        /// 
        /// 
        /// 
        public static T GetDeserializeObject(this string data)
        {
            if (string.IsNullOrWhiteSpace(data)) return default;
            return JsonConvert.DeserializeObject(data, jsonSetting);
        }

        /// 
        /// 使用序列化和反序列化获得一次深拷贝
        /// 
        /// 
        /// 
        /// 
        public static T GetMemberwiseCopy(this T data)
        {
            return data.ToJsonString().GetDeserializeObject();
        }
    }
}

每个方法都写好了注释,默认禁止循环引用

PBKDF2加密方法

在类库中添加Security文件夹,向其中添加Crypto.cs类:

using Microsoft.AspNetCore.Cryptography.KeyDerivation;
using System;
using System.Runtime.CompilerServices;
using System.Security.Cryptography;

namespace MS.Common.Security
{
    /// 
    /// Provides helper methods for hashing/salting and verifying passwords.
    /// 
    public static class Crypto
    {
        /* =======================
         * HASHED PASSWORD FORMATS
         * =======================
         *
         * Version 3:
         * PBKDF2 with HMAC-SHA256, 128-bit salt, 256-bit subkey, 10000 iterations.
         * Format: { 0x01, prf (UInt32), iter count (UInt32), salt length (UInt32), salt, subkey }
         * (All UInt32s are stored big-endian.)
         */

        private const int PBKDF2IterCount = 10000;
        private const int PBKDF2SubkeyLength = 256 / 8; // 256 bits
        private const int SaltSize = 128 / 8; // 128 bits


        /// 
        /// Returns a hashed representation of the specified .
        /// 
        /// The password to generate a hash value for.
        /// The hash value for  as a base-64-encoded string.
        ///  is null.
        public static string HashPassword(string password)
        {
            if (password == null)
            {
                throw new ArgumentNullException(nameof(password));
            }

            return HashPasswordInternal(password);
        }

        /// 
        /// Determines whether the specified RFC 2898 hash and password are a cryptographic match.
        /// 
        /// The previously-computed RFC 2898 hash value as a base-64-encoded string.
        /// The plaintext password to cryptographically compare with hashedPassword.
        /// true if the hash value is a cryptographic match for the password; otherwise, false.
        /// 
        ///  must be of the format of HashPassword (salt + Hash(salt+input).
        /// 
        /// 
        ///  or  is null.
        /// 
        public static bool VerifyHashedPassword(string hashedPassword, string password)
        {
            if (hashedPassword == null)
            {
                throw new ArgumentNullException(nameof(hashedPassword));
            }
            if (password == null)
            {
                throw new ArgumentNullException(nameof(password));
            }

            return VerifyHashedPasswordInternal(hashedPassword, password);
        }

        private static readonly RandomNumberGenerator _rng = RandomNumberGenerator.Create();

        private static string HashPasswordInternal(string password)
        {
            var bytes = HashPasswordInternal(password, KeyDerivationPrf.HMACSHA256, PBKDF2IterCount, SaltSize, PBKDF2SubkeyLength);
            return Convert.ToBase64String(bytes);
        }

        private static byte[] HashPasswordInternal(
            string password,
            KeyDerivationPrf prf,
            int iterCount,
            int saltSize,
            int numBytesRequested)
        {
            // Produce a version 3 (see comment above) text hash.
            var salt = new byte[saltSize];
            _rng.GetBytes(salt);
            var subkey = KeyDerivation.Pbkdf2(password, salt, prf, iterCount, numBytesRequested);

            var outputBytes = new byte[13 + salt.Length + subkey.Length];

            // Write format marker.
            outputBytes[0] = 0x01;

            // Write hashing algorithm version.
            WriteNetworkByteOrder(outputBytes, 1, (uint)prf);

            // Write iteration count of the algorithm.
            WriteNetworkByteOrder(outputBytes, 5, (uint)iterCount);

            // Write size of the salt.
            WriteNetworkByteOrder(outputBytes, 9, (uint)saltSize);

            // Write the salt.
            Buffer.BlockCopy(salt, 0, outputBytes, 13, salt.Length);

            // Write the subkey.
            Buffer.BlockCopy(subkey, 0, outputBytes, 13 + saltSize, subkey.Length);
            return outputBytes;
        }

        private static bool VerifyHashedPasswordInternal(string hashedPassword, string password)
        {
            var decodedHashedPassword = Convert.FromBase64String(hashedPassword);

            if (decodedHashedPassword.Length == 0)
            {
                return false;
            }

            try
            {
                // Verify hashing format.
                if (decodedHashedPassword[0] != 0x01)
                {
                    // Unknown format header.
                    return false;
                }

                // Read hashing algorithm version.
                var prf = (KeyDerivationPrf)ReadNetworkByteOrder(decodedHashedPassword, 1);

                // Read iteration count of the algorithm.
                var iterCount = (int)ReadNetworkByteOrder(decodedHashedPassword, 5);

                // Read size of the salt.
                var saltLength = (int)ReadNetworkByteOrder(decodedHashedPassword, 9);

                // Verify the salt size: >= 128 bits.
                if (saltLength = 128 bits.
                var subkeyLength = decodedHashedPassword.Length - 13 - salt.Length;
                if (subkeyLength > 24);
            buffer[offset + 1] = (byte)(value >> 16);
            buffer[offset + 2] = (byte)(value >> 8);
            buffer[offset + 3] = (byte)(value >> 0);
        }

        // Compares two byte arrays for equality.
        // The method is specifically written so that the loop is not optimized.
        [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
        private static bool ByteArraysEqual(byte[] a, byte[] b)
        {
            if (ReferenceEquals(a, b))
            {
                return true;
            }

            if (a == null || b == null || a.Length != b.Length)
            {
                return false;
            }

            var areSame = true;
            for (var i = 0; i 

简单说明,网站用户密码加密就使用该方法,这段代码是从开源nuget包CryptoHelper中扒下来的。

雪花算法实现

在类库中添加IDCode文件夹,在IDCode文件夹中继续添加Snowflake文件夹,该文件夹下新建三个类:DisposableAction.cs、IdWorker.cs、TimeExtensions.cs
DisposableAction.cs:

using System;

namespace MS.Common.IDCode
{
    public class DisposableAction : IDisposable
    {
        readonly Action _action;

        public DisposableAction(Action action)
        {
            if (action == null)
                throw new ArgumentNullException("action");
            _action = action;
        }

        public void Dispose()
        {
            _action();
        }
    }
}

IdWorker.cs:

/** Copyright 2010-2012 Twitter, Inc.*/
/**
 * An object that generates IDs.
 * This is broken into a separate class in case
 * we ever want to support multiple worker threads
 * per process
 */
using System;

namespace MS.Common.IDCode
{
    public class IdWorker
    {
        //基准时间
        public const long Twepoch = 1288834974657L;
        //机器标识位数
        const int WorkerIdBits = 5;
        //数据标志位数
        const int DatacenterIdBits = 5;
        //序列号识位数
        const int SequenceBits = 12;
        //机器ID最大值
        const long MaxWorkerId = -1L ^ (-1L  MaxWorkerId || workerId  MaxDatacenterId || datacenterId 

TimeExtensions.cs:

using System;

namespace MS.Common.IDCode
{
    public static class TimeExtensions
    {
        public static Func currentTimeFunc = InternalCurrentTimeMillis;

        public static long CurrentTimeMillis()
        {
            return currentTimeFunc();
        }

        public static IDisposable StubCurrentTime(Func func)
        {
            currentTimeFunc = func;
            return new DisposableAction(() =>
            {
                currentTimeFunc = InternalCurrentTimeMillis;
            });
        }

        public static IDisposable StubCurrentTime(long millis)
        {
            currentTimeFunc = () => millis;
            return new DisposableAction(() =>
            {
                currentTimeFunc = InternalCurrentTimeMillis;
            });
        }

        private static readonly DateTime Jan1st1970 = new DateTime
           (1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);

        private static long InternalCurrentTimeMillis()
        {
            return (long)(DateTime.UtcNow - Jan1st1970).TotalMilliseconds;
        }
    }
}

说明:这部分代码是从snowflake-net中扒来的,使用方法readme里也有,注意应尽量保证全局单例的情况下使用该方法生成ID

总结/说明

  • 主要添加了一些常用扩展方法(我自己常用的)
  • 实际上传至github项目中还有封装的guid、随机数方法,没有在文中贴出来,有兴趣可以去项目中查看,在IDCode-Guid、IDCode-Random中

ASP.NET Core搭建多层网站架构【2-公共基础库】

标签:fun   问题   off   用户   write   match   hashcode   isp   lambda表达式   

原文地址:https://www.cnblogs.com/kasnti/p/12237686.html


评论


亲,登录后才可以留言!