主頁 > .NET開發 > ASP.NET Core Web API 最佳實踐指南

ASP.NET Core Web API 最佳實踐指南

2020-09-19 09:56:33 .NET開發

原文地址: ASP.NET-Core-Web-API-Best-Practices-Guide

介紹

當我們撰寫一個專案的時候,我們的主要目標是使它能如期運行,并盡可能地滿足所有用戶需求,

但是,你難道不認為創建一個能正常作業的專案還不夠嗎?同時這個專案不應該也是可維護和可讀的嗎?

事實證明,我們需要把更多的關注點放到我們專案的可讀性和可維護性上,這背后的主要原因是我們或許不是這個專案的唯一撰寫者,一旦我們完成后,其他人也極有可能會加入到這里面來,

因此,我們應該把關注點放到哪里呢?

在這一份指南中,關于開發 .NET Core Web API 專案,我們將敘述一些我們認為會是最佳實踐的方式,進而讓我們的專案變得更好和更加具有可維護性,

現在,讓我們開始想一些可以應用到 ASP.NET Web API 專案中的一些最佳實踐,

Startup 類 和 服務配置

STARTUP CLASS AND THE SERVICE CONFIGURATION

Startup 類中,有兩個方法:ConfigureServices 是用于服務注冊,Configure 方法是向應用程式的請求管道中添加中間件,

因此,最好的方式是保持 ConfigureServices 方法簡潔,并且盡可能地具有可讀性,當然,我們需要在該方法內部撰寫代碼來注冊服務,但是我們可以通過使用 擴展方法 來讓我們的代碼更加地可讀和可維護,

例如,讓我們看一個注冊 CORS 服務的不好方式:

public void ConfigureServices(IServiceCollection services)
{
    services.AddCors(options => 
    {
        options.AddPolicy("CorsPolicy", builder => builder.AllowAnyOrigin()
            .AllowAnyMethod()
            .AllowAnyHeader()
            .AllowCredentials());
    });
}

盡管這種方式看起來挺好,也能正常地將 CORS 服務注冊成功,但是想象一下,在注冊了十幾個服務之后這個方法體的長度,

這樣一點也不具有可讀性,

一種好的方式是通過在擴展類中創建靜態方法:

public static class ServiceExtensions
{
    public static void ConfigureCors(this IServiceCollection services)
    {
        services.AddCors(options =>
        {
            options.AddPolicy("CorsPolicy", builder => builder.AllowAnyOrigin()
                .AllowAnyMethod()
                .AllowAnyHeader()
                .AllowCredentials());
        });
    }
}

然后,只需要呼叫這個擴展方法即可:

public void ConfigureServices(IServiceCollection services)
{
    services.ConfigureCors();
}

了解更多關于 .NET Core 的專案配置,請查看:.NET Core Project Configuration

專案組織

PROJECT ORGANIZATION

我們應該嘗試將我們的應用程式拆分為多個小專案,通過這種方式,我們可以獲得最佳的專案組織方式,并能將關注點分離(SoC),我們的物體、契約、訪問資料庫操作、記錄資訊或者發送郵件的業務邏輯應該始終放在單獨的 .NET Core 類別庫專案中,

應用程式中的每個小專案都應該包含多個檔案夾用來組織業務邏輯,

這里有個簡單的示例用來展示一個復雜的專案應該如何組織:

基于環境的設定

ENVIRONMENT BASED SETTINGS

當我們開發應用程式時,它處于開發環境,但是一旦我們發布之后,它將處于生產環境,因此,將每個環境進行隔離配置往往是一種好的實踐方式,

在 .NET Core 中,這一點很容易實作,

一旦我們創建好了專案,就已經有一個 appsettings.json 檔案,當我們展開它時會看到 appsettings.Development.json 檔案:

此檔案中的所有設定將用于開發環境,

我們應該添加另一個檔案 appsettings.Production.json,將其用于生產環境:

生產檔案將位于開發檔案下面,

