1. 程式人生 > 實用技巧 >netCore3.1專案搭建過程記錄-省的每次都傻乎乎的不知道應該先幹啥

netCore3.1專案搭建過程記錄-省的每次都傻乎乎的不知道應該先幹啥

十年河東,十年河西,莫欺少年窮

學無止境,精益求精

我搭建專案的習慣,一般先搭建專案整體的層次劃分,首先貼出我搭建專案的各個層次劃分:

專案分為8層,資料庫層採用EfCore 結合 sqlSugar的方式,從上到下依次為:公共類層、EfCore上下文層、SqlSugar上下文層,業務實體層、sqlSugar實體層【由工具生成】、介面層、服務層、web站點【webApi屬於這一層】,各層次之間的引用,我就不累述了。

清晰了專案的各層次,下面我們逐步完善這個裸體專案

1、搭建你的資料庫上下文層

1.1、搭建EfCore上下文

1、開啟程式包管理控制檯,選中EFCoreContext層,並依次執行如下控制命令

 Install-Package Microsoft.EntityFrameworkCore
 Install-Package Microsoft.EntityFrameworkCore.SqlServer
 Install-Package Microsoft.EntityFrameworkCore.Tools
 Install-Package Microsoft.VisualStudio.Web.CodeGeneration.Design -Version 3.1.4

2、選中啟動專案webSite ,並依次執行上述控制命令

 Install-Package Microsoft.EntityFrameworkCore
 Install
-Package Microsoft.EntityFrameworkCore.SqlServer Install-Package Microsoft.EntityFrameworkCore.Tools Install-Package Microsoft.VisualStudio.Web.CodeGeneration.Design -Version 3.1.4

如下圖:

3、以上兩步驟執行完畢後,在程式包管理控制檯中繼續切換到EFCoreContext層,並執行如下指令生成資料庫上下文【注意,連線字串要改成你自己的】

4、執行成功後,生成的上下文如下:

我的資料庫指令碼請參考:

通用許可權管理【資料庫】設計方案

最後在啟動類中註冊SqlServer服務,如下:

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers();

            #region 註冊SQLSERVER
            services.AddDbContext<DbRoleManagerContext>(options =>
                 options.UseSqlServer(Configuration.GetConnectionString("WuAnDBContext")));
            #endregion

這樣的話,EFCore的上下文就生成了,因為netCore對原生SQL的支援不是特別好,因此,我的專案中引入了sqlSugar作為支援。下面我們來搭建sqlSugar的上下文。

1.2、搭建sqlSugar上下文及sqlsugar實體

沒有玩過SqlSugar的小虎斑可以參考這篇部落格:SqlSugar+SqlServer+NetCore3.1 入門案例

SqlSugar的工具【可通過專案/工具生成】大家可參考:http://www.codeisbug.com/Doc/8/1123或者 直接去CSDN 上下載相關工具/專案:https://download.csdn.net/download/wolongbb/12997789

1、在專案SqlSugarContext、SqlSugarModel 中,通過NuGet安裝sqlSugar引用 和 SqlSugar的依賴項Newtonsoft.Json V 12.0.3及System.Data.SqlClient V 4.8.2 版本

2、通過下載的專案,生成sqlSugar上下文及sqlsugar實體

注意:在下載的專案SoEasyPlatform-master中需要自行修改連線字串及名稱空間

我的sqlsugar上下文如下:

using SqlSugarModel.Enties;
using SqlSugar;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;

namespace SugarContext
{
    public class SugarDbContext
    {
        /// 獲取連線字串        
        private static string Connection = "Data Source=LAPTOP-P5GVS4UM;Initial Catalog=DbRoleManager;User ID=sa;Password=Aa123456";

        public SugarDbContext()
        {
            Db = new SqlSugarClient(new ConnectionConfig()
            {
                ConnectionString = Connection,
                DbType = DbType.SqlServer,
                InitKeyType = InitKeyType.Attribute,//從特性讀取主鍵和自增列資訊
                IsAutoCloseConnection = true,//開啟自動釋放模式和EF原理一樣我就不多解釋了

            });
            //調式程式碼 用來列印SQL 
            Db.Aop.OnLogExecuting = (sql, pars) =>
            {
                Console.WriteLine(sql + "\r\n" +
                    Db.Utilities.SerializeObject(pars.ToDictionary(it => it.ParameterName, it => it.Value)));
                Console.WriteLine();
            };

        }
        //注意:不能寫成靜態的
        public SqlSugarClient Db;//用來處理事務多表查詢和複雜的操作
    }
    public class SugarDbContext<T> where T : class, new()
    {
        public SugarDbContext()
        {
            Db = new SqlSugarClient(new ConnectionConfig()
            {
                ConnectionString = "Data Source=LAPTOP-P5GVS4UM;Initial Catalog=DbRoleManager;User ID=sa;Password=Aa123456",
                DbType = DbType.SqlServer,
                InitKeyType = InitKeyType.Attribute,//從特性讀取主鍵和自增列資訊
                IsAutoCloseConnection = true,//開啟自動釋放模式和EF原理一樣我就不多解釋了

            });
            //調式程式碼 用來列印SQL 
            Db.Aop.OnLogExecuting = (sql, pars) =>
            {
                Console.WriteLine(sql + "\r\n" +
                    Db.Utilities.SerializeObject(pars.ToDictionary(it => it.ParameterName, it => it.Value)));
                Console.WriteLine();
            };

        }
        //注意:不能寫成靜態的
        public SqlSugarClient Db;//用來處理事務多表查詢和複雜的操作
        public SimpleClient<T> CurrentDb { get { return new SimpleClient<T>(Db); } }//用來操作當前表的資料

        public SimpleClient<User_Dept> User_DeptDb { get { return new SimpleClient<User_Dept>(Db); } }//用來處理User_Dept表的常用操作
        public SimpleClient<User_MenuButton> User_MenuButtonDb { get { return new SimpleClient<User_MenuButton>(Db); } }//用來處理User_MenuButton表的常用操作
        public SimpleClient<User_Role> User_RoleDb { get { return new SimpleClient<User_Role>(Db); } }//用來處理User_Role表的常用操作
        public SimpleClient<User_Role_Dept> User_Role_DeptDb { get { return new SimpleClient<User_Role_Dept>(Db); } }//用來處理User_Role_Dept表的常用操作
        public SimpleClient<User_Role_MenuButton> User_Role_MenuButtonDb { get { return new SimpleClient<User_Role_MenuButton>(Db); } }//用來處理User_Role_MenuButton表的常用操作
        public SimpleClient<User_Account> User_AccountDb { get { return new SimpleClient<User_Account>(Db); } }//用來處理User_Account表的常用操作
        public SimpleClient<User_Account_Dept> User_Account_DeptDb { get { return new SimpleClient<User_Account_Dept>(Db); } }//用來處理User_Account_Dept表的常用操作
        public SimpleClient<User_Account_Role> User_Account_RoleDb { get { return new SimpleClient<User_Account_Role>(Db); } }//用來處理User_Account_Role表的常用操作


        /// <summary>
        /// 獲取所有
        /// </summary>
        /// <returns></returns>
        public virtual List<T> GetList()
        {
            return CurrentDb.GetList();
        }

        /// <summary>
        /// 根據表示式查詢
        /// </summary>
        /// <returns></returns>
        public virtual List<T> GetList(Expression<Func<T, bool>> whereExpression)
        {
            return CurrentDb.GetList(whereExpression);
        }


        /// <summary>
        /// 根據表示式查詢分頁
        /// </summary>
        /// <returns></returns>
        public virtual List<T> GetPageList(Expression<Func<T, bool>> whereExpression, PageModel pageModel)
        {
            return CurrentDb.GetPageList(whereExpression, pageModel);
        }

        /// <summary>
        /// 根據表示式查詢分頁並排序
        /// </summary>
        /// <param name="whereExpression">it</param>
        /// <param name="pageModel"></param>
        /// <param name="orderByExpression">it=>it.id或者it=>new{it.id,it.name}</param>
        /// <param name="orderByType">OrderByType.Desc</param>
        /// <returns></returns>
        public virtual List<T> GetPageList(Expression<Func<T, bool>> whereExpression, PageModel pageModel, Expression<Func<T, object>> orderByExpression = null, OrderByType orderByType = OrderByType.Asc)
        {
            return CurrentDb.GetPageList(whereExpression, pageModel, orderByExpression, orderByType);
        }


        /// <summary>
        /// 根據主鍵查詢
        /// </summary>
        /// <returns></returns>
        public virtual T GetById(dynamic id)
        {
            return CurrentDb.GetById(id);
        }

        /// <summary>
        /// 根據主鍵刪除
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public virtual bool Delete(dynamic id)
        {
            return CurrentDb.Delete(id);
        }


        /// <summary>
        /// 根據實體刪除
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public virtual bool Delete(T data)
        {
            return CurrentDb.Delete(data);
        }

        /// <summary>
        /// 根據主鍵刪除
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public virtual bool Delete(dynamic[] ids)
        {
            return CurrentDb.AsDeleteable().In(ids).ExecuteCommand() > 0;
        }

        /// <summary>
        /// 根據表示式刪除
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public virtual bool Delete(Expression<Func<T, bool>> whereExpression)
        {
            return CurrentDb.Delete(whereExpression);
        }


        /// <summary>
        /// 根據實體更新,實體需要有主鍵
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public virtual bool Update(T obj)
        {
            return CurrentDb.Update(obj);
        }

        /// <summary>
        ///批量更新
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public virtual bool Update(List<T> objs)
        {
            return CurrentDb.UpdateRange(objs);
        }

        /// <summary>
        /// 插入
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public virtual bool Insert(T obj)
        {
            return CurrentDb.Insert(obj);
        }


        /// <summary>
        /// 批量
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public virtual bool Insert(List<T> objs)
        {
            return CurrentDb.InsertRange(objs);
        }


        //自已擴充套件更多方法 
    }
}
View Code

這樣的話,sqlSugar上下文層及sqlsugar實體層就搭建完畢了,結果如下:

綜上所述,資料庫部分的搭建就完成了,有EFCore和sqlSugar,我們就可以玩轉資料庫的操作了。

2、搭建你的webSite層【Swagger,log4Net,Json web Token,異常捕獲中介軟體,Api路由等等】

2.1、搭建你的swagger並結合Jwt驗證,使你的Api更安全並易於管理及測試,更詳細內容可參考:NetCore3.1 如何新增帶有JWT Token 驗證的Swagger

1、開啟程式包管理控制檯,執行如下指令,安裝swagger相關引用

Install-Package Swashbuckle.AspNetCore -Version 5.0.0

2、安裝Swashbuckle.AspNetCore.Filters包 版本5.12

Install-Package Swashbuckle.AspNetCore.Filters -Version 5.1.2

3、Nuget安裝JwtBearer引用v 3.1.10版本

4、修改StartUp類如下:

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using EFCoreContext.Models;
using log4net;
using log4net.Config;
using log4net.Repository;
using LogicModel;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.Tokens;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.Filters;
using WebSite.Middlewares;

namespace WebSite
{
    public class Startup
    {
        public static ILoggerRepository repository { get; set; }
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
            // 指定配置檔案
            repository = LogManager.CreateRepository("NETCoreRepository");
            XmlConfigurator.Configure(repository, new FileInfo("Log4Net.config"));
        }


        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers();

            #region 註冊SQLSERVER
            services.AddDbContext<DbRoleManagerContext>(options =>
                 options.UseSqlServer(Configuration.GetConnectionString("WuAnDBContext")));
            #endregion

            #region JWT
            services.AddAuthentication(x =>
            {
                x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
            }).AddJwtBearer(x =>
            {
                x.RequireHttpsMetadata = false;
                x.SaveToken = true;
                x.TokenValidationParameters = new TokenValidationParameters
                {
                    ValidateIssuerSigningKey = true,
                    IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(TokenManagementModel.Secret)),
                    ValidIssuer = TokenManagementModel.Issuer,
                    ValidAudience = TokenManagementModel.Audience,
                    ValidateIssuer = false,
                    ValidateAudience = false
                };
            });
            #endregion

            #region 註冊Swagger服務
            // 註冊Swagger服務
            services.AddSwaggerGen(c =>
            {
                // 新增文件資訊
                c.SwaggerDoc("v1", new OpenApiInfo { Title = "許可權管理相關介面", Version = "V1" });
                //c.SwaggerDoc("demo", new OpenApiInfo { Title = "示例介面", Version = "demo" });
                c.DocInclusionPredicate((docName, apiDesc) => apiDesc.GroupName == docName.ToUpper());
                var basePath = Path.GetDirectoryName(typeof(Program).Assembly.Location);//獲取應用程式所在目錄(絕對,不受工作目錄影響,建議採用此方法獲取路徑)
                var xmlPath = Path.Combine(basePath, "WebSite.xml");
                c.IncludeXmlComments(xmlPath);
                #region Jwt
                //開啟許可權小鎖
                c.OperationFilter<AddResponseHeadersFilter>();
                c.OperationFilter<AppendAuthorizeToSummaryOperationFilter>();

                //在header中新增token,傳遞到後臺
                c.OperationFilter<SecurityRequirementsOperationFilter>();
                c.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme
                {
                    Description = "JWT授權(資料將在請求頭中進行傳遞)直接在下面框中輸入Bearer {token}(注意兩者之間是一個空格) \"",
                    Name = "Authorization",//jwt預設的引數名稱
                    In = ParameterLocation.Header,//jwt預設存放Authorization資訊的位置(請求頭中)
                    Type = SecuritySchemeType.ApiKey
                });


                #endregion
            });
            #endregion
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            //允許跨域
            app.UseCors(builder => builder.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod());

            #region 啟用Swagger中介軟體
            // 啟用Swagger中介軟體
            app.UseSwagger(c => c.RouteTemplate = "swagger/{documentName}/swagger.json");
            // 配置SwaggerUI
            app.UseSwaggerUI(c =>
            {
                c.SwaggerEndpoint($"/swagger/v1/swagger.json", "V1");
            });
            #endregion

            //註冊異常中介軟體
            app.UseMiddleware<ExceptionMiddlewares>();

            app.UseAuthentication();

            app.UseHttpsRedirection();

            app.UseRouting();

            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }
    }
}
View Code

Jwt金鑰類:

    /// <summary>
    /// POCO類,用來儲存簽發或者驗證jwt時用到的資訊
    /// </summary>
    public class TokenManagementModel
    {
        public static string Secret = "987654321987654321";//私鑰

        public static string Issuer = "webapi.cn";

        public static string Audience = "WebApi";

        public static int AccessExpiration = 180;//過期時間

        public static int RefreshExpiration = 180;//重新整理時間
    }
View Code

4、由於這塊內容在NetCore3.1 如何新增帶有JWT Token 驗證的Swagger中講解比較詳細,就不一步步演示了。

2.2、搭建你的Log4Net日誌並結合異常處理中介軟體,使你的系統輕而易舉的處理系統錯誤及異常,這塊內容可參考:NetCore 異常處理過濾器、中介軟體 、並整合Log4Net

1、首選通過Nuget引用Log4Net,如下:

然後在sutartUp的建構函式中註冊Log4Net

        public static ILoggerRepository repository { get; set; }
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
            // 指定配置檔案
            repository = LogManager.CreateRepository("NETCoreRepository");
            XmlConfigurator.Configure(repository, new FileInfo("Log4Net.config"));
        }

2、新增Log4Net日誌的XML配置檔案Log4Net.config

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <!-- This section contains the log4net configuration settings -->
    <log4net>
        <appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender">
            <layout type="log4net.Layout.PatternLayout" value="%date [%thread] %-5level %logger - %message%newline" />
        </appender>

        <appender name="RollingLogFileAppender" type="log4net.Appender.RollingFileAppender">
            <file value="Log\\LogInfo\\" />
            <appendToFile value="true" />
            <rollingStyle value="Composite" />
            <staticLogFileName value="false" />
            <datePattern value="yyyyMMdd'.log'" />
            <maxSizeRollBackups value="10" />
            <maximumFileSize value="5MB" />
            <layout type="log4net.Layout.PatternLayout">
                <conversionPattern value="%n異常時間:%d [%t] %n異常級別:%-5p &#xD;&#xA;異 常 類:%c [%x] %n%m %n" />
            </layout>
        </appender>

        <!-- Setup the root category, add the appenders and set the default level -->
        <root>
            <level value="ALL" />
            <appender-ref ref="ConsoleAppender" />
            <appender-ref ref="FileAppender" />
            <appender-ref ref="RollingLogFileAppender" />
        </root>

    </log4net>
</configuration>

截止到這兒,Log4Net就成功引入到專案中了。

3、在專案中新增異常處理中介軟體:ExceptionMiddlewares,結合Log4Net進行異常日誌記錄

using log4net;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace WebSite.Middlewares
{
    public class ExceptionMiddlewares
    {
        private ILog log;
        private readonly RequestDelegate next;
        private IHostingEnvironment environment;
        public ExceptionMiddlewares(RequestDelegate next, IHostingEnvironment environment, ILogger<ExceptionMiddlewares> logger)
        {
            this.log = LogManager.GetLogger(Startup.repository.Name, typeof(ExceptionMiddlewares));
            this.next = next;
            this.environment = environment;
        }

        public async Task Invoke(HttpContext context)
        {
            try
            {
                await next.Invoke(context);
                var features = context.Features;
            }
            catch (Exception e)
            {
                await HandleException(context, e);
            }
        }

        private async Task HandleException(HttpContext context, Exception e)
        {
            context.Response.StatusCode = 500;
            context.Response.ContentType = "text/json;charset=utf-8;";
            string error = "";

            if (environment.IsDevelopment())
            {
                var json = new { message = e.Message + "" + e.StackTrace + "" };
                log.Error(json);
                error = JsonConvert.SerializeObject(json);
            }
            else
            {
                var json = new { message = e.Message + "" + e.StackTrace + "" };
                log.Error(json);
                error = JsonConvert.SerializeObject(json); 
                error = "抱歉,出錯了";
            }
                

            await context.Response.WriteAsync(error);
        }
    }
}
View Code

4、在startup類中註冊異常中介軟體

            //註冊異常中介軟體
            app.UseMiddleware<ExceptionMiddlewares>();

5、這塊內容已詳細實現,可參考:NetCore 異常處理過濾器、中介軟體 、並整合Log4Net,這兒不再重複累述。

做個測試,寫個被除數為0 的程式,看看中介軟體能否捕獲異常,Log4Net能否記錄下異常資訊

Log4Net記錄的日誌如下:

3、繼續完善你搭建的專案【新增選項模式,封裝分頁類,統一返回值BaseResponse,MongoDB/Redis/MQ/快取/Jwt生成、驗證,等等】

寫到這兒,專案的基本搭建就完成了,關於:新增選項模式,封裝分頁類,統一返回值BaseResponse,MongoDB/Redis/MQ/快取/Jwt生成、驗證等等,有興趣的可以搜尋我的部落格,基本都有實現。

可參考:

NetCore讀取配置檔案,簡單實現。

NetCore3.1 使用 mongoDb 儲存日誌,提升查詢效率

訊息佇列 RocketMQ 併發量十萬級

Redis 的基礎資料型別

高併發時,使用Redis應注意的問題 及 Redis快取幫助類

EFcoe中如果通過日誌記錄Linq轉化的SQL語句

EFCore 封裝分頁功能

ASP.NET Core 中的響應快取 / ResponseCache

實時web應用方案——SignalR(.net core) 理論篇

NetCore 配置檔案---直接讀取及選項模式讀取

NetCore 基於identity的登入驗證授權機制

深入理解 NetCore 中的依賴注入的好處 及 、Singleton、Scoped、Transient 三種物件的差異

asp.net core 系列 5 專案實戰之:NetCore 的 async 和 await(參考自:Microsoft教程)

SqlSugar 用法大全

總結:

一個優秀的程式猿,不僅僅要會用框架,也應該會自己搭建一個框架,想成為架構師,這只是開始。

@天才臥龍的部落格