aspnet について
ASP.NET Core のバックエンド開発向け規約と例をまとめたスキルです。REST API、EF Core によるデータアクセス、認証・認可を扱う開発チーム向けに、構成や命名、応答処理、品質確認を示します。
主な機能
- 01.NET 8+ と ASP.NET Core の技術スタック規約
- 02レイヤー構成、命名、DTO 接尾辞の定義
- 03RESTful CRUD ルートと非同期メソッドの標準化
- 04統一応答形式と業務例外の処理
- 05JWT 認証と Policy ベース認可の説明
- 06品質チェック項目と一般的なアンチパターン
活用シーン
- 01ASP.NET Core プロジェクトのフォルダーとレイヤー構成
- 02一貫性のある REST API ルートと応答の設計
- 03JWT 認証と権限ポリシーの実装
- 04バックエンド規約と例外処理のレビュー
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ youlaitech/ youlai-skills --skill aspnetインストール後、Claude Code で/aspnetを入力して読み込まれたか確認します。
詳細
ASP.NET Core バックエンド開発規約
トリガー条件
- ASP.NET Core プロジェクトを開発する
- REST API を実装する
- データアクセスに Entity Framework Core を使用する
- JWT 認証を実装する
- 権限制御を実装する
Part 1: 技術スタック
| レイヤー | 選定技術 | 説明 |
|---|---|---|
| 実行環境 | .NET 8+ | クロスプラットフォームのランタイム |
| Web フレームワーク | ASP.NET Core | 依存性注入とミドルウェアパイプラインを内蔵 |
| ORM | Entity Framework Core | LINQ クエリ、Code First、マイグレーション |
| データベース | MySQL 8.x | InnoDB エンジン |
| キャッシュ | Redis 7.x | 分散キャッシュ |
| 認証 | JWT (Microsoft.AspNetCore.Authentication.JwtBearer) | Token 認証 |
| API ドキュメント | Swashbuckle | OpenAPI 生成 |
Part 2: ディレクトリ構造
src/
├── Youlai.Api/ # API 层(Controller + 中间件)
│ ├── Program.cs # 启动配置
│ ├── Controllers/ # 按模块分目录,复数命名
│ │ ├── Auth/{AuthController, WxMaAuthController}
│ │ ├── System/{UsersController, RolesController, MenusController, ...}
│ │ ├── Codegen/{CodegenController}
│ │ ├── File/{FilesController}
│ │ └── Message/{SseController}
│ ├── Filters/ # LogActionFilter
│ ├── Middlewares/ # ExceptionHandlingMiddleware, RateLimitMiddleware
│ ├── Security/ # CurrentUser, HasPermAttribute, JwtExtensions, PermissionAuthorizationHandler
│ └── Swagger/ # FileUploadOperationFilter
│
├── Youlai.Application/ # 应用层(Service + DTO)
│ ├── DependencyInjection.cs
│ ├── Auth/ # IAuthService, AuthService, WxMaAuthService + Models/
│ ├── Common/ # ILoggingService, SseService
│ ├── Constants/ # RedisKeyConstants
│ ├── Exceptions/ # BusinessException
│ ├── Options/ # SecurityOptions, RedisOptions, WxMaOptions
│ ├── Results/ # Result, PageResult, ResultCode
│ ├── Security/ # ICurrentUser, JwtTokenManager, DataPermissionService + Models/
│ └── System/ # IUserService, UserService 等 + Models/{User,Role,Menu,Dept,...}/
│
├── Youlai.Domain/ # 领域层(最薄,无依赖)
│ ├── Entities/ # SysUser, SysRole, SysMenu 等
│ └── Enums/ # ActionType, LogModule, MenuType
│
├── Youlai.Infrastructure/ # 基础设施层
│ ├── DependencyInjection.cs
│ ├── Persistence/AppDbContext.cs
│ ├── FileStorage/ # LocalFileStorage, MinioFileStorage
│ └── CodegenTemplates/ # Scriban 模板
│
└── tests/Youlai.Api.Tests/ # 集成测试
設計原則:
Youlai.Domainは最も薄いレイヤーで、他のレイヤーに依存しませんYoulai.ApplicationはDomainに依存し、ApiとInfrastructureには依存しませんYoulai.Infrastructureはインターフェースを実装し、Applicationに依存しますYoulai.Apiはエントリーポイントとして、すべてのレイヤーを組み合わせます
Part 3: 命名規約
3.1 ファイル名
| 種類 | 規約 | 例 |
|---|---|---|
| Controller | {Entity}Controller.cs | UserController.cs |
| Service インターフェース | I{Entity}Service.cs | IUserService.cs |
| Service 実装 | {Entity}Service.cs | UserService.cs |
| Entity | PascalCase | SysUser.cs |
| DTO | 機能 + 型の接尾辞 | UserForm.cs, UserVO.cs |
| Enum | PascalCase + 接尾辞なし | ActionType.cs |
3.2 クラス名
| 種類 | 規約 | 例 |
|---|---|---|
| Controller | {Entity}Controller | UserController |
| Service インターフェース | I{Entity}Service | IUserService |
| Service 実装 | {Entity}Service | UserService |
| Entity | PascalCase | SysUser |
| DTO | 機能 + 型 | UserVO, UserForm, UserPageQuery |
DTO 接尾辞の規約:
| 接尾辞 | 用途 | 例 |
|---|---|---|
VO | ビューオブジェクト(フロントエンドへの返却用) | UserVO, RolePageVO |
Form / RequestDto | フォーム/リクエストオブジェクト | UserForm, LoginRequestDto |
Query | クエリパラメーター | UserQuery, RolePageQuery |
Dto | 転送オブジェクト | CurrentUserDto, ExcelResultDto |
3.3 メソッド名
| 操作 | 接頭辞 | 例 |
|---|---|---|
| 単一項目の取得 | Get | GetByIdAsync() |
| リストの取得 | List | ListUsersAsync() |
| ページング取得 | Page | PageUsersAsync() |
| 新規作成 | Create | CreateUserAsync() |
| 更新 | Update | UpdateUserAsync() |
| 削除 | Delete | DeleteUserAsync() |
| ドロップダウンの選択肢 | GetXxxOptions | GetUserOptionsAsync() |
3.4 変数名
| 種類 | 規約 | 例 |
|---|---|---|
| ローカル変数 | camelCase | userList |
| 定数 | PascalCase | MaxSize |
| プライベートフィールド | _ 接頭辞の camelCase | _userService |
| ブール値 | Is/Has/Can 接頭辞 | IsDeleted, HasPermission |
⚠️ ASP.NET Core では、プライベートフィールドに
_接頭辞を付けるのが規約です(Spring Boot とは異なります)。公式ドキュメントおよび EF Core と同じ規約です。
Part 4: RESTful API 規約
4.1 標準 CRUD パス
| 操作 | メソッド | パス | 説明 |
|---|---|---|---|
| ページングリスト | GET | /api/v1/users/page | Query パラメーター |
| 詳細 | GET | /api/v1/users/{id} | パスパラメーター |
| フォームデータ | GET | /api/v1/users/{id}/form | 編集画面の初期値 |
| 新規作成 | POST | /api/v1/users | Body: UserForm |
| 更新 | PUT | /api/v1/users | Body: UserForm |
| 削除 | DELETE | /api/v1/users/{id} | パスパラメーター |
| 一括削除 | DELETE | /api/v1/users/batch | Body: List<long> |
| ドロップダウンの選択肢 | GET | /api/v1/users/options | List<OptionType> を返す |
4.2 Controller テンプレート
[ApiController]
[Route("api/v1/users")]
[ApiExplorerSettings(GroupName = "用户管理")]
public class UserController : ControllerBase
{
private readonly IUserService _userService;
public UserController(IUserService userService) =>
_userService = userService;
[HttpGet("page")]
[SwaggerOperation(Summary = "用户分页列表")]
public async Task<Result<PageResult<UserVO>>> Page([FromQuery] UserPageQuery query) =>
Result.Success(await _userService.PageUsersAsync(query));
[HttpPost]
[SwaggerOperation(Summary = "新增用户")]
[Authorize(Policy = "sys:user:add")]
public async Task<Result<long>> Create([FromBody] UserForm form) =>
Result.Success(await _userService.CreateUserAsync(form));
[HttpPut]
[SwaggerOperation(Summary = "更新用户")]
[Authorize(Policy = "sys:user:edit")]
public async Task<Result> Update([FromBody] UserForm form)
{
await _userService.UpdateUserAsync(form);
return Result.Success();
}
[HttpDelete("{id}")]
[SwaggerOperation(Summary = "删除用户")]
[Authorize(Policy = "sys:user:delete")]
public async Task<Result> Delete(long id)
{
await _userService.DeleteUserAsync(id);
return Result.Success();
}
[HttpGet("options")]
[SwaggerOperation(Summary = "用户选项列表")]
public async Task<Result<List<OptionType>>> Options() =>
Result.Success(await _userService.GetUserOptionsAsync());
}
Part 5: レスポンス形式と例外処理
5.1 統一レスポンス
public class Result<T>
{
public int Code { get; set; }
public string Msg { get; set; } = string.Empty;
public T? Data { get; set; }
public static Result<T> Success(T? data = default) => new() { Code = 200, Msg = "操作成功", Data = data };
public static Result<T> Failed(int code, string msg) => new() { Code = code, Msg = msg };
}
public class PageResult<T>
{
public List<T> List { get; set; } = [];
public long Total { get; set; }
public static PageResult<T> Of(List<T> list, long total) => new() { List = list, Total = total };
}
5.2 ビジネス例外
public class BusinessException : Exception
{
public BusinessException(string message) : base(message) { }
}
5.3 グローバル例外処理
public class ExceptionMiddleware
{
private readonly RequestDelegate _next;
public ExceptionMiddleware(RequestDelegate next) => _next = next;
public async Task InvokeAsync(HttpContext context)
{
try { await _next(context); }
catch (BusinessException ex) { await WriteResult(context, 200, ex.Message); }
catch (Exception ex) { await WriteResult(context, 500, "系统异常"); }
}
private static async Task WriteResult(HttpContext context, int code, string msg)
{
context.Response.StatusCode = 200;
context.Response.ContentType = "application/json";
await context.Response.WriteAsync(JsonSerializer.Serialize(new { Code = code, Msg = msg, Data = (object?)null }));
}
}
Part 6: エンティティ規約
public abstract class BaseEntity
{
[Key][Column("id")] public long Id { get; set; }
[Column("create_time")] public DateTime CreateTime { get; set; } = DateTime.Now;
[Column("update_time")] public DateTime UpdateTime { get; set; } = DateTime.Now;
[Column("deleted")] public bool Deleted { get; set; } = false;
}
[Table("sys_user")]
public class SysUser : BaseEntity
{
[Required][MaxLength(50)][Column("username")] public string Username { get; set; } = "";
[Required][MaxLength(100)][Column("password")] public string Password { get; set; } = "";
[Required][MaxLength(50)][Column("nickname")] public string Nickname { get; set; } = "";
[MaxLength(20)][Column("mobile")] public string? Mobile { get; set; }
[Column("status")] public int Status { get; set; } = 1;
[Column("dept_id")] public long? DeptId { get; set; }
}
Part 7: Service 規約
public interface IUserService
{
Task<PageResult<UserVO>> PageUsersAsync(UserPageQuery query);
Task<UserVO> GetUserByIdAsync(long id);
Task<UserForm> GetUserFormDataAsync(long id);
Task<long> CreateUserAsync(UserForm form);
Task UpdateUserAsync(UserForm form);
Task DeleteUserAsync(long id);
}
public class UserService : IUserService
{
private readonly AppDbContext _context;
public UserService(AppDbContext context) => _context = context;
public async Task<PageResult<UserVO>> PageUsersAsync(UserPageQuery query)
{
var q = _context.Users.Where(u => !u.Deleted);
if (!string.IsNullOrEmpty(query.Keywords))
q = q.Where(u => u.Username.Contains(query.Keywords));
if (query.Status.HasValue)
q = q.Where(u => u.Status == query.Status.Value);
var total = await q.CountAsync();
var list = await q.OrderByDescending(u => u.CreateTime)
.Skip((query.PageNum - 1) * query.PageSize).Take(query.PageSize)
.Select(u => new UserVO { Id = u.Id, Username = u.Username, Status = u.Status, CreateTime = u.CreateTime })
.ToListAsync();
return PageResult<UserVO>.Of(list, total);
}
public async Task<long> CreateUserAsync(UserForm form)
{
if (await _context.Users.AnyAsync(u => u.Username == form.Username && !u.Deleted))
throw new BusinessException("用户名已存在");
var user = new SysUser { Username = form.Username, Password = BCrypt.HashPassword(form.Password), Status = form.Status };
_context.Users.Add(user);
await _context.SaveChangesAsync();
return user.Id;
}
}
Part 8: 認証規約
8.1 JWT の設定
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options => {
options.TokenValidationParameters = new TokenValidationParameters {
ValidateIssuer = true,
ValidIssuer = builder.Configuration["Jwt:Issuer"],
IssuerSigningKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Secret"]!))
};
});
builder.Services.AddAuthorization(options => {
options.AddPolicy("sys:user:add", p => p.RequireClaim("permission", "sys:user:add"));
options.AddPolicy("sys:user:edit", p => p.RequireClaim("permission", "sys:user:edit"));
});
8.2 権限チェック
// Controller 中使用 Policy 进行权限校验
[Authorize(Policy = "sys:user:add")]
// 权限标识命名:模块:实体:操作
// sys:user:create / sys:user:edit / sys:user:delete / sys:role:list / sys:menu:assign
8.3 現在のユーザー
public interface ICurrentUser
{
long UserId { get; }
string Username { get; }
List<string> Permissions { get; }
}
public class CurrentUser : ICurrentUser
{
public long UserId => long.Parse(HttpContext.User.FindFirst(JwtRegisteredClaimNames.Sub)?.Value ?? "0");
public string Username => HttpContext.User.FindFirst(JwtRegisteredClaimNames.UniqueName)?.Value ?? "";
public List<string> Permissions => HttpContext.User.FindFirst("permission")?.Value?.Split(',').ToList() ?? [];
}
Part 9: コメント規約
- 公開メソッドには
/// <summary>の XML コメントを使用します(IDE のインテリセンスで認識できます) - Swagger では
[SwaggerOperation(Summary = "...")]を使用します - コードを変更する際は、コメントも必ず更新します
/// <summary>
/// 用户管理控制器。
/// </summary>
[ApiExplorerSettings(GroupName = "用户管理")]
public class UserController : ControllerBase
{
/// <summary>
/// 创建新用户。
/// </summary>
/// <param name="form">用户表单(username 必填且唯一)</param>
/// <returns>新用户 ID</returns>
/// <exception cref="BusinessException">用户名已存在</exception>
[HttpPost]
[SwaggerOperation(Summary = "新增用户")]
public async Task<Result<long>> Create([FromBody] UserForm form) { ... }
}
Part 10: コード品質チェックリスト
- RESTful API のパス規約に従っている
- 統一レスポンス形式を使用している
- グローバル例外処理ミドルウェアを実装している
- 権限チェックに
Authorize(Policy = "...")を使用している - ページングインターフェースで
PageResultを使用している - エンティティが
BaseEntityを継承している - EF Core の論理削除を使用している
- API に
[SwaggerOperation]注釈を追加している - レイヤー構成が Controller → Service → Repository になっている
- 非同期メソッドの末尾が
Asyncになっている - 公開メソッドに
/// <summary>の XML コメントがある
常见反模式
| 反模式 | 正确做法 |
|---|---|
throw new Exception("用户不存在") | throw new BusinessException("用户不存在") |
catch { } 空捕获 | 捕获并记录日志或抛出业务异常 |
public async Task<T> Xxx() 无 Async 后缀 | 加 Async 后缀:XxxAsync() |
| Controller 中写业务逻辑 | 委托 Service 层处理 |
同步方法调 SaveChangesAsync().Result | 使用 await SaveChangesAsync() |
コメント
Markdown 対応