設定修改后,我們就可以通過不同的 appsettings 檔案來加載不同的配置,取決于我們應用程式當前所處環境,.NET Core 將會給我們提供正確的設定,更多關于這一主題,請查閱:Multiple Environments in ASP.NET Core.

資料訪問層

DATA ACCESS LAYER

在一些不同的示例教程中,我們可能看到 DAL 的實作在主專案中,并且每個控制器中都有實體,我們不建議這么做,

當我們撰寫 DAL 時,我們應該將其作為一個獨立的服務來創建,在 .NET Core 專案中,這一點很重要,因為當我們將 DAL 作為一個獨立的服務時,我們就可以將其直接注入到 IOC(控制反轉)容器中,IOC 是 .NET Core 內置功能,通過這種方式,我們可以在任何控制器中通過建構式注入的方式來使用,

public class OwnerController: Controller
{
    private readonly IRepository _repository;
    public OwnerController(IRepository repository)
    {
        _repository = repository;
    }
}

控制器

CONTROLLERS

控制器應該始終盡量保持整潔,我們不應該將任何業務邏輯放置于內,

因此,我們的控制器應該通過建構式注入的方式接收服務實體,并組織 HTTP 的操作方法(GET,POST,PUT,DELETE,PATCH...):

public class OwnerController : Controller
{
    private readonly ILoggerManager _logger;
    private readonly IRepository _repository;
    public OwnerController(ILoggerManager logger, IRepository repository)
    {
        _logger = logger;
        _repository = repository;
    }

    [HttpGet]
    public IActionResult GetAllOwners()
    {
    }
    [HttpGet("{id}", Name = "OwnerById")]
    public IActionResult GetOwnerById(Guid id)
    {
    }
    [HttpGet("{id}/account")]
    public IActionResult GetOwnerWithDetails(Guid id)
    {
    }
    [HttpPost]
    public IActionResult CreateOwner([FromBody]Owner owner)
    {
    }
    [HttpPut("{id}")]
    public IActionResult UpdateOwner(Guid id, [FromBody]Owner owner)
    {
    }
    [HttpDelete("{id}")]
    public IActionResult DeleteOwner(Guid id)
    {
    }
}

我們的 Action 應該盡量保持簡潔,它們的職責應該包括處理 HTTP 請求,驗證模型,捕捉例外和回傳回應,

[HttpPost]
public IActionResult CreateOwner([FromBody]Owner owner)
{
    try
    {
        if (owner.IsObjectNull())
        {
            return BadRequest("Owner object is null");
        }
        if (!ModelState.IsValid)
        {
            return BadRequest("Invalid model object");
        }
        _repository.Owner.CreateOwner(owner);
        return CreatedAtRoute("OwnerById", new { id = owner.Id }, owner);
    }
    catch (Exception ex)
    {
        _logger.LogError($"Something went wrong inside the CreateOwner action: { ex} ");
        return StatusCode(500, "Internal server error");
    }
}

在大多數情況下,我們的 action 應該將 IActonResult 作為回傳型別(有時我們希望回傳一個特定型別或者是 JsonResult ...),通過使用這種方式,我們可以很好地使用 .NET Core 中內置方法的回傳值和狀態碼,

使用最多的方法是:

  • OK => returns the 200 status code
  • NotFound => returns the 404 status code
  • BadRequest => returns the 400 status code
  • NoContent => returns the 204 status code
  • Created, CreatedAtRoute, CreatedAtAction => returns the 201 status code
  • Unauthorized => returns the 401 status code
  • Forbid => returns the 403 status code
  • StatusCode => returns the status code we provide as input

處理全域例外

HANDLING ERRORS GLOBALLY

