aspnet-core について
ASP.NET Core 開発者向けのクイックリファレンススキルで、一般的なアプリ設定や実装パターンを扱います。コントローラー、サービス、依存性注入、設定、例外処理の例とトラブルシューティングを含みます。
主な機能
- 01Program.cs のアプリ設定
- 02コントローラーとサービスのパターン
- 03依存性注入のライフタイム
- 04設定バインディングの例
- 05グローバル例外処理
- 06一般的な問題の診断
活用シーン
- 01ASP.NET Core アプリの構成
- 02コントローラーとサービスのコード整理
- 03依存性注入のライフタイム選択
- 04ルーティングや設定の問題診断
MCP 対応
包括的なドキュメントを参照するため、技術識別子 `aspnet-core` とともに `mcp__documentation__fetch_docs` を案内します。
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ claude-dev-suite/ claude-dev-suite --skill aspnet-coreインストール後、Claude Code で/aspnet-coreを入力して読み込まれたか確認します。
詳細
ASP.NET Core - クイックリファレンス
詳細な知識:包括的なドキュメントについては、technology:
aspnet-coreを指定してmcp__documentation__fetch_docsを使用してください。
Program.cs の設定
var builder = WebApplication.CreateBuilder(args);
// Services
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("Default")));
// Dependency injection
builder.Services.AddScoped<IUserService, UserService>();
builder.Services.AddScoped<IUserRepository, UserRepository>();
// Configuration
builder.Services.Configure<JwtOptions>(builder.Configuration.GetSection("Jwt"));
var app = builder.Build();
// Middleware pipeline (order matters!)
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();
コントローラーパターン
[ApiController]
[Route("api/[controller]")]
[Produces("application/json")]
public class UsersController : ControllerBase
{
private readonly IUserService _userService;
public UsersController(IUserService userService) => _userService = userService;
[HttpGet]
[ProducesResponseType<IEnumerable<UserResponse>>(StatusCodes.Status200OK)]
public async Task<IActionResult> GetAll([FromQuery] int page = 1, [FromQuery] int size = 10)
{
var users = await _userService.GetAllAsync(page, size);
return Ok(users);
}
[HttpGet("{id:int}")]
[ProducesResponseType<UserResponse>(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> GetById(int id)
{
var user = await _userService.GetByIdAsync(id);
return user is null ? NotFound() : Ok(user);
}
[HttpPost]
[ProducesResponseType<UserResponse>(StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<IActionResult> Create([FromBody] CreateUserRequest request)
{
var user = await _userService.CreateAsync(request);
return CreatedAtAction(nameof(GetById), new { id = user.Id }, user);
}
[HttpPut("{id:int}")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> Update(int id, [FromBody] UpdateUserRequest request)
{
var result = await _userService.UpdateAsync(id, request);
return result ? NoContent() : NotFound();
}
[HttpDelete("{id:int}")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public async Task<IActionResult> Delete(int id)
{
await _userService.DeleteAsync(id);
return NoContent();
}
}
サービスレイヤー
public interface IUserService
{
Task<UserResponse?> GetByIdAsync(int id);
Task<IEnumerable<UserResponse>> GetAllAsync(int page, int size);
Task<UserResponse> CreateAsync(CreateUserRequest request);
Task<bool> UpdateAsync(int id, UpdateUserRequest request);
Task DeleteAsync(int id);
}
public class UserService : IUserService
{
private readonly IUserRepository _repository;
private readonly ILogger<UserService> _logger;
public UserService(IUserRepository repository, ILogger<UserService> logger)
{
_repository = repository;
_logger = logger;
}
public async Task<UserResponse?> GetByIdAsync(int id)
{
var user = await _repository.GetByIdAsync(id);
return user is null ? null : MapToResponse(user);
}
public async Task<UserResponse> CreateAsync(CreateUserRequest request)
{
var user = new User { Name = request.Name, Email = request.Email };
await _repository.AddAsync(user);
_logger.LogInformation("User {UserId} created", user.Id);
return MapToResponse(user);
}
private static UserResponse MapToResponse(User user)
=> new(user.Id, user.Name, user.Email, user.CreatedAt);
}
レコードを使用した DTO
public record CreateUserRequest(string Name, string Email);
public record UpdateUserRequest(string Name, string Email);
public record UserResponse(int Id, string Name, string Email, DateTime CreatedAt);
依存性注入のライフタイム
| ライフタイム | 用途 |
|---|---|
AddTransient<T> | 軽量でステートレスなサービス |
AddScoped<T> | リクエストごとのサービス(リポジトリ、DbContext) |
AddSingleton<T> | 共有状態、キャッシュ、設定 |
設定のバインド
// appsettings.json
// { "Jwt": { "Key": "...", "Issuer": "..." } }
public class JwtOptions
{
public string Key { get; set; } = default!;
public string Issuer { get; set; } = default!;
public string Audience { get; set; } = default!;
public int ExpiryMinutes { get; set; } = 60;
}
// Register
builder.Services.Configure<JwtOptions>(builder.Configuration.GetSection("Jwt"));
// Use
public class AuthService
{
private readonly JwtOptions _options;
public AuthService(IOptions<JwtOptions> options) => _options = options.Value;
}
グローバル例外処理
app.UseExceptionHandler(app => app.Run(async context =>
{
var exception = context.Features.Get<IExceptionHandlerFeature>()?.Error;
var response = exception switch
{
NotFoundException e => (StatusCodes.Status404NotFound, e.Message),
ValidationException e => (StatusCodes.Status400BadRequest, e.Message),
_ => (StatusCodes.Status500InternalServerError, "An unexpected error occurred"),
};
context.Response.StatusCode = response.Item1;
await context.Response.WriteAsJsonAsync(new { error = response.Item2 });
}));
アンチパターン
| アンチパターン | 問題点 | 正しいアプローチ |
|---|---|---|
| コントローラーにビジネスロジックを記述する | SRP に違反します | サービスレイヤーを使用します |
依存関係の生成に new を使う | テストできません | コンストラクター DI を使用します |
| DbContext をシングルトンにする | スレッドセーフではありません | Scoped ライフタイムを使用します |
| コントローラーですべての例外をキャッチする | 繰り返しが多く、一貫性がありません | グローバル例外ハンドラーを使用します |
| API からエンティティを返す | 内部情報を公開してしまいます | DTO/レコードを使用します |
クイックトラブルシューティング
| 問題 | 考えられる原因 | 解決策 |
|---|---|---|
| DI の解決エラー | 登録が不足しています | Program.cs でサービスを登録します |
| エンドポイントが 404 を返す | ルートテンプレートが間違っています | [Route] 属性を確認します |
| モデルバインディングが null になる | [FromX] 属性が間違っています | JSON には [FromBody] を使用します |
| 設定値が null になる | セクションパスが間違っています | GetSection() のパスを確認します |
コメント
Markdown 対応