オリジナル住所:ASP.NET-コアのWeb-API-ベストプラクティスガイド
はじめに#
我々はプロジェクトを書くとき、私たちの主な目標は、それがスケジュールどおりに実行させる、そして可能な限りユーザーのすべてのニーズを満たすことです。
しかし、あなたはそれがプロジェクトに十分なことを作成するために働くことができるとは思いませんか?それプロジェクトはまた、保守性と読みやすいべきではありません。同時に?
それは我々が我々のプロジェクトの可読性と保守性のより多くの焦点プットを配置する必要があることが判明しました。この背後にある主な理由は、我々がこのプロジェクトの唯一の作家ではないかもしれないということです。我々が完了したら、他の人は、ほとんどの場合、この来る内部に追加されます。
したがって、我々はそれ焦点を置くべきですか?
この中でガイドです、.NETのコアのWeb APIプロジェクトの開発に、私たちは私たちのいくつかは実際に最善の方法だろうと思っ説明します。そして、私たちのプロジェクトは、より良く、より保守します。
さて、ベストプラクティスのいくつかのASP.NETのWeb APIプロジェクトの一部に適用することができますについての思考を始めましょう。
スタートアップクラスおよびサービスの設定#
STARTUP CLASSおよびサービスの設定
でStartup
クラス、2つの方法がある:ConfigureServices
サービス登録のために、Configure
要求パイプラインミドルウェアアプリケーションに追加されます。
そのため、維持することです最善の方法ConfigureServices
この方法は、可能な限りシンプルで読みやすいです。もちろん、我々はサービスを登録するためのメソッド内のコードを記述する必要がありますが、我々は使用することができ扩展方法
、私たちのコードが読みやすくかつ保守的にします。
例えば、悪い方法のCORS登録サービスで見てみましょう:
Copypublic void ConfigureServices(IServiceCollection services)
{
services.AddCors(options =>
{
options.AddPolicy("CorsPolicy", builder => builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials());
});
}
このアプローチは良いが見えますが、通常は成功しCORSサービスを登録することができます。しかし、この方法ダース登録サービスよりも後に体の長さを想像してみてください。
そのようには読めません。
良い方法は、拡張クラスの静的メソッドを作成することです:
Copypublic static class ServiceExtensions
{
public static void ConfigureCors(this IServiceCollection services)
{
services.AddCors(options =>
{
options.AddPolicy("CorsPolicy", builder => builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials());
});
}
}
次に、あなただけの、この拡張メソッドを呼び出す必要があります:
Copypublic void ConfigureServices(IServiceCollection services)
{
services.ConfigureCors();
}
より多くの.NETのコアプロジェクトの設定について読み、参照してください。.NETのコアプロジェクト構成
プロジェクト組織#
プロジェクトORGANIZATION
私たちは、複数の小さなプロジェクトに私たちのアプリケーションを分割しようとする必要があります。このように、我々は最高のプロジェクト組織、および懸念の缶分離(SOC)を取得することができます。私たちの事業体のビジネスロジック、契約、データベース操作へのアクセス、ログ情報や送信メールは、常に別の.NETコアクラスライブラリプロジェクトに配置する必要があります。
各アプリケーションは、ビジネス・ロジックを整理するために使用される複数のフォルダ内の小規模なプロジェクトを含める必要があります。
ここでは、複雑なプロジェクトを編成する必要があるか示すための簡単な例です:
環境設定に基づいて#
ENVIRONMENTベースの設定
我々はアプリケーションを開発する場合、それは、開発環境です。私たちは解放しかし、一度、それは本番環境になります。そのため、環境を隔離するように構成された各は、多くの場合、実際には良い方法です。
実施するのは非常に簡単です.NETのコア、で。
我々は良いプロジェクトを作成したら、すでにあるappsettings.json
ファイルは、我々が表示されますときにそれを展開し、appsettings.Development.json
ファイルを:
このファイル内のすべての設定は、開発環境のために使用されます。
私たちは、別のファイルを追加する必要がありappsettings.Production.json
、本番環境で使用され、:
生産ファイルには、開発ファイルの下に配置されます。
変更内容を設定した後、我々は、用途に応じて、異なるのAppSettingsによって異なる構成をファイルをロードすることができます私たちの環境では現在、.NETのコアは、正しい設定でご提供します。:このトピックの詳細については、を参照してくださいASP.NETコアで複数環境。
データアクセスレイヤ#
データアクセス層
チュートリアルいくつかの異なる実施例では、我々は、DALの主なプロジェクトの実施、及び両方の場合の各コントローラを見ることができます。我々はそれをお勧めしません。
我々はDALを書くとき、私たちは、作成するスタンドアロンのサービスとしてでなければなりません。スタンドアロンのサービスとして、ときに我々DAL、我々はIOC(制御の反転)コンテナに直接注入することができますので、.NETのコアプロジェクトでは、これは、重要です。IOCは、.NETのコアが内蔵された機能です。このように、我々は、任意のコントローラで、注射の方法により、コンストラクタを使用することができます。
Copypublic class OwnerController: Controller
{
private readonly IRepository _repository;
public OwnerController(IRepository repository)
{
_repository = repository;
}
}
コントローラー#
CONTROLLERS
コントローラは常に清潔できちんと保管しなければなりません。私たちは、どのようなビジネスロジックに置くべきではありません。
したがって、我々は、コントローラのコンストラクタの実施形態を介して注入されるべきサービスのインスタンスを受信し、そして組織HTTP(GET、POST、PUT、DELETE、PATCH ...)の動作の方法:
Copypublic 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)
{
}
}
私たちの行動はそれをシンプルに保つために試してみてください、彼らの任務は、HTTPリクエストを処理含める必要があり、検証モデルは、例外をキャッチし、応答を返します。
Copy[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");
}
}
ほとんどの場合、私たちの行動をしなければならないIActonResult
戻り値の型として使用する(時には我々は、特定のタイプまたはに戻りたいですJsonResult
...)。この方法によって、我々は、.NETのコア戻り値とステータスコードで構築を有効に利用することができます。
最も使用されている方法は次のとおりです。
- OK => 200のステータスコードを返します。
- NOTFOUND => 404のステータスコードを返します
- BadRequest => 400のステータスコードを返します
- NoContent => 204のステータスコードを返します
- 作成、CreatedAtRoute、CreatedAtAction => 201のステータスコードを返します
- 不正=> 401のステータスコードを返します
- 禁じる=>は403のステータスコードを返します。
- StatusCode =>入力として、私たちが提供するステータスコードを返します。
グローバル例外処理#
GLOBALLYエラーの処理
上記の例では、内部アクション持っているtry-catch
コードのブロックを。これは重要ですが、私たちはアクションメソッド本体に(未処理を含む)すべての例外に対処する必要があります。一部の開発者は、アクションで使用されるtry-catch
何の問題もなく、ブロック明確にこの方法を。しかし、我々はそれ簡単なアクションを維持しようとします。したがって、我々の行動からそれを削除try-catch
し、集中場所を配置することは、より良い方法だろう。.NETのコアは、わずかな修正を必要とする、グローバルな例外に対処するための方法を提供してくれる、あなたは内蔵のサウンドおよびミドルウェアを使用することができます。私たちは、修正され行う必要がStartup
変更されたクラスのConfigure
メソッドを:
Copypublic 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();
});
}
我々はまた、カスタムミドルウェアを作成することにより、当社の独自の例外処理を実現することができます。
Copy// 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>();
}
}
その後、我々は唯一の要求パイプラインのアプリケーションにそれを注入する必要があります。
Copypublic void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseCustomExceptionMiddleware();
}
重複したコード削除するフィルタを使用して#を
重複するコードを削除TO ACTIONFILTERSを使用しました
ASP.NETコアフィルタは、私たちは、特定の状態またはパイプラインの後を要求する前にいくつかのコードを実行することができます。私たちがアクションを複製しているのであれば、検証作業を単純化するために使用することができます。
我々はPUTやアクションでPOSTリクエストメソッドを扱うとき、我々は我々の期待に沿った我々のモデルオブジェクトことを確認する必要があります。その結果、これは認証コードを繰り返すために私たちを導くだろう、私たちは、このような状況を避けたい(実質的には、我々は任意のコードの重複を避けるため、すべてを行う必要があります。)私たちは、認証コードのActionFilterを使用することによって置き換えることができますコード:
Copyif (!ModelState.IsValid)
{
//bad request and logging logic
}
私たちは、フィルタを作成することができます。
Copypublic class ModelValidationAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
if (!context.ModelState.IsValid)
{
context.Result = new BadRequestObjectResult(context.ModelState);
}
}
}
その後、Startup
クラスConfigureServices
の注入の機能で:
Copyservices.AddScoped<ModelValidationAttribute>();
私たちは今、私たちのアプリケーションでは、上述のフィルタアクションに注入することができます。
Microsoft.AspNetCore.Allの元のパッケージ#
MICROSOFT.ASPNETCORE.ALL META-PACKAGE
注:2.1とASP.NETコアのそれ以降のバージョンを使用している場合。むしろMicrosoft.AspNetCore.Allよりも、Microsoft.AspNetCore.Appパッケージをお勧めします。このすべては、セキュリティ上の理由からです。新しいプロジェクトを作成WEBAPIバージョン2.1を使用している場合に加えて、我々は自動的に代わりAspNetCore.AppパッケージAspNetCore.Allを取得します。
このメタパッケージは、すべてのAspNetCore関連のパッケージ、EntityFrameworkCoreパッケージ、SignalRパッケージ(バージョン2.1)およびサポートパッケージの実行に依存するフレームワークが含まれています。私たちは手動で私たちがパッケージ化するのに使用するかもしれないいくつかをインストールする必要はありませんので、このようにして、新しいプロジェクトを作成するのは簡単です。
もちろん、Microsoft.AspNetCore.allの元のパッケージを使用することができるようにするために、あなたはあなたのマシンが.NETコアランタイムがインストールされていることを確認する必要があります。
ルート#
ルーティング
.NETのコアウェブAPIプロジェクトでは、我々は、プロパティは、私たちはアクションの実際のルーティングパラメータ名とパラメータ方法を一致させることができますので、このルートがあり、伝統的なルートの代わりに属性のルートを使用する必要があります。もう一つの理由は、より読みやすく、「ID」よりも、私たちのために、パラメータが「OWNERIDを」という名前のルートパラメーターの説明です。
我々は使用することができ、[ルート]コントローラのトップをマークする属性を:
Copy[Route("api/[controller]")]
public class OwnerController : Controller
{
[Route("{id}")]
[HttpGet]
public IActionResult GetOwnerById(Guid id)
{
}
}
制御および操作のためのルーティングルールを作成する別の方法があります:
Copy[Route("api/owner")]
public class OwnerController : Controller
{
[Route("{id}")]
[HttpGet]
public IActionResult GetOwnerById(Guid id)
{
}
}
これは、いくつかの意見の相違のこれら二つの方法のために良いだろうが、我々は常に第二のアプローチをお勧めします。これは、私たちがプロジェクトで使用されてきた方法です。
我々はルートについて話すとき、我々はルートを命名言及する必要があります。私たちは、業務用のわかりやすい名前を使用できますが、/ノードをルーティングするために、我々は代わりに名詞動詞を使用する必要があります。
貧困層の一例:
Copy[Route("api/owner")]
public class OwnerController : Controller
{
[HttpGet("getAllOwners")]
public IActionResult GetAllOwners()
{
}
[HttpGet("getOwnerById/{id}"]
public IActionResult GetOwnerById(Guid id)
{
}
}
良い例:
Copy[Route("api/owner")]
public class OwnerController : Controller
{
[HttpGet]
public IActionResult GetAllOwners()
{
}
[HttpGet("{id}"]
public IActionResult GetOwnerById(Guid id)
{
}
}
:RESTfulな練習の説明についての詳細は、以下を参照してください。トップREST APIのベストプラクティス
ログ#
LOGGING
私たちは、本番環境への私たちのアプリケーションを公開する場合、我々は場所でロギングメカニズムを追加する必要があります。私たちは、ソートアウト本番環境でアプリケーションを実行するためにログが便利です。
継承を通じて、.NETのコアILogger
、独自のロギングのインタフェース。容易に使用することができる依存性注入機構による。
Copypublic class TestController: Controller
{
private readonly ILogger _logger;
public TestController(ILogger<TestController> logger)
{
_logger = logger;
}
}
その後、私たちの行動に、私たちは_loggerオブジェクトを使用して、別のログレベルの援助をログに記録することができます。
.NETのコアのプロバイダは、ロギングの様々なサポートしています。したがって、我々は、プロジェクト内の別のプロバイダを使用して、当社のログ・ロジックを実現するために来るかもしれません。
NLogは、私たちの習慣を記録するために使用することができ、非常に良好なロジッククラスライブラリである、それは非常にスケーラブルです。構造化ログ記録のサポート、および設定が簡単。私たちは、コンソール、あるいはデータベースファイルに情報を記録することができます。
.NETのコアでのアプリケーションライブラリの詳細については、以下を参照してください。.NETのコアシリーズ- NLogでログを。
Serilogは、それが内蔵され、.NETのコアロギングシステムに適用され、また非常に良いライブラリです。
推奨される文字列連結:パフォーマンスのヒントを向上させることができ、ログがある
_logger.LogInformation("{0},{1}", DateTime.Now, "info")
代わりに、ログへのアプローチが_logger.LogInformation($"{DateTime.Now},info")
。
暗号化#
CRYPTOHELPER
私たちは、パスワードがデータベースにクリアテキストで保存されることをお勧めしません。セキュリティ上の理由から、我々はハッシュ化する必要があります。これは、このガイドの範囲を超えています。パスワードをハッシュするためにいくつかの良い方法を含むインターネット上のハッシュアルゴリズムの多くは、あります。
あなたは.NETのコアアプリケーションのための使いやすい暗号化ライブラリを提供する必要がある場合でも、CryptoHelperは良い選択です。
CryptoHelperは、達成するためのPBKDF2をベースに独立した暗号化ハッシュ.NETコアライブラリに適用可能です。作成することでData Protection
ハッシュされたパスワードスタックに。また、ライブラリには、非常に単純なNuGetで利用可能である、と。
Copyusing 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);
}
コンテンツネゴシエーション#
コンテントネゴシエーション
デフォルトでは、.NETのコアのWeb APIは、JSON形式で結果を返します。ほとんどの場合、これは我々が望むものです。
しかし、顧客は、それは、このようなXML形式などの他の形式に応じて、弊社のWeb APIの戻りを、どうすればよいでしょうか。
この問題を解決するために、我々は、オンデマンド形式のために私たちの応答の結果をサーバーを設定する必要があります。
Copypublic void ConfigureServices(IServiceCollection services)
{
services.AddControllers().AddXmlSerializerFormatters();
}
練習への最善の方法は、未処理の統一フォーマットを返します406ステータスコードを要求することですので、しかし、時にはクライアントは、我々は、Web APIをサポートしていないフォーマットを要求します。この実施形態はまた、単純なプロセスConfigureServicesに配置することができます。
Copypublic void ConfigureServices(IServiceCollection services)
{
services.AddControllers(options => options.ReturnHttpNotAcceptable = true).AddXmlSerializerFormatters();
}
また、当社独自の書式設定ルールを作成することができます。
この部分は大きな話題である、あなたはもっと知りたい場合は、を参照してください。.NETのコアでコンテントネゴシエーション
JWTの使用#を
JWTを使用しました
今日のWeb開発、JSONウェブトークン(JWT)は、ますます人気が高まっています。.NETのコアJWTのサポートを内蔵し、実装することは非常に簡単に感謝します。JWTは、サーバーとクライアント上のデータ伝送のセキュリティのためのJSON形式に私たちを可能にする開発標準、です。
私たちは、JWTの認定にConfigureServicesを設定することができます。
Copypublic 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
};
});
}
アプリケーションで使用できるようにするために、我々はまた、設定で次のコードで呼び出す必要があります:
Copypublic void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseAuthentication();
}
次のように加えて、トークンを作成する使用することができます。
Copyvar 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)
コントローラは、ユーザ認証トークンに基づいて、以下のように使用することができます。
Copyvar auth = await HttpContext.AuthenticateAsync();
var id = auth.Principal.Claims.FirstOrDefault(x => x.Type.Equals(ClaimTypes.NameIdentifier))?.Value;
また、JWTは、許可セクションのために、簡単にすることができJWT構成の役割にステートメントを追加することができます。
:その他の部分では、.NETのコアJWTの認証および承認については、以下を参照してください認証aspnetcore-JWT-1および認証aspnetcore-JWT-2
要約#
これを読んで、友人があるかもしれない全体の記事のような、実践ガイドラインに沿って、より多くのプロジェクトについて話をしなかったので、これらのベストプラクティスのいくつかの合意ではあまりないTDD、DDDとそうで。しかし、私は個人的には、上記のすべては、ベストプラクティス、より良い診療ガイドラインのより高いレベルのいくつかを理解するために習得のみこれらの基本の基本だと思います。少しドングリからオークス、あなたが初心者のためのベストプラクティスガイドとしてこれを見ることができます。
このガイドでは、私たちの主な目的は、Web API .NETのコア開発プロジェクト際に使用するためのベストプラクティスのいくつかに慣れることです。パートは、他のフレームにも適用があります。そのため、彼らは便利なマスタリング。
それはあなたを助けるでしょう願って、このガイドを読むためにありがとうございます。