在上面的示例中,我們的 action 內部有一個 try-catch 代碼塊,這一點很重要,我們需要在我們的 action 方法體中處理所有的例外(包括未處理的),一些開發者在 action 中使用 try-catch 代碼塊,這種方式明顯沒有任何問題,但我們希望 action 盡量保持簡潔,因此,從我們的 action 中洗掉 try-catch ,并將其放在一個集中的地方會是一種更好的方式,.NET Core 給我們提供了一種處理全域例外的方式,只需要稍加修改,就可以使用內置且完善的的中間件,我們需要做的修改就是在 Startup 類中修改 Configure 方法:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    app.UseExceptionHandler(config => 
    {
        config.Run(async context => 
        {
            context.Response.StatusCode = 500;
            context.Response.ContentType = "application/json";

            var error = context.Features.Get<IExceptionHandlerFeature>();
            if (error != null)
            {
                var ex = error.Error;
                await context.Response.WriteAsync(new ErrorModel
                {
                    StatusCode = 500,
                    ErrorMessage = ex.Message
                }.ToString());
            }
        });
    });

    app.UseRouting();

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

我們也可以通過創建自定義的中間件來實作我們的自定義例外處理:

// You may need to install the Microsoft.AspNetCore.Http.Abstractions package into your project
public class CustomExceptionMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger<CustomExceptionMiddleware> _logger;
    public CustomExceptionMiddleware(RequestDelegate next, ILogger<CustomExceptionMiddleware> logger)
    {
        _next = next;
        _logger = logger;
    }

    public async Task Invoke(HttpContext httpContext)
    {
        try
        {
            await _next(httpContext);
        }
        catch (Exception ex)
        {
            _logger.LogError("Unhandled exception....", ex);
            await HandleExceptionAsync(httpContext, ex);
        }
    }

    private Task HandleExceptionAsync(HttpContext httpContext, Exception ex)
    {
        //todo
        return Task.CompletedTask;
    }
}

// Extension method used to add the middleware to the HTTP request pipeline.
public static class CustomExceptionMiddlewareExtensions
{
    public static IApplicationBuilder UseCustomExceptionMiddleware(this IApplicationBuilder builder)
    {
        return builder.UseMiddleware<CustomExceptionMiddleware>();
    }
}

之后,我們只需要將其注入到應用程式的請求管道中即可:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    app.UseCustomExceptionMiddleware();
}

使用過濾器移除重復代碼

USING ACTIONFILTERS TO REMOVE DUPLICATED CODE

ASP.NET Core 的過濾器可以讓我們在請求管道的特定狀態之前或之后運行一些代碼,因此如果我們的 action 中有重復驗證的話,可以使用它來簡化驗證操作,

當我們在 action 方法中處理 PUT 或者 POST 請求時,我們需要驗證我們的模型物件是否符合我們的預期,作為結果,這將導致我們的驗證代碼重復,我們希望避免出現這種情況,(基本上,我們應該盡我們所能避免出現任何代碼重復,)我們可以在代碼中通過使用 ActionFilter 來代替我們的驗證代碼:

if (!ModelState.IsValid)
{
    //bad request and logging logic
}

我們可以創建一個過濾器:

public class ModelValidationAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext context)
    {
        if (!context.ModelState.IsValid)
        {
            context.Result = new BadRequestObjectResult(context.ModelState);
        }
    }
}

然后在 Startup 類的 ConfigureServices 函式中將其注入:

services.AddScoped<ModelValidationAttribute>();

現在,我們可以將上述注入的過濾器應用到我們的 action 中,

Microsoft.AspNetCore.All 元包

MICROSOFT.ASPNETCORE.ALL META-PACKAGE

注:如果你使用的是 2.1 和更高版本的 ASP.NET Core,建議使用 Microsoft.AspNetCore.App 包,而不是 Microsoft.AspNetCore.All,這一切都是出于安全原因,此外,如果使用 2.1 版本創建新的 WebAPI 專案,我們將自動獲取 AspNetCore.App 包,而不是 AspNetCore.All,

這個元包包含了所有 AspNetCore 的相關包,EntityFrameworkCore 包,SignalR 包(version 2.1) 和依賴框架運行的支持包,采用這種方式創建一個新專案很方便,因為我們不需要手動安裝一些我們可能使用到的包,

