基於ASP.NET WEB API實現分散式數據訪問中間層(提供對資料庫的CRUD)

来源:http://www.cnblogs.com/zuowj/archive/2017/12/14/8039891.html
-Advertisement-
Play Games

一些小的C/S項目(winform、WPF等),因需要訪問操作資料庫,但又不能把DB連接配置在客戶端上,原因有很多,可能是DB連接無法直接訪問,或客戶端不想安裝各種DB訪問組件,或DB連接不想暴露在客戶端(即使加密連接字元串仍有可能被破解的情況),總之都是出於安全考慮,同時因項目小,也無需採用分散式 ...


一些小的C/S項目(winform、WPF等),因需要訪問操作資料庫,但又不能把DB連接配置在客戶端上,原因有很多,可能是DB連接無法直接訪問,或客戶端不想安裝各種DB訪問組件,或DB連接不想暴露在客戶端(即使加密連接字元串仍有可能被破解的情況),總之都是出於安全考慮,同時因項目小,也無需採用分散式架構來將業務操作封裝到服務端,但又想保證客戶端業務的正常處理,這時我們就可以利用ASP.NET WEB API框架開發一個簡單的提供對資料庫的直接操作(CRUD)框架,簡稱為:分散式數據訪問中間層。

實現方案很簡單,就是利用ASP.NET WEB API框架編寫於一個DataController,然後在DataController分別實現CRUD相關的公開ACTION方法即可,具體實現代碼如下:(因為邏輯簡單,一看就懂,故下麵不再詳細說明邏輯,文末會有一些總結)

ASP.NET WEB API服務端相關核心代碼:

1.DataController代碼:

    [SqlInjectionFilter]
    [Authorize]
    public class DataController : ApiController
    {

        [AllowAnonymous]
        [HttpPost]
        public ApiResultInfo Login([FromBody]string[] loginInfo)
        {
            ApiResultInfo loginResult = null;
            try
            {
                if (loginInfo == null || loginInfo.Length != 4)
                {
                    throw new Exception("登錄信息不全。");
                }

                using (var da = BaseUtil.CreateDataAccess())
                {
                    if (用戶名及密碼判斷邏輯)
                    {
                        throw new Exception("登錄名或密碼錯誤。");
                    }
                    else
                    {
                        string token = Guid.NewGuid().ToString("N");
                        HttpRuntime.Cache.Insert(Constants.CacheKey_SessionTokenPrefix + token, loginInfo[0], null, Cache.NoAbsoluteExpiration, TimeSpan.FromHours(1));
                    
                        //登錄成功後需要處理的邏輯


                        loginResult = ApiResultInfo.BuildOKResult(token);
                    }
                }
            }
            catch (Exception ex)
            {
                LogUitl.Error(ex, "Api.Data.Login", BaseUtil.SerializeToJson(loginInfo));
                loginResult = ApiResultInfo.BuildErrResult("LoginErr", ex.Message);
            }

            return loginResult;
        }

        [HttpPost]
        public ApiResultInfo LogOut([FromBody] string token)
        {
            try
            {
                if (!string.IsNullOrEmpty(token))
                {
                    if (HttpRuntime.Cache[Constants.CacheKey_SessionTokenPrefix + token] != null)
                    {
                        HttpRuntime.Cache.Remove(token);
                    }

                    using (var da = BaseUtil.CreateDataAccess())
                    {
                        //登出後需要處理的邏輯
                    }
                }
            }
            catch
            { }

            return ApiResultInfo.BuildOKResult();
        }


        [HttpPost]
        public ApiResultInfo GetValue([FromBody]SqlCmdInfo sqlCmd)
        {
            using (var da = BaseUtil.CreateDataAccess())
            {
                var result = da.ExecuteScalar<string>(sqlCmd.SqlCmdText, sqlCmd.GetCommandType(), sqlCmd.Parameters.TryToArray());
                return ApiResultInfo.BuildOKResult(result);
            }

        }


        [Compression]
        [HttpPost]
        public ApiResultInfo GetDataSet([FromBody]SqlCmdInfo sqlCmd)
        {
            using (var da = BaseUtil.CreateDataAccess())
            {
                var ds = da.ExecuteDataSet(sqlCmd.SqlCmdText, sqlCmd.GetCommandType(), sqlCmd.Parameters.TryToArray());
                return ApiResultInfo.BuildOKResult(ds);
            }
        }

        [Compression]
        [HttpPost]
        public ApiResultInfo GetDataTable([FromBody]SqlCmdInfo sqlCmd)
        {
            using (var da = BaseUtil.CreateDataAccess())
            {
                var table = da.ExecuteDataTable(sqlCmd.SqlCmdText, sqlCmd.GetCommandType(), sqlCmd.Parameters.TryToArray());
                return ApiResultInfo.BuildOKResult(table);
            }
        }


        [HttpPost]
        public ApiResultInfo ExecuteCommand([FromBody]SqlCmdInfo sqlCmd)
        {
            using (var da = BaseUtil.CreateDataAccess())
            {
                int result = da.ExecuteCommand(sqlCmd.SqlCmdText, sqlCmd.GetCommandType(), sqlCmd.Parameters.TryToArray());
                return ApiResultInfo.BuildOKResult(result);
            }
        }

        [HttpPost]
        public ApiResultInfo BatchExecuteCommand([FromBody] IEnumerable<SqlCmdInfo> sqlCmds)
        {
            using (var da = BaseUtil.CreateDataAccess())
            {
                int execCount = 0;
                da.UseTransaction();
                foreach (var sqlCmd in sqlCmds)
                {
                    execCount += da.ExecuteCommand(sqlCmd.SqlCmdText, sqlCmd.GetCommandType(), sqlCmd.Parameters.TryToArray());
                }
                da.Commit();
                return new ApiResultInfo(execCount > 0);
            }
        }


        [HttpPost]
        public async Task<ApiResultInfo> ExecuteCommandAsync([FromBody]SqlCmdInfo sqlCmd)
        {
            return await Task.Factory.StartNew((arg) =>
             {
                 var sqlCmdObj = arg as SqlCmdInfo;
                 string connName = BaseUtil.GetDbConnectionName(sqlCmdObj.DbType);
                 using (var da = BaseUtil.CreateDataAccess(connName))
                 {
                     try
                     {
                         int result = da.ExecuteCommand(sqlCmdObj.SqlCmdText, sqlCmdObj.GetCommandType(), sqlCmdObj.Parameters.TryToArray());
                         return ApiResultInfo.BuildOKResult(result);
                     }
                     catch (Exception ex)
                     {
                         LogUitl.Error(ex, "Api.Data.ExecuteCommandAsync", BaseUtil.SerializeToJson(sqlCmdObj));

                         return ApiResultInfo.BuildErrResult("ExecuteCommandAsyncErr", ex.Message,
                                new Dictionary<string, object> { { "StackTrace", ex.StackTrace } });
                     }
                 }
             }, sqlCmd);
        }

        [HttpPost]
        public IHttpActionResult SaveLog([FromBody]string[] logInfo)
        {
            if (logInfo == null || logInfo.Length < 3)
            {
                return Ok();
            }

            string[] saveLogInfo = new string[7];
            for (int i = 1; i < logInfo.Length; i++)
            {
                if (saveLogInfo.Length > i + 1)
                {
                    saveLogInfo[i] = logInfo[i];
                }
            }


            switch (saveLogInfo[0].ToUpperInvariant())
            {
                case "ERR":
                    {
                        LogUitl.Error(saveLogInfo[1], saveLogInfo[2], saveLogInfo[3], saveLogInfo[4], saveLogInfo[5], saveLogInfo[6]);
                        break;
                    }
                case "WARN":
                    {
                        LogUitl.Warn(saveLogInfo[1], saveLogInfo[2], saveLogInfo[3], saveLogInfo[4], saveLogInfo[5], saveLogInfo[6]);
                        break;
                    }
                case "INFO":
                    {
                        LogUitl.Info(saveLogInfo[1], saveLogInfo[2], saveLogInfo[3], saveLogInfo[4], saveLogInfo[5], saveLogInfo[6]);
                        break;
                    }
            }

            return Ok();

        }



    }

 2.SqlInjectionFilterAttribute (防止SQL註入、危險關鍵字攻擊過濾器)

    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
    public class SqlInjectionFilterAttribute : ActionFilterAttribute
    {
        public override void OnActionExecuting(System.Web.Http.Controllers.HttpActionContext actionContext)
        {
            if (actionContext.ActionArguments.ContainsKey("sqlCmd"))
            {
                var sqlCmd = actionContext.ActionArguments["sqlCmd"] as SqlCmdInfo;
                if (BaseUtil.IsIncludeDangerSql(sqlCmd.SqlCmdText))
                {
                    throw new Exception("存在SQL註入風險,禁止操作!");
                }
            }

            base.OnActionExecuting(actionContext);
        }
    }

IsIncludeDangerSql:判斷是否包含危險關鍵字

        /// <summary>
        /// 判斷是否包含危險的SQL關鍵詞
        /// </summary>
        /// <param name="sqlCmdText"></param>
        /// <returns>包含返回true,否則false</returns>
        public static bool IsIncludeDangerSql(string sqlCmdText)
        {
            if (string.IsNullOrWhiteSpace(sqlCmdText)) return false;

            sqlCmdText = sqlCmdText.Replace("[", " ").Replace("]", " ");

            //string dangerSqlObjs = @"sys\.columns|sys\.tables|sys\.views|sys\.objects|sys\.procedures|sys\.indexes|INFORMATION_SCHEMA\.TABLES|INFORMATION_SCHEMA\.VIEWS|INFORMATION_SCHEMA\.COLUMNS|GRANT|DENY|SP_HELP|SP_HELPTEXT";
            //dangerSqlObjs += @"|object_id|syscolumns|sysobjects|sysindexes|drop\s+\w+|alter\s+\w+|create\s+\w+";

            string dangerSqlObjs = @"sys\.\w+|INFORMATION_SCHEMA\.\w+|GRANT|DENY|SP_HELP|SP_HELPTEXT|sp_executesql";
            dangerSqlObjs += @"|object_id|syscolumns|sysobjects|sysindexes|exec\s+\(.+\)|(create|drop|alter)\s+(database|table|index|procedure|view|trigger)\s+\w+(?!#)";

            string patternStr = string.Format(@"(^|\s|,|\.)({0})(\s|,|\(|;|$)", dangerSqlObjs);
            bool mathed = Regex.IsMatch(sqlCmdText, patternStr, RegexOptions.IgnoreCase);
            if (mathed)
            {
                //TODO:記錄到危險請求表中,以便後續追查
                LogUitl.Warn("檢測到包含危險的SQL關鍵詞語句:" + sqlCmdText, "IsIncludeDangerSql");
            }

            return mathed;
        }

3.SqlCmdInfo (ACTION參數對象,SQL命令信息類)

    [Serializable]
    public class SqlCmdInfo
    {
        public string SqlCmdText { get; set; }

        public ArrayList Parameters { get; set; }

        public bool IsSPCmdType { get; set; }

        public int DbType { get; set; }

        public CommandType GetCommandType()
        {
            return IsSPCmdType ? CommandType.StoredProcedure : CommandType.Text;
        }
    }

4.CompressionAttribute(壓縮返回內容過濾器,當返回的是大量數據時,可以標記該過濾器,以便提高響應速度)

    /// <summary>
    /// 壓縮返回信息
    /// </summary>
    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
    public class CompressionAttribute : ActionFilterAttribute
    {
        public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
        {
            var content = actionExecutedContext.Response.Content;

            #region 根據請求是否壓縮,暫時不用
            ////var acceptEncoding = actionExecutedContext.Request.Headers.AcceptEncoding.
            ////    Where(x => x.Value == "gzip" || x.Value == "deflate").ToList();
            ////if (acceptEncoding.HasItem() && content != null && actionExecutedContext.Request.Method != HttpMethod.Options)
            ////{
            ////    var first = acceptEncoding.FirstOrDefault();
            ////    if (first != null)
            ////    {
            ////        var bytes = content.ReadAsByteArrayAsync().Result;
            ////        switch (first.Value)
            ////        {
            ////            case "gzip":
            ////                actionExecutedContext.Response.Content = new ByteArrayContent(CompressionHelper.GZipBytes(bytes));
            ////                actionExecutedContext.Response.Content.Headers.Add("Content-Encoding", "gzip");
            ////                break;
            ////            case "deflate":
            ////                actionExecutedContext.Response.Content = new ByteArrayContent(CompressionHelper.DeflateBytes(bytes));
            ////                actionExecutedContext.Response.Content.Headers.Add("Content-encoding", "deflate");
            ////                break;
            ////        }
            ////    }
            ////}

            #endregion

            //只要使用了CompressionAttribute,則預設使用GZIP壓縮
            var bytes = content.ReadAsByteArrayAsync().Result;
            actionExecutedContext.Response.Content = new ByteArrayContent(CompressionHelper.GZipBytes(bytes));
            actionExecutedContext.Response.Content.Headers.Add("Content-Encoding", "gzip");

            base.OnActionExecuted(actionExecutedContext);
        }
    }
    /// <summary>
    /// 壓縮幫助類
    /// </summary>
    internal static class CompressionHelper
    {
        public static byte[] DeflateBytes(byte[] bytes)
        {
            if (bytes == null || bytes.Length == 0)
            {
                return null;
            }
            using (var output = new MemoryStream())
            {
                using (var compressor = new DeflateStream(output, CompressionMode.Compress, false))
                {
                    compressor.Write(bytes, 0, bytes.Length);
                }
                return output.ToArray();
            }
        }

        public static byte[] GZipBytes(byte[] bytes)
        {
            if (bytes == null || bytes.Length == 0)
            {
                return null;
            }
            using (var output = new MemoryStream())
            {
                using (var compressor = new GZipStream(output, CompressionMode.Compress, false))
                {
                    compressor.Write(bytes, 0, bytes.Length);
                }
                return output.ToArray();
            }
        }
    }

 5.RequestAuthenticationHandler (驗證請求合法性處理管道(包含請求內容解密),即:未正確登錄則不能調API操作資料庫)

    public class RequestAuthenticationHandler : DelegatingHandler
    {
        private const string rsaPrivateKey = "私鑰字元串";
        protected async override System.Threading.Tasks.Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
        {
            try
            {
                //驗證TOKEN
                HttpRequestHeaders headers = request.Headers;
                IEnumerable<string> tokenHeaders = null;
                if (headers.TryGetValues("AccessToken", out tokenHeaders) && tokenHeaders.Any())
                {

                    string loginID = TokenVerification(tokenHeaders.ElementAt(0));

                    if (!string.IsNullOrEmpty(loginID))
                    {
                        var principal = new GenericPrincipal(new GenericIdentity(loginID, "token"), null);
                        Thread.CurrentPrincipal = principal;
                        if (HttpContext.Current != null)
                        {
                            HttpContext.Current.User = principal;
                        }
                    }
                }

                IEnumerable<string> encryptHeaders=null;
                if (headers.TryGetValues("Encryption", out encryptHeaders) && encryptHeaders.Any())
                {
                    if (encryptHeaders.ElementAt(0) == "1")
                    {
                        //私鑰解密請求體內容
                        var originContent = request.Content;
                        string requestData = await request.Content.ReadAsStringAsync();

                        string deContentStr = EncryptUtility.RSADecrypt(rsaPrivateKey, requestData);
                        request.Content = new StringContent(deContentStr);

                        request.Content.Headers.Clear();
                        foreach (var header in originContent.Headers)
                        {
                            request.Content.Headers.Add(header.Key, header.Value);
                        }
                    }
                }

            }
            catch (Exception ex)
            {
                LogUitl.Error(ex, "Api.RequestAuthenticationHandler");
            }

            HttpResponseMessage response = await base.SendAsync(request, cancellationToken);

            return response;
        }

        private string TokenVerification(string token)
        {
            if (string.IsNullOrEmpty(token))
            {
                return null;
            }

            string loginID = null;
            if (HttpRuntime.Cache[Constants.CacheKey_SessionTokenPrefix + token] == null) //如果過期,則嘗試從DB中恢復授權狀態
            {
                using (var da = BaseUtil.CreateDataAccess())
                {
                    //loginID = 根據Token獲取登錄用戶ID邏輯
                    if (!string.IsNullOrEmpty(loginID))
                    {
                        HttpRuntime.Cache.Insert(Constants.CacheKey_SessionTokenPrefix + token, loginID, null, Cache.NoAbsoluteExpiration, TimeSpan.FromHours(1));
                    }
                }
            }
            else
            {
                loginID = HttpRuntime.Cache[Constants.CacheKey_SessionTokenPrefix + token].ToNotNullString();
            }

            return loginID;

        }

    }

 6.HandleExceptionFilterAttribute(全局異常處理過濾器,只要某個ACTION發生異常就會報被該過濾器捕獲並處理)

    /// <summary>
    /// 統一全局異常過濾處理
    /// </summary>
    public class HandleExceptionFilterAttribute : ExceptionFilterAttribute
    {
        public override void OnException(HttpActionExecutedContext actionExecutedContext)
        {
            string ctrllerName = actionExecutedContext.ActionContext.ControllerContext.ControllerDescriptor.ControllerName;
            string actionName = actionExecutedContext.ActionContext.ActionDescriptor.ActionName;
            string sqlCmd = null;
            if (actionExecutedContext.ActionContext.ActionArguments.ContainsKey("sqlCmd"))
            {
                sqlCmd = BaseUtil.SerializeToJson(actionExecutedContext.ActionContext.ActionArguments["sqlCmd"] as SqlCmdInfo);
            }

            //記錄到日誌表中
            LogUitl.Error(actionExecutedContext.Exception.Message, "Api.HandleExceptionFilterAttribute",
                            string.Format("SqlCmdInfo:{0};StackTrace:{1}", sqlCmd, actionExecutedContext.Exception.StackTrace));

            var errResult = new ApiResultInfo(false, sqlCmd, actionName + "Err", actionExecutedContext.Exception.Message);
            errResult.ExtendedData["StackTrace"] = actionExecutedContext.Exception.StackTrace;

            actionExecutedContext.Response = actionExecutedContext.ActionContext.Request.CreateResponse(HttpStatusCode.OK, errResult, "application/json");

        }
    }

7.ApiResultInfo(API返回結果實體類)

    [Serializable]
    public class ApiResultInfo
    {
        public bool Stauts { get; set; }

        public object Data { get; set; }

        public string ErrCode { get; set; }

        public string ErrMsg { get; set; }

        public Dictionary<string, object> ExtendedData { get; set; }


        public ApiResultInfo()
        {
            this.ExtendedData = new Dictionary<string, object>();
        }


        public ApiResultInfo(bool status, object data = null, string errCode = null, string errMsg = null, Dictionary<string, object> extData = null)
        {
            this.Stauts = status;
            this.Data = data;
            this.ErrCode = errCode;
            this.ErrMsg = errMsg;
            this.ExtendedData = extData;
            if (this.ExtendedData == null)
            {
                this.ExtendedData = new Dictionary<string, object>();
            }
        }

        /// <summary>
        /// 構建成功結果對象
        /// </summary>
        /// <param name="data"></param>
        /// <param name="extData"></param>
        /// <returns></returns>
        public static ApiResultInfo BuildOKResult(object data = null, Dictionary<string, object> extData = null)
        {
            return new ApiResultInfo(true, data, extData: extData);
        }

        /// <summary>
        /// 構建錯誤結果對象
        /// </summary>
        /// <param name="errCode"></param>
        /// <param name="errMsg"></param>
        /// <param name="extData"></param>
        /// <returns></returns>
        public static ApiResultInfo BuildErrResult(string errCode = null, string errMsg = null, Dictionary<string, object> extData = null)
        {
            return new ApiResultInfo(false, errCode: errCode, errMsg: errMsg, extData: extData);
        }
    }

 8.非對稱加解密演算法(允許客戶端請求時進行公鑰加密請求內容,然後服務端API中通過RequestAuthenticationHandler自定義驗證管道解密請求內容) 

        /// <summary>
        /// 生成公鑰及私鑰對
        /// </summary>
        /// <param name="publickey"></param>
        /// <param name="privatekey"></param>
        public static void GeneratePublicAndPrivateKey(out string publickey, out string privatekey)
        {
            RSACryptoServiceProvider crypt = new RSACryptoServiceProvider();
            publickey = crypt.ToXmlString(false);//公鑰
            privatekey = crypt.ToXmlString(true);//私鑰
        }


        /// <summary>
        /// 分段使用公鑰加密
        /// </summary>
        /// <param name="publicKey"></param>
        /// <param name="rawInput"></param>
        /// <returns></returns>
        public static string RSAEncrypt(string publicKey, string rawInput)
        {
            if (string.IsNullOrEmpty(rawInput))
            {
                return string.Empty;
            }

            if (string.IsNullOrWhiteSpace(publicKey))
            {
                throw new ArgumentException("Invalid Public Key");
            }

            using (var rsaProvider = new RSACryptoServiceProvider())
            {
                var inputBytes = Encoding.UTF8.GetBytes(rawInput);//有含義的字元串轉化為位元組流
                rsaProvider.FromXmlString(publicKey);//載入公鑰
                int bufferSize = (rsaProvider.KeySize / 8) - 11;//單塊最大長度
                var buffer = new byte[bufferSize];
                using (MemoryStream inputStream = new MemoryStream(inputBytes),
                     outputStream = new MemoryStream())
                {
                    while (true)
                    { //分段加密
                        int readSize = inputStream.Read(buffer, 0, bufferSize);
                        if (readSize <= 0)
                        {
                            break;
                        }

                        var temp = new byte[readSize];
                        Array.Copy(buffer, 0, temp, 0, readSize);
                        var encryptedBytes = rsaProvider.Encrypt(temp, false);
                        outputStream.Write(encryptedBytes, 0, encryptedBytes.Length);
                    }
                    return Convert.ToBase64String(outputStream.ToArray());//轉化為位元組流方便傳輸
                }
            }
        }


        /// <summary>
        /// 分段使用私鑰解密
        /// </summary>
        /// <param name="privateKey"></param>
        /// <param name="encryptedInput"></param>
        /// <returns></returns>
        public static string RSADecrypt(string privateKey, string encryptedInput)
        {
            if (string.IsNullOrEmpty(encryptedInput))
            {
                return string.Empty;
            }

            if (string.IsNullOrWhiteSpace(privateKey))
            {
                throw new ArgumentException("Invalid Private Key");
            }

            using (var rsaProvider = new RSACryptoServiceProvider())
            {
                var inputBytes = Convert.FromBase64String(encryptedInput);
                rsaProvider.FromXmlString(privateKey);
                int bufferSize = rsaProvider.KeySize / 8;
                var buffer = new byte[bufferSize];
                using (MemoryStream inputStream = new MemoryStream(inputBytes),
                     outputStream = new MemoryStream())
                {
                    while (true)
                    {
                        int readSize = inputStream.Read(buffer, 0, bufferSize);
                        if (readSize <= 0)
                        {
                            break;
                        }

                        var temp = new byte[readSize];
                        Array.Copy(buffer, 0, temp, 0, readSize);
                        var rawBytes = rsaProvider.Decrypt(temp, false);
                        outputStream.Write(rawBytes, 0, rawBytes.Length);
                    }
                    return Encoding.UTF8.GetString(outputStream.ToArray());
                }
            }
        }

9.LogUitl(基於NLOG.MONGO組件簡單封裝實現MONGODB日誌功能)--後期有機會再單獨講MONGODB的相關知識 

    public static class LogUitl
    {
        private static NLog.Logger _Logger = null;
        private const string cacheKey_NLogConfigFlag = "NLogConfigFlag";
        private static Logger GetLogger()
        {
            if (_Logger == null || HttpRuntime.Cache[cacheKey_NLogConfigFlag] == null)
            {
                LoggingConfiguration config = new LoggingConfiguration();

                string connSetStr = ConfigUtility.GetAppSettingValue("MongoDbConnectionSet");

                MongoTarget mongoTarget = new MongoTarget();
                mongoTarget.ConnectionString = EncryptUtility.Decr

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1.1 流程式控制制之for迴圈 1 迭代式迴圈:for,語法如下 for i in range(10): 縮進的代碼塊 2 break與continue(同上) 3 迴圈嵌套 for i in range(1,10): for j in range(1,i+1): print('%s*%s=%s' % ...
  • 第一步: 創建一個SpringBoot應用 第二步: 創建一個實體,用來存儲數據,在src/main/java/com/example/first下創建包entity , 在entity下創建Person.java文件 第三步: 創建resource, 在src/main/java/com/exam ...
  • 前言 在golang中,只需要在函數調用前加上關鍵字go即可創建一個併發任務單元,而這個新建的任務會被放入隊列中,等待調度器安排。相比系統的MB級別線程棧,goroutine的自定義棧只有2KB,這使得我們能夠輕易創建上萬個併發任務,如此對性能提升不少。但隨之而來的有以下幾個問題: "如何等待所有g ...
  • 第一篇... List列表 ...
  • 在web.config中的system.web 節點下添加如下代碼: ...
  • 當我們在VS的C++中使用vector、list、map等這些STL容器,在開啟調試的時候可以看到這樣的信息: 然而在我們自己手寫鏈表,調試的時候卻要像這樣一級一級展開,很是麻煩。 有時候會想,如果要能像STL裡面的list那樣子直接顯示出來就方便許多。經過幾番尋找,終於被我找到了方法。 使用 .n ...
  • 開源bitcms內容管理系統,經過幾個版本的更新和客戶的使用已經基本上完善了。採用Entity Framework 6 + MVC5開發。資料庫由原來的Sqlite升級為mysql,主要考慮系統要適用中小型系統。 ...
  • 1.首先創見Core控制台應用程式 並且引入 AspNetCore.All 首先我們寫入配置信息:直接代碼如下 接下來是讀取 我們上面寫入的配置信息 //GetSection 讀取對象類型的配置信息 Bind 綁定信息 var user = new User(); Console.WriteLine ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...