.Net 带你从0到1使用C#新建框架【保姆级】【一】

本文将使用C#从0到1带你新建一个简单的框架

注:本文不是最好的框架,只是用于给新手学习的简单框架,当然,小型项目用起来也没有问题。

首先,我们需要以下软件:

  1. Visual Studio 2022(本文将使用.Net 6,需要至少为Visual Studio 2022)
  2. SQLserver 2008 R2及以上
  3. Redis或Redis for Windows
  4. Navicat或SQL Server Management Studio(SSMS)
  5. 选择性使用:PDManer(PDMan数据库建模 (pdmaner.com)

其次,我们将使用以下开源组件

  1. Furion【依赖极少的开源组件,已经帮你完成了绝大部分的帮助类,包括:日志、Swagger,JWT鉴权等】(让 .NET 开发更简单,更通用,更流行。 Furion | Furion (baiqian.ltd)
  2. Sqlsugar【国产超级ORM,比EFCore更快,支持所有主流数据库和绝大部分国产数据库,支持百万数据写入和亿级查询,支持自动按照租户分库,支持自动分表分库】(SqlSugar .Net ORM 5.X 官网 、文档、教程 - SqlSugar 5x - .NET果糖网 (donet5.com)
  3. CSRedis【由FreeSQL作者开发的开源免费的Redis库,内置了常用的Redis帮助方法】(github.com/2881099/csr...
  4. LazyCaptcha【一个开源的图片验证库,支持多种图片验证码】 (LazyCaptcha: 仿EasyCaptcha的.net core 下的图形验证码 (gitee.com)
  5. IP2Region.Net【开源IP地址库,可以快速查询IP地址所属地】(ip2region/binding/csharp at master · lionsoul2014/ip2region (github.com)
  6. UAParser【开源UA信息读取库,解析当前用户所使用的浏览器、设备等信息】(github.com/ua-parser/u...
  7. Yitter.IdGenerator【雪花Id生成库,可以快速生成唯一数字Id】(yitter/IdGenerator: 💎多语言实现,高性能生成唯一数字ID。 💎优化的雪花算法(SnowFlake)------雪花漂移算法,在缩短ID长度的同时,具备极高瞬时并发处理能力(50W/0.1s)。 💎原生支持 C#/Java/Go/Rust/C/JavaScript/TypeScript/Python/Pascal 多语言,提供其它适用于其它语言的多线程安全调用动态库(FFI)。💎支持容器环境自动扩容(自动注册 WorkerId ),单机或分布式唯一IdGenerator。💎顶尖优化,超强效能。 (github.com)
  8. 选择性使用:Aliyun.OSS.NetCore或Tencent.QCloud.Cos.Sdk【阿里云或腾讯云对象存储SDK】

本文默认你已经学会了C#和Visual Studio的基础操作,如果你不会,建议先保存本文,然后先去学一下基础知识。

建立项目

  1. 项目分层()
名称 作用 引用层
YourProject.Core 此层为各种帮助方法 YourProject.Globals
YourProject.Globals 此层为全局使用的一些静态配置等
YourProject.Entities 此层为实体和传输对象 YourProject.Core,YourProject.Globals
YourProject.Service 此层为服务所在层 YourProject.Core,YourProject.Globals,YourProject.Entities
YourProject.Web.Core 此层用于API YourProject.Globals,YourProject.Web.Core,YourProject.Entities,YourProject.Service
YourProject.Web 此层仅用于启动和前端页面 YourProject.Globals,YourProject.Web.Core
YourProject.CodeFirst 【选用】此层仅用于使用CodeFirst时自动向数据库注入基础数据 YourProject.Globals,YourProject.Web.Core

开始项目

我们需要在YourProject.Web.Core新建类:Startup.cs

Csharp 复制代码
/// <summary>
/// 注册启动服务
/// </summary>
[AppStartup(1)]//《==此处为Furion中的启动配置,用于在配置启动时需要的注册
public class StartUp : AppStartup
{
    public void ConfigureService(IServiceCollection services)
    {
    
    }
    
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
    
    }
}

其次在YourProject.Web的Program.cs中,将所有代码注释后,写入:

Csharp 复制代码
Serve.Run();

接下来,我们需要配置SQLSugar,因为接下来的日志组件需要使用:

Csharp 复制代码
/// <summary>
/// Sqlsugar配置
/// </summary>
/// <typeparam name="T"></typeparam>
public class SugarDbContext<T> where T : class, new()
{
    protected static readonly SqlSugarScope DbInstance = new(new ConnectionConfig()
    {
        DbType = DbType.SqlServer,//你的数据库类型
        ConnectionString = App.Configuration["ConnectionStrings:DefaultConnectionString"],//连接字符串
        IsAutoCloseConnection = true,
        InitKeyType = InitKeyType.Attribute,
        LanguageType = LanguageType.Default,
        ConfigureExternalServices = new ConfigureExternalServices()//其他配置
        {
            EntityNameService = (type, entity) =>//表配置
            {
                entity.IsDisabledUpdateAll = true;//禁用全表升级
                entity.IsDisabledDelete = true;//禁用删除
            }
        }
    },
        db =>
        {
            db.CodeFirst.SetStringDefaultLength(255).InitTables<T>();//设置代码优先,并配置默认字段长度255,并初始化
            db.Ado.CommandTimeOut = 120;//执行超时时间

            db.Aop.OnLogExecuting = (sql, pars) =>//在SQL执行前执行
            {
                if (App.Configuration["AppSettings:IsDev"].ToBool())
                {
                    var sqlString = WriteSQL(sql, pars);
                    Console.ForegroundColor = ConsoleColor.Yellow;
                    Console.WriteLine(sqlString);
                    sqlString.LogDebug();
                    Console.ForegroundColor = ConsoleColor.White;
                }
            };

            db.Aop.OnError = (execption) =>//SQL执行出错
            {
                string errorInfo = $"SQL执行出错!【SQL语句】:{execption.Sql}\r\n【消息内容】:{execption.Message}";
                errorInfo.LogError();
            };

            db.Aop.OnExecutingChangeSql = (sql, pars) => //可以修改SQL和参数的值
            {
                return new KeyValuePair<string, SugarParameter[]>(sql, pars);
            };

            db.Aop.DataExecuting = (oldValue, entityInfo) =>//执行期间自动执行
            {
                //添加时自动添加创建时间
                if (entityInfo.PropertyName == "CreateTime" && entityInfo.OperationType == DataFilterType.InsertByObject)
                {
                    entityInfo.SetValue(DateTime.Now);
                }
                //修改时自动添加修改时间
                if (entityInfo.PropertyName == "UpdateTime" && entityInfo.OperationType == DataFilterType.UpdateByObject)
                {
                    entityInfo.SetValue(DateTime.Now);
                }
            };

            db.Aop.OnDiffLogEvent = it =>//差异日志
            {
                var log = "差异日志:";
                log += "BeforeData:" + it.BeforeData + "\r\n";//操作前的值(如果是Insert操作则为NULL),
                log += "AfterData" + it.AfterData + "\r\n";   //操作后的值,包含:字段描述  列名  值  表名  表描述
                log += "SQL:" + it.Sql + "\r\n";              //错误SQL
                log += "Parameters:" + it.Parameters + "\r\n";//参数
                log += "Data:" + it.BusinessData + "\r\n";    //传入的值
                log += "ExecTime:" + it.Time + "\r\n";        //执行时间
                log += "DiffType:" + it.DiffType + "\r\n";    //操作类型 Enum类型,有Insert,Update

                log.LogInformation();
            };

            db.Aop.OnLogExecuted = (sql, pars) =>//执行完成后的操作
            {
                int sqlExecTimeout = App.Configuration["ConnectionStrings:MaxExecTime"].ToInt32(); //在配置文件中设置最大等待SQL执行时间
                sqlExecTimeout = sqlExecTimeout > 0 ? sqlExecTimeout : 1;//赋值默认值
                double i = db.Ado.SqlExecutionTime.TotalMilliseconds;
                if (i > sqlExecTimeout * 1000)//如果SQL执行缓慢,则写入日志
                {
                    string sqlString = WriteSQL(sql, pars);
                    Console.ForegroundColor = ConsoleColor.Yellow;
                    ("SQL语句:" + sqlString + "执行缓慢,用时" + i + "毫秒,请排查").LogWarning();
                    Console.ForegroundColor = ConsoleColor.White;
                }
            };
        });


    /// <summary>
    /// 输出赋值后的SQL
    /// </summary>
    /// <param name="sql"></param>
    /// <param name="pars"></param>
    /// <returns></returns>
    private static string WriteSQL(string sql, SugarParameter[] pars)
    {
        //查看赋值后的SQL语句,仅供测试时使用
        StringBuilder sb_sql = new(sql);
        var tempOrderPars = pars.Where(p => p.Value != null).OrderByDescending(p => p.ParameterName.Length).ToList();//防止 @par1错误替换@par12
        for (var index = 0; index < tempOrderPars.Count; index++)
        {
            sb_sql.Replace(tempOrderPars[index].ParameterName, "'" + tempOrderPars[index].Value.ToString() + "'");
        }

        return sb_sql.ToString();
    }
}

其次,我们需要日志组件,使用.Net自带的Log,配合Furion进行配置:

【选用】格式化控制台输出的内容

Csharp 复制代码
//添加控制台格式化输出
services.AddConsoleFormatter(options =>
{
    options.DateFormat = "yyyy-MM-dd HH:mm:ss.fffffff zzz dddd";//格式化输出日期
    options.ColorBehavior = LoggerColorBehavior.Enabled;//启用颜色美化
    options.WithTraceId = true;//输出TraceId
    options.WithStackFrame = true;//输出日志
    options.WriteHandler = (logMsg, scopeProvider, writer, fmtMsg, opt) =>//自定义日志输出程序
    {
        writer.WriteLine(fmtMsg);
    };

    options.MessageFormat = (logMsg) =>
    {
        if (logMsg.LogLevel == LogLevel.Warning)
        {
            Console.ForegroundColor = ConsoleColor.Yellow;
        }
        else if (logMsg.LogLevel == LogLevel.Error)
        {
            Console.ForegroundColor = ConsoleColor.Red;
        }
        else if (logMsg.LogLevel == LogLevel.Critical)
        {
            Console.ForegroundColor = ConsoleColor.DarkRed;
        }
        else
        {
            Console.ForegroundColor = ConsoleColor.Green;
        }
        var template = TP.Wrapper("日志内容", $"日志等级:{logMsg.LogLevel}",
            $"##【时间】##      {logMsg.LogDateTime:yyyy-MM-dd HH:mm:ss:fffff}",
            $"##【名称】##      {logMsg.LogName}",
            $"##【线程】##      {logMsg.EventId}",
            $"##【消息】##      {logMsg.Message}",
            $"##【错误内容】##   {logMsg.Exception?.ToString()}");
        return template;
    };
});//日志格式化

效果如图:

Csharp 复制代码
//日志分级写入文件
Array.ForEach(new[] { LogLevel.Debug, LogLevel.Information, LogLevel.Warning, LogLevel.Error, LogLevel.Critical }, logLevel =>
{
    services.AddFileLogging(Environment.CurrentDirectory + @"\Logs\Debug\workstation-" + DateTime.Now.ToString("yyyy-MM-dd") + ".log", options =>
    {
        options.WriteFilter = (logMsg) =>
        {
            return logMsg.LogLevel == LogLevel.Debug;
        };
        options.FileNameRule = fileName =>
        {
            return string.Format(fileName, DateTime.Now);
        };
        options.MessageFormat = (logMsg) =>
        {
            StringBuilder log = new();
            log.Append("【时间】:" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fffff") + "\r\n");
            log.Append("【等级】:" + logMsg.LogLevel.ToString() + "\r\n");
            log.Append("【名称】:" + logMsg.LogName + "\r\n");
            log.Append("【线程】" + logMsg.EventId.Id + "\r\n");
            log.Append("【消息】:" + logMsg.Message + "\r\n");
            log.Append("【错误内容】:" + logMsg.Exception?.ToString() + "\r\n" + "\r\n");

            return log.ToString();
        };
        options.DateFormat = "yyyy-MM-dd HH:mm:ss.fffff";
        options.HandleWriteError = (writeError) =>
        {
            writeError.UseRollbackFileName(Path.GetFileNameWithoutExtension(writeError.CurrentFileName) + "-oops" + Path.GetExtension(writeError.CurrentFileName));
        };
        options.FileSizeLimitBytes = 500 * 1024;
        options.MaxRollingFiles = 30;
    });//Debug日志写入本地文件
    services.AddFileLogging(Environment.CurrentDirectory + @"\Logs\Information\workstation-" + DateTime.Now.ToString("yyyy-MM-dd") + ".log", options =>
    {
        options.WriteFilter = (logMsg) =>
        {
            return logMsg.LogLevel == LogLevel.Information;
        };
        options.FileNameRule = fileName =>
        {
            return string.Format(fileName, DateTime.Now);
        };
        options.MessageFormat = (logMsg) =>
        {
            StringBuilder log = new();
            log.Append("【时间】:" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fffff") + "\r\n");
            log.Append("【等级】:" + logMsg.LogLevel.ToString() + "\r\n");
            log.Append("【名称】:" + logMsg.LogName + "\r\n");
            log.Append("【线程】" + logMsg.EventId.Id + "\r\n");
            log.Append("【消息】:" + logMsg.Message + "\r\n");
            log.Append("【错误内容】:" + logMsg.Exception?.ToString() + "\r\n" + "\r\n");

            return log.ToString();
        };
        options.DateFormat = "yyyy-MM-dd HH:mm:ss.fffff";
        options.HandleWriteError = (writeError) =>
        {
            writeError.UseRollbackFileName(Path.GetFileNameWithoutExtension(writeError.CurrentFileName) + "-oops" + Path.GetExtension(writeError.CurrentFileName));
        };
        options.FileSizeLimitBytes = 500 * 1024;
        options.MaxRollingFiles = 30;
    });//information日志写入本地文件
    services.AddFileLogging(Environment.CurrentDirectory + @"\Logs\Warning\workstation-" + DateTime.Now.ToString("yyyy-MM-dd") + ".log", options =>
    {
        options.WriteFilter = (logMsg) =>
        {
            return logMsg.LogLevel == LogLevel.Warning;
        };
        options.FileNameRule = fileName =>
        {
            return string.Format(fileName, DateTime.Now);
        };
        options.MessageFormat = (logMsg) =>
        {
            StringBuilder log = new();
            log.Append("【时间】:" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fffff") + "\r\n");
            log.Append("【等级】:" + logMsg.LogLevel.ToString() + "\r\n");
            log.Append("【名称】:" + logMsg.LogName + "\r\n");
            log.Append("【线程】" + logMsg.EventId.Id + "\r\n");
            log.Append("【消息】:" + logMsg.Message + "\r\n");
            log.Append("【错误内容】:" + logMsg.Exception?.ToString() + "\r\n" + "\r\n");

            return log.ToString();
        };
        options.DateFormat = "yyyy-MM-dd HH:mm:ss.fffff";
        options.HandleWriteError = (writeError) =>
        {
            writeError.UseRollbackFileName(Path.GetFileNameWithoutExtension(writeError.CurrentFileName) + "-oops" + Path.GetExtension(writeError.CurrentFileName));
        };
        options.FileSizeLimitBytes = 500 * 1024;
        options.MaxRollingFiles = 30;
    });//warning日志写入本地文件
    services.AddFileLogging(Environment.CurrentDirectory + @"\Logs\Error\workstation-" + DateTime.Now.ToString("yyyy-MM-dd") + ".log", options =>
    {
        options.WriteFilter = (logMsg) =>
        {
            return logMsg.LogLevel == LogLevel.Error;
        };
        options.FileNameRule = fileName =>
        {
            return string.Format(fileName, DateTime.Now);
        };
        options.MessageFormat = (logMsg) =>
        {
            StringBuilder log = new();
            log.Append("【时间】:" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fffff") + "\r\n");
            log.Append("【等级】:" + logMsg.LogLevel.ToString() + "\r\n");
            log.Append("【名称】:" + logMsg.LogName + "\r\n");
            log.Append("【线程】" + logMsg.EventId.Id + "\r\n");
            log.Append("【消息】:" + logMsg.Message + "\r\n");
            log.Append("【错误内容】:" + logMsg.Exception?.ToString() + "\r\n" + "\r\n");

            return log.ToString();
        };
        options.DateFormat = "yyyy-MM-dd HH:mm:ss.fffff";
        options.HandleWriteError = (writeError) =>
        {
            writeError.UseRollbackFileName(Path.GetFileNameWithoutExtension(writeError.CurrentFileName) + "-oops" + Path.GetExtension(writeError.CurrentFileName));
        };
        options.FileSizeLimitBytes = 500 * 1024;
        options.MaxRollingFiles = 30;
    });//error日志写入本地文件
    services.AddFileLogging(Environment.CurrentDirectory + @"\Logs\Critical\workstation-" + DateTime.Now.ToString("yyyy-MM-dd") + ".log", options =>
    {
        options.WriteFilter = (logMsg) =>
        {
            return logMsg.LogLevel == LogLevel.Critical;
        };
        options.FileNameRule = fileName =>
        {
            return string.Format(fileName, DateTime.Now);
        };
        options.MessageFormat = (logMsg) =>
        {
            StringBuilder log = new();
            log.Append("【时间】:" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fffff") + "\r\n");
            log.Append("【等级】:" + logMsg.LogLevel.ToString() + "\r\n");
            log.Append("【名称】:" + logMsg.LogName + "\r\n");
            log.Append("【线程】" + logMsg.EventId.Id + "\r\n");
            log.Append("【消息】:" + logMsg.Message + "\r\n");
            log.Append("【错误内容】:" + logMsg.Exception?.ToString() + "\r\n" + "\r\n");

            return log.ToString();
        };
        options.DateFormat = "yyyy-MM-dd HH:mm:ss.fffff";
        options.HandleWriteError = (writeError) =>
        {
            writeError.UseRollbackFileName(Path.GetFileNameWithoutExtension(writeError.CurrentFileName) + "-oops" + Path.GetExtension(writeError.CurrentFileName));
        };
        options.FileSizeLimitBytes = 500 * 1024;
        options.MaxRollingFiles = 30;
    });//critical日志写入本地文件
});//日志分级并根据日期写入文件

效果如图:

【选用】将日志写入数据库

Csharp 复制代码
//日志写入数据库
services.AddDatabaseLogging<WriteLogToDB>(options =>
{
    options.MinimumLevel = LogLevel.Warning;
    options.IgnoreReferenceLoop = true;
});//日志写入数据库

我们需要在YourProject.Core中新建Logger文件夹 新建类:WriteLogToDB,代码如下:

Csharp 复制代码
/// <summary>
/// 将日志写入数据库
/// </summary>
public class WriteLogToDB : SugarDbContext<Log_LogRecord_Entity>, IDatabaseLoggingWriter
{

    public void Write(LogMessage logMsg, bool flush)
    {
        Log_LogRecord_Entity log = new()
        {
            LogName = logMsg.LogName,
            LogLevel = (int)logMsg.LogLevel,
            EventId = logMsg.EventId.ToString(),
            Message = logMsg.Message,
            Exception = logMsg.Exception.ToString(),
            State = logMsg.State.ToString(),
            LogDateTime = logMsg.LogDateTime,
            ThreadId = logMsg.ThreadId,
            Context = JsonHelper.Serialize(logMsg.Context),
        };

        DbInstance.Insertable(log).ExecuteCommand();
    }
}

新建类:Log_LogRecord_Entity【用于向数据库记录】

Csharp 复制代码
using SqlSugar;

/*
 * @author : xkdong@163.com
 * @date : 2023-4-26
 * @desc : 日志记录表
 */
namespace Mofang.Workstation.Core
{
    /// <summary>
    /// 日志记录表
    /// </summary>
    [SugarTable("Log_LogRecord", TableDescription = "日志记录表")]
    public class Log_LogRecord_Entity
    {
        /// <summary>
        /// 编号
        /// </summary>
        [SugarColumn(IsIdentity = true, IsPrimaryKey = true, ColumnDescription = "编号")]
        public int Id { get; set; }


        /// <summary>
        /// 日志名称
        /// </summary>
        [SugarColumn(ColumnDescription = "日志名称")]
        public string LogName { get; set; }


        /// <summary>
        /// 日志等级
        /// </summary>
        [SugarColumn(ColumnDescription = "日志等级")]
        public int LogLevel { get; set; }


        /// <summary>
        /// 事件Id
        /// </summary>
        [SugarColumn(ColumnDescription = "事件Id")]
        public string EventId { get; set; }


        /// <summary>
        /// 消息内容
        /// </summary>
        [SugarColumn(ColumnDescription = "消息内容", ColumnDataType = "TEXT")]
        public string Message { get; set; }


        /// <summary>
        /// 错误内容
        /// </summary>
        [SugarColumn(ColumnDescription = "错误内容", ColumnDataType = "TEXT")]
        public string Exception { get; set; }


        /// <summary>
        /// 状态
        /// </summary>
        [SugarColumn(ColumnDescription = "状态")]
        public string State { get; set; }


        /// <summary>
        /// 日志时间
        /// </summary>
        [SugarColumn(ColumnDescription = "日志时间")]
        public DateTime LogDateTime { get; set; }


        /// <summary>
        /// 线程Id
        /// </summary>
        [SugarColumn(ColumnDescription = "线程Id")]
        public int ThreadId { get; set; }


        /// <summary>
        /// 日志上下文
        /// </summary>
        [SugarColumn(ColumnDescription = "日志上下文", ColumnDataType = "TEXT")]
        public string Context { get; set; }


    }
}

效果如图: 【(⊙o⊙)...木有图,自己想象下,就普通的表】

相关推荐
SongYuLong的博客4 小时前
C# (定时器、线程)
开发语言·c#
百锦再5 小时前
详解基于C#开发Windows API的SendMessage方法的鼠标键盘消息发送
windows·c#·计算机外设
无敌最俊朗@7 小时前
unity3d————协程原理讲解
开发语言·学习·unity·c#·游戏引擎
程序设计实验室7 小时前
在网页上调起本机C#程序
c#
Crazy Struggle10 小时前
.NET 8 强大功能 IHostedService 与 BackgroundService 实战
c#·.net·.net core
fs哆哆10 小时前
C#编程:优化【性别和成绩名次】均衡分班
开发语言·c#
fathing11 小时前
c# 调用c++ 的dll 出现找不到函数入口点
开发语言·c++·c#
wyh要好好学习13 小时前
C# WPF 记录DataGrid的表头顺序,下次打开界面时应用到表格中
开发语言·c#·wpf
AitTech13 小时前
C#实现:电脑系统信息的全面获取与监控
开发语言·c#
咩咩觉主14 小时前
尽量通俗易懂地概述.Net && U nity跨语言/跨平台相关知识
unity·c#·.net·.netcore