當然,為了能使用 Microsoft.AspNetCore.all 元包,需要確保你的機器安裝了 .NET Core Runtime,

路由

ROUTING

在 .NET Core Web API 專案中,我們應該使用屬性路由代替傳統路由,這是因為屬性路由可以幫助我們匹配路由引數名稱與 Action 內的實際引數方法,另一個原因是路由引數的描述,對我們而言,一個名為 "ownerId" 的引數要比 "id" 更加具有可讀性,

我們可以使用 [Route] 屬性來在控制器的頂部進行標注:

[Route("api/[controller]")]
public class OwnerController : Controller
{
    [Route("{id}")]
    [HttpGet]
    public IActionResult GetOwnerById(Guid id)
    {
    }
}

還有另一種方式為控制器和操作創建路由規則:

[Route("api/owner")]
public class OwnerController : Controller
{
    [Route("{id}")]
    [HttpGet]
    public IActionResult GetOwnerById(Guid id)
    {
    }
}

對于這兩種方式哪種會好一些存在分歧,但是我們經常建議采用第二種方式,這是我們一直在專案中采用的方式,

當我們談論路由時,我們需要提到路由的命名規則,我們可以為我們的操作使用描述性名稱,但對于 路由/節點,我們應該使用 NOUNS 而不是 VERBS,

一個較差的示例:

[Route("api/owner")]
public class OwnerController : Controller
{
    [HttpGet("getAllOwners")]
    public IActionResult GetAllOwners()
    {
    }
    [HttpGet("getOwnerById/{id}"]
    public IActionResult GetOwnerById(Guid id)
    {
    }
}

一個較好的示例:

[Route("api/owner")]
public class OwnerController : Controller
{
    [HttpGet]
    public IActionResult GetAllOwners()
    {
    }
    [HttpGet("{id}"]
    public IActionResult GetOwnerById(Guid id)
    {
    }
}

更多關于 Restful 實踐的細節解釋,請查閱:Top REST API Best Practices

日志

LOGGING

如果我們打算將我們的應用程式發布到生產環境,我們應該在合適的位置添加一個日志記錄機制,在生產環境中記錄日志對于我們梳理應用程式的運行很有幫助,

.NET Core 通過繼承 ILogger 介面實作了它自己的日志記錄,通過借助依賴注入機制,它可以很容易地使用,

public class TestController: Controller
{
    private readonly ILogger _logger;
    public TestController(ILogger<TestController> logger)
    {
        _logger = logger;
    }
}

然后,在我們的 action 中,我們可以通過使用 _logger 物件借助不同的日志級別來記錄日志,

.NET Core 支持使用于各種日志記錄的 Provider,因此,我們可能會在專案中使用不同的 Provider 來實作我們的日志邏輯,

NLog 是一個很不錯的可以用于我們自定義的日志邏輯類別庫,它極具擴展性,支持結構化日志,且易于配置,我們可以將資訊記錄到控制臺,檔案甚至是資料庫中,

想了解更多關于該類別庫在 .NET Core 中的應用,請查閱:.NET Core series – Logging With NLog.

Serilog 也是一個很不錯的類別庫,它適用于 .NET Core 內置的日志系統,

這里有一個能提高日志性能的小技巧:字串拼接建議使用 _logger.LogInformation("{0},{1}", DateTime.Now, "info") 方式來記錄日志,而不是 _logger.LogInformation($"{DateTime.Now},info")

加密

CRYPTOHELPER

我們不會建議將密碼以明文形式存盤到資料庫中,處于安全原因,我們需要對其進行哈希處理,這超出了本指南的內容范圍,互聯網上有大量哈希演算法,其中不乏一些不錯的方法來將密碼進行哈希處理,

但是如果需要為 .NET Core 的應用程式提供易于使用的加密類別庫,CryptoHelper 是一個不錯的選擇,

CryptoHelper 是適用于 .NET Core 的獨立密碼哈希庫,它是基于 PBKDF2 來實作的,通過創建 Data Protection 堆疊來將密碼進行哈希化,這個類別庫在 NuGet 上是可用的,并且使用也很簡單:

using CryptoHelper;

// Hash a password
public string HashPassword(string password)
{
    return Crypto.HashPassword(password);
}

// Verify the password hash against the given password
public bool VerifyPassword(string hash, string password)
{
    return Crypto.VerifyHashedPassword(hash, password);
}

內容協商

CONTENT NEGOTIATION

默認情況下,.NET Core Web API 會回傳 JSON 格式的結果,大多數情況下,這是我們所希望的,

但是如果客戶希望我們的 Web API 回傳其它的回應格式,例如 XML 格式呢?

為了解決這個問題,我們需要進行服務端配置,用于按需格式化我們的回應結果:

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

但有時客戶端會請求一個我們 Web API 不支持的格式,因此最好的實踐方式是對于未經處理的請求格式統一回傳 406 狀態碼,這種方式也同樣能在 ConfigureServices 方法中進行簡單配置:

public void ConfigureServices(IServiceCollection services)
{
     services.AddControllers(option => option.ReturnHttpNotAcceptable = true).AddXmlDataContractSerializerFormatters();
}

我們也可以創建我們自己的格式化規則,

這一部分內容是一個很大的主題,如果你希望了解更多,請查閱:Content Negotiation in .NET Core

使用 JWT

USING JWT

現如今的 Web 開發中,JSON Web Tokens (JWT) 變得越來越流行,得益于 .NET Core 內置了對 JWT 的支持,因此實作起來非常容易,JWT 是一個開發標準,它允許我們以 JSON 格式在服務端和客戶端進行安全的資料傳輸,

我們可以在 ConfigureServices 中配置 JWT 認證:

public void ConfigureServices(IServiceCollection services)
{
    services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
        .AddJwtBearer(options => 
        {
            options.TokenValidationParameters = new TokenValidationParameters
            {
                ValidateIssuer = true,
                ValidIssuer = _authToken.Issuer,

                ValidateAudience = true,
                ValidAudience = _authToken.Audience,

                ValidateIssuerSigningKey = true,
                IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_authToken.Key)),

                RequireExpirationTime = true,
                ValidateLifetime = true,

                //others
            };
        });
}

為了能在應用程式中使用它,我們還需要在 Configure 中呼叫下面一段代碼:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    app.UseAuthentication();
}

此外,創建 Token 可以使用如下方式:

var securityToken = new JwtSecurityToken(
                claims: new Claim[]
                {
                    new Claim(ClaimTypes.NameIdentifier,user.Id),
                    new Claim(ClaimTypes.Email,user.Email)
                },
                issuer: _authToken.Issuer,
                audience: _authToken.Audience,
                notBefore: DateTime.Now,
                expires: DateTime.Now.AddDays(_authToken.Expires),
                signingCredentials: new SigningCredentials(
                    new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_authToken.Key)),
                    SecurityAlgorithms.HmacSha256Signature));

Token = new JwtSecurityTokenHandler().WriteToken(securityToken)

基于 Token 的用戶驗證可以在控制器中使用如下方式:

var auth = await HttpContext.AuthenticateAsync();
var id = auth.Principal.Claims.FirstOrDefault(x => x.Type.Equals(ClaimTypes.NameIdentifier))?.Value;

我們也可以將 JWT 用于授權部分,只需添加角色宣告到 JWT 配置中即可,

更多關于 .NET Core 中 JWT 認證和授權部分,請查閱:authentication-aspnetcore-jwt-1 和 authentication-aspnetcore-jwt-2

總結

讀到這里,可能會有朋友對上述一些最佳實踐不是很認同,因為全篇都沒有談及更切合專案的實踐指南,比如 TDDDDD 等,但我個人認為上述所有的最佳實踐是基礎,只有把這些基礎掌握了,才能更好地理解一些更高層次的實踐指南,萬丈高樓平地起,所以你可以把這看作是一篇面向新手的最佳實踐指南,

在這份指南中,我們的主要目的是讓你熟悉關于使用 .NET Core 開發 web API 專案時的一些最佳實踐,這里面的部分內容在其它框架中也同樣適用,因此,熟練掌握它們很有用,

非常感謝你能閱讀這份指南,希望它能對你有所幫助,

Github 更新地址:ASP.NET Core Web API 最佳實踐

轉載請註明出處,本文鏈接:https://www.uj5u.com/net/80561.html

標籤:.NET Core

上一篇:如何運用領域驅動設計 - 聚合

下一篇:帶你使用Visual Studio 2019創建一個MVC Web應用

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • WebAPI簡介

    Web體系結構: 有三個核心:資源(resource),URL(統一資源識別符號)和表示 他們的關系是這樣的:一個資源由一個URL進行標識,HTTP客戶端使用URL定位資源,表示是從資源回傳資料,媒體型別是資源回傳的資料格式。 接下來我們說下HTTP. HTTP協議的系統是一種無狀態的方式,使用請求/ ......

    uj5u.com 2020-09-09 22:07:47 more
  • asp.net core 3.1 入口:Program.cs中的Main函式

    本文分析Program.cs 中Main()函式中代碼的運行順序分析asp.net core程式的啟動,重點不是剖析原始碼,而是理清程式開始時執行的順序。到呼叫了哪些實體,哪些法方。asp.net core 3.1 的程式入口在專案Program.cs檔案里,如下。ususing System; us ......

    uj5u.com 2020-09-09 22:07:49 more
  • asp.net網站作為websocket服務端的應用該如何寫

    最近被websocket的一個問題困擾了很久,有一個需求是在web網站中搭建websocket服務。客戶端通過網頁與服務器建立連接,然后服務器根據ip給客戶端網頁發送資訊。 其實,這個需求并不難,只是剛開始對websocket的內容不太了解。上網搜索了一下,有通過asp.net core 實作的、有 ......

    uj5u.com 2020-09-09 22:08:02 more
  • ASP.NET 開源匯入匯出庫Magicodes.IE Docker中使用

    Magicodes.IE在Docker中使用 更新歷史 2019.02.13 【Nuget】版本更新到2.0.2 【匯入】修復單列匯入的Bug,單元測驗“OneColumnImporter_Test”。問題見(https://github.com/dotnetcore/Magicodes.IE/is ......

    uj5u.com 2020-09-09 22:08:05 more
  • 在webform中使用ajax

    如果你用過Asp.net webform, 說明你也算是.NET 開發的老兵了。WEBform應該是2011 2013左右,當時還用visual studio 2005、 visual studio 2008。后來基本都用的是MVC。 如果是新開發的專案,估計沒人會用webform技術。但是有些舊版 ......

    uj5u.com 2020-09-09 22:08:50 more
  • iis添加asp.net網站,訪問提示:由于擴展配置問題而無法提供您請求的

    今天在iis服務器配置asp.net網站,遇到一個問題,記錄一下: 問題:由于擴展配置問題而無法提供您請求的頁面。如果該頁面是腳本,請添加處理程式。如果應下載檔案,請添加 MIME 映射。 WindowServer2012服務器,添加角色安裝完.netframework和iis之后,運行aspx頁面 ......

    uj5u.com 2020-09-09 22:10:00 more
  • WebAPI-處理架構

    帶著問題去思考,大家好! 問題1:HTTP請求和回傳相應的HTTP回應資訊之間發生了什么? 1:首先是最底層,托管層,位于WebAPI和底層HTTP堆疊之間 2:其次是 訊息處理程式管道層,這里比如日志和快取。OWIN的參考是將訊息處理程式管道的一些功能下移到堆疊下端的OWIN中間件了。 3:控制器處理 ......

    uj5u.com 2020-09-09 22:11:13 more
  • 微信門戶開發框架-使用指導說明書

    微信門戶應用管理系統,采用基于 MVC + Bootstrap + Ajax + Enterprise Library的技術路線,界面層采用Boostrap + Metronic組合的前端框架,資料訪問層支持Oracle、SQLServer、MySQL、PostgreSQL等資料庫。框架以MVC5,... ......

    uj5u.com 2020-09-09 22:15:18 more
  • WebAPI-HTTP編程模型

    帶著問題去思考,大家好!它是什么?它包含什么?它能干什么? 訊息 HTTP編程模型的核心就是訊息抽象,表示為:HttPRequestMessage,HttpResponseMessage.用于客戶端和服務端之間交換請求和回應訊息。 HttpMethod類包含了一組靜態屬性: private stat ......

    uj5u.com 2020-09-09 22:15:23 more
  • 部署WebApi隨筆

    一、跨域 NuGet參考Microsoft.AspNet.WebApi.Cors WebApiConfig.cs中配置: // Web API 配置和服務 config.EnableCors(new EnableCorsAttribute("*", "*", "*")); 二、清除默認回傳XML格式 ......

    uj5u.com 2020-09-09 22:15:48 more
最新发布
  • C#多執行緒學習(二) 如何操縱一個執行緒

    <a href="https://www.cnblogs.com/x-zhi/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/2943582/20220801082530.png" alt="" /></...

    uj5u.com 2023-04-19 09:17:20 more
  • C#多執行緒學習(二) 如何操縱一個執行緒

    C#多執行緒學習(二) 如何操縱一個執行緒 執行緒學習第一篇:C#多執行緒學習(一) 多執行緒的相關概念 下面我們就動手來創建一個執行緒,使用Thread類創建執行緒時,只需提供執行緒入口即可。(執行緒入口使程式知道該讓這個執行緒干什么事) 在C#中,執行緒入口是通過ThreadStart代理(delegate)來提供的 ......

    uj5u.com 2023-04-19 09:16:49 more
  • 記一次 .NET某醫療器械清洗系統 卡死分析

    <a href="https://www.cnblogs.com/huangxincheng/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/214741/20200614104537.png" alt="" /&g...

    uj5u.com 2023-04-18 08:39:04 more
  • 記一次 .NET某醫療器械清洗系統 卡死分析

    一:背景 1. 講故事 前段時間協助訓練營里的一位朋友分析了一個程式卡死的問題,回過頭來看這個案例比較經典,這篇稍微整理一下供后來者少踩坑吧。 二:WinDbg 分析 1. 為什么會卡死 因為是表單程式,理所當然就是看主執行緒此時正在做什么? 可以用 ~0s ; k 看一下便知。 0:000> k # ......

    uj5u.com 2023-04-18 08:33:10 more
  • SignalR, No Connection with that ID,IIS

    <a href="https://www.cnblogs.com/smartstar/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/u36196.jpg" alt="" /></a>...

    uj5u.com 2023-03-30 17:21:52 more
  • 一次對pool的誤用導致的.net頻繁gc的診斷分析

    <a href="https://www.cnblogs.com/dotnet-diagnostic/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/3115652/20230225090434.png" alt=""...

    uj5u.com 2023-03-28 10:15:33 more
  • 一次對pool的誤用導致的.net頻繁gc的診斷分析

    <a href="https://www.cnblogs.com/dotnet-diagnostic/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/3115652/20230225090434.png" alt=""...

    uj5u.com 2023-03-28 10:13:31 more
  • C#遍歷指定檔案夾中所有檔案的3種方法

    <a href="https://www.cnblogs.com/xbhp/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/957602/20230310105611.png" alt="" /></a&...

    uj5u.com 2023-03-27 14:46:55 more
  • C#/VB.NET:如何將PDF轉為PDF/A

    <a href="https://www.cnblogs.com/Carina-baby/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/2859233/20220427162558.png" alt="" />...

    uj5u.com 2023-03-27 14:46:35 more
  • 武裝你的WEBAPI-OData聚合查詢

    <a href="https://www.cnblogs.com/podolski/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/616093/20140323000327.png" alt="" /><...

    uj5u.com 2023-03-27 14:46:16 more