用最清爽的方式開發dotNet

君寧天下發表於2023-12-07

用最清爽的方式開發dotNet

不管是官方自帶模板還是其他開源搞的,總是一來一大堆,如果你也嫌棄這些過於臃腫,不如看看我這個方式

前提

假設我要做一個簡單的api

方式

想到清爽,那肯定是簡單方便,腦袋第一個念頭就是.Net6 推出的miniapi了

官方路子

兩篇官方文件足以,按照文件step by step 就ok了,其他的需要就加

我的野路子

官方是官方,官方走的路子當然還是基於它最標準的搞法,我的路子則是基於國內實際情況
總體思路就是用控制檯改api

模擬前提場景

搞一個普通企業官網的api,那麼要求就是以下幾點

  • 需要資料庫操作
  • 需要授權鑑權
  • 需要swagger文件
  • 需要上傳檔案

根據這些要求,我需要引入最基本的就幾個:

  • Swashbuckle.AspNetCore (swagger相關)
  • SqlSugarCore (sqlsugar Orm) (用啥都可以,例如還有freesql)
  • Microsoft.AspNetCore.Authentication.JwtBearer (授權鑑權這裡用簡單的jwt)
  • Mapster (dto和entity互轉)

如果有其他需求,再自己加,一點也不冗餘

注意:需要先右鍵控制檯專案,將 <Project Sdk="Microsoft.NET.Sdk"> 改為 <Project Sdk="Microsoft.NET.Sdk.Web"> 其原因可以去官網翻找資料感悟一下

程式碼

dotNet 幾忘了,反正很早就是通用主機了,如果你同時還要搞blazor server啥的,把這一坨封裝一下即可,通用的

Program.cs 裡面直接無腦寫下以下程式碼

var builder = WebApplication.CreateBuilder(args);

#region 基本設定
builder.Services.AddMemoryCache();
builder.Services.AddControllers();
builder.Services.Configure<FormOptions>(options =>
{
    // 設定上傳大小限制256MB
    options.MultipartBodyLengthLimit = 268435456;

});
builder.Services.AddSingleton<SqlSugarMemoryCacheService>();
#endregion

#region 授權鑑權


// 新增身份驗證和授權中介軟體
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidateLifetime = true,
            ValidateIssuerSigningKey = true,
            ValidIssuer = "ningissuer",
            ValidAudience = "wr",
            IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("sdfsdfsrty45634kkhxxhtdgdfss345t678xx"))
        };
    });

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("AdminOnly", policy =>
    {
        policy.RequireClaim("role", "admin");
    });
});

#endregion

#region swagger
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(c =>
{
    c.SwaggerDoc("v1", new OpenApiInfo { Title = "企業官網Api", Version = "v1" });

    // 新增身份驗證
    c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
    {
        Description = "JWT Authorization header using the Bearer scheme",
        Name = "Authorization",
        In = ParameterLocation.Header,
        Type = SecuritySchemeType.ApiKey
    });

    // 新增授權要求
    c.AddSecurityRequirement(new OpenApiSecurityRequirement
        {
            {
                new OpenApiSecurityScheme
                {
                    Reference = new OpenApiReference
                    {
                        Type = ReferenceType.SecurityScheme,
                        Id = "Bearer"
                    }
                },
                new string[] {}
            }
        });

    // 設定 XML 註釋檔案的路徑
    var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
    var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
    c.IncludeXmlComments(xmlPath);
});
#endregion

var app = builder.Build();
app.UseSwagger();
app.UseStaticFiles();
// 啟用身份驗證和授權中介軟體
app.UseAuthentication();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
    endpoints.MapControllers(); // 這裡配置了使用控制器的路由
});
app.UseSwaggerUI(c =>
{
    c.SwaggerEndpoint("/swagger/v1/swagger.json", "企業官網Api");
    c.RoutePrefix = string.Empty; // 將 Swagger UI 設定為應用程式的根路徑
});

ServiceLocator.Instance = app.Services;
ServiceLocator.ApplicationBuilder = app;

var db = SqlSugarHelper.Db;
//資料庫初始化
app.MapGet("/seed", async () =>
{


    db.CodeFirst.InitTables<SysUserEntity>();

    string name = "op";
    string pwd = "op";
    var loginResult = await db.Queryable<SysUserEntity>().Where(a => !a.IsBan && a.UsePwd == pwd && a.UserName == name).AnyAsync();
    if (!loginResult)
    {
        await db.Insertable<SysUserEntity>(new SysUserEntity { IsBan = false, UsePwd = pwd, UserName = name }).ExecuteCommandAsync();
    }

    db.CodeFirst.InitTables<FileSourceEntity>();
    db.CodeFirst.InitTables<ArticleEntity>();

});

app.MapGet("/health", () => "1024");

app.Run();

介面就“勉為其難”的新建個api資料夾然後


/// <summary>
    /// 系統使用者
    /// </summary>
    [Route("api/[controller]/[action]")]
    [ApiController]
    public class SysUserController : BaseApi
    {
        public SysUserController()
        {

        }

        /// <summary>
        /// 檢測Token資訊
        /// </summary>
        /// <returns></returns>
        [HttpGet]
        [Authorize]
        public ApiResult CheckToken()
        {

            var httpContext = HttpContext;

            // 從請求頭中獲取 Authorization 標頭的值
            var authorizationHeader = httpContext.Request.Headers["Authorization"].FirstOrDefault();

            if (!string.IsNullOrEmpty(authorizationHeader) && authorizationHeader.StartsWith("Bearer "))
            {
                // 提取令牌字串(去除 "Bearer " 字首)
                var token = authorizationHeader.Substring(7);

                var tokenHandler = new JwtSecurityTokenHandler();
                var jwtToken = tokenHandler.ReadJwtToken(token);

                // 獲取 ClaimTypes.Name 的值
                var username = jwtToken.Claims.FirstOrDefault(claim => claim.Type == "name")?.Value;

                // 在這裡使用 username 進行其他操作

                return Success($"當前Token使用者是:{username}");
            }

            return Error("Toekn資訊解析失敗");
        }

        /// <summary>
        /// 登入
        /// </summary>
        /// <param name="model"></param>
        /// <returns></returns>
        [AllowAnonymous]
        [HttpPost]
        public async Task<ApiResult> Login(SysUserEntity model)
        {
            string secretKey = "sdfsdfsrty45634kkhxxhtdgdfss345t678xx";
            var loginResult = await db.Queryable<SysUserEntity>().Where(a => !a.IsBan && a.UsePwd == model.UsePwd && a.UserName == model.UserName).AnyAsync();
            // 驗證使用者名稱和密碼

            if (!loginResult)
            {
                return Error("賬號密碼錯誤");
            }

            // 生成 JWT 令牌
            var tokenHandler = new JwtSecurityTokenHandler();

            var key = Encoding.ASCII.GetBytes(secretKey);
            var tokenDescriptor = new SecurityTokenDescriptor
            {
                Subject = new ClaimsIdentity(new List<Claim>
                {
                    new Claim(ClaimTypes.Name, model.UserName),
                    new Claim(JwtRegisteredClaimNames.Jti, model.Id.ToString()),
                    new Claim(JwtRegisteredClaimNames.Iat, DateTime.Now.ToString()),
                    new Claim(ClaimTypes.Expiration, DateTime.Now.AddHours(10).ToString())
                }),
                Expires = DateTime.UtcNow.AddDays(7),
                SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature),
                Issuer = "ningissuer",
                Audience = "wr",
               
            };
            var token = tokenHandler.CreateToken(tokenDescriptor);
            var tokenString = tokenHandler.WriteToken(token);
            // 返回 JWT 令牌
            return Success(new { token = "Bearer " + tokenString });
        }
    }

到這裡,基本上已經結束了,剩下的無非加加業務,或者加一些更豐富的元件,什麼autofac啦,nacos啦,yarp啦,seq啦

總結

對專案而言

其實這種方式已經足夠適用絕大多數中小公司的普通專案需求了,如果你還要加些限流或者什麼中介軟體的話,也是可以很直觀的去加,而不是像某些框架封裝一坨又一坨,你在哪加個什麼東西要翻找半天,毀壞了原本dotNet自身的生態(指官方文件)

這樣出來對的專案也很直觀,物盡其才,只要後續開發定好一個規範管理,就不會像你公司那破框架一堆密密麻麻的東西都沒使用過的情況出現

對新手而言

同時呢,這樣構建一個專案框架,也方便新手學習,因為十分的直觀,不會對莫名其妙出現的東西感覺到匪夷所思,根本不知道拿來做什麼的,像這樣需要什麼加什麼,就對所有加的東西包括nuget包,中介軟體,或者封裝啥的都有個很清晰的認知

對轉行到.Net的人而言

dotnet官方本身已經是一個大封裝了,不要把別的語言思維帶到這裡,做什麼破功能都要自己寫,寫又寫不好,寫好了又沒文件,人走了之後又坑公司又坑其他.net開發者

結語,給所有中小公司和個人的開發建議

馬上2024了,.Net的生態已經算是十分豐富了,請不要再試圖自行造輪子

舉個例子假如你要
對接微信(企業微信,小程式,公眾號)/位元組用這個:https://github.com/fudiwei/DotNetCore.SKIT.FlurlHttpClient.Wechat

別在那自己瞎琢磨封裝,對個人而言你瞎封裝有什麼用對你也沒什麼好處費時費力,還封裝不好,你能保證自己封裝完了還會提供詳細的文件?

一句很重要的話,我在一線開發從curd幹到框架,我覺得很多人都沒意識到的一點就是:
企業的專案,技術方面所有都要為了實際業務而做出努力,而不是為了技術而技術。

就剛才這封裝的例子,如果你是自己封裝,隨便有點變動你是不是要拋下業務需求不管去維護?
一切的程式碼和封裝前提思想就是不要為了寫程式碼而去寫程式碼,唉,忍不吐槽一下,這其實是碼農基本素養,但還是看的太多太心累

程式碼檔案補充

SqlSugarHelper


public class SqlSugarHelper
{

    public static readonly SqlSugarScope Db = new SqlSugarScope(new ConnectionConfig()
    {
        ConnectionString = "server=xxx;Database=xxx;Uid=root;Pwd=xxx;Port=6607;Allow User Variables=True;",//連線符字串
        DbType = DbType.MySql,
        IsAutoCloseConnection = true,
    }, db =>
    {
        ExternalServicesSetting(db);
        db.Aop.OnLogExecuting = (sql, pars) =>
        {
            Console.WriteLine(sql);
        };
    });

    /// <summary>
    /// 擴充配置
    /// </summary>
    /// <param name="db"></param>
    /// <param name="config"></param>
    private static void ExternalServicesSetting(SqlSugarClient db)
    {
        var cache = ServiceLocator.Instance.GetService<SqlSugarMemoryCacheService>();
        db.CurrentConnectionConfig.ConfigureExternalServices = new ConfigureExternalServices
        {
            DataInfoCacheService = cache,
        };
    }
}

相關文章