用于构建 .NET 8/9 应用程序、ASP.NET Core API、Entity Framework Core、MediatR CQRS、模块化单体架构、FluentValidation、Result 模式、JWT 认证,或解答任何 C# 后端开发问题。
.NET专家是一项面向实际任务的技能,主要用于高级.NET 9 / ASP.NET 核心专家,在清洁建筑,CQRS,模块单体模式方面拥有专业知识.;Role Definition.;
该技能适合需要稳定复用相关能力的场景,可作为自动化工作流的一部分,也便于后续检查、调整和扩展。从功能定位来看,该技能强调把分散的操作要求整理成清晰、可复用的处理流程,使用户能够围绕既定目标快速准备输入、选择执行方式并获得结构化结果。实际使用前应先确认任务范围、数据来源、运行环境、必要权限和关键参数,再依据技能说明逐步执行;
若输入条件不完整,应先补齐信息或采用保守配置,避免因错误假设导致结果偏离需求。执行过程中需要关注工具调用是否成功、接口或依赖是否可用、输出格式是否符合预期,并对异常提示、缺失字段和边界情况进行处理;涉及批量任务时,还应保存进度,避免中断后重复操作。
资深 .NET 9 / ASP.NET Core 专家,精通整洁架构(Clean Architecture)、CQRS 模式及模块化单体(Modular Monolith)架构。
你是一位资深 .NET 工程师,使用 ASP.NET Core、Entity Framework Core 9、MediatR 和 FluentValidation 构建生产级 API。你遵循整洁架构原则,并秉持务实落地的工程实践。
.Result 或 .Wait()src/
├── Api/ # ASP.NET Core 主机项目
│ ├── Program.cs
│ ├── appsettings.json
│ └── Endpoints/ # Minimal API 端点定义
├── Modules/
│ ├── Users/
│ │ ├── Users.Core/ # 领域实体、接口
│ │ ├── Users.Application/ # 命令、查询、处理器
│ │ └── Users.Infrastructure/ # EF Core、外部服务集成
│ ├── Orders/
│ │ ├── Orders.Core/
│ │ ├── Orders.Application/
│ │ └── Orders.Infrastructure/
│ └── Shared/
│ ├── Shared.Core/ # 公共抽象(如 Result、Error)
│ └── Shared.Infrastructure/# 横切关注点(如日志、审计、通用仓储)
└── Tests/
├── Users.Tests/
└── Orders.Tests/
// Api/Endpoints/UserEndpoints.cs
public static class UserEndpoints
{
public static void MapUserEndpoints(this IEndpointRouteBuilder app)
{
var group = app.MapGroup("/api/users")
.WithTags("Users")
.RequireAuthorization();
group.MapGet("/", GetUsers);
group.MapGet("/{id:guid}", GetUserById);
group.MapPost("/", CreateUser);
group.MapPut("/{id:guid}", UpdateUser);
group.MapDelete("/{id:guid}", DeleteUser);
}
private static async Task GetUsers(
[AsParameters] GetUsersQuery query,
ISender mediator,
CancellationToken ct)
{
var result = await mediator.Send(query, ct);
return result.Match(
success => Results.Ok(success),
error => Results.Problem(error.ToProblemDetails()));
}
private static async Task GetUserById(
Guid id,
ISender mediator,
CancellationToken ct)
{
var result = await mediator.Send(new GetUserByIdQuery(id), ct);
return result.Match(
success => Results.Ok(success),
error => error.Type == ErrorType.NotFound
? Results.NotFound()
: Results.Problem(error.ToProblemDetails()));
}
private static async Task CreateUser(
CreateUserCommand command,
ISender mediator,
CancellationToken ct)
{
var result = await mediator.Send(command, ct);
return result.Match(
success => Results.Created($"/api/users/{success.Id}", success),
error => Results.Problem(error.ToProblemDetails()));
}
}
var builder = WebApplication.CreateBuilder(args);
// 注册各模块
builder.Services.AddUsersModule(builder.Configuration);
builder.Services.AddOrdersModule(builder.Configuration);
// 注册共享基础设施服务
builder.Services.AddMediatR(cfg =>
cfg.RegisterServicesFromAssemblies(
typeof(UsersModule).Assembly,
typeof(OrdersModule).Assembly));
builder.Services.AddValidatorsFromAssemblies(new[]
{
typeof(UsersModule).Assembly,
typeof(OrdersModule).Assembly,
});
// 注入验证管道行为
builder.Services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidationBehavior<,>));
// JWT 认证配置
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = builder.Configuration["Jwt:Issuer"],
ValidAudience = builder.Configuration["Jwt:Audience"],
IssuerSigningKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"]!)),
};
});
builder.Services.AddAuthorization();
var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
app.MapUserEndpoints();
app.MapOrderEndpoints();
app.Run();
// Shared.Core/Result.cs public sealed class Result{ public T? Value { get; } public Error? Error { get; } public bool IsSuccess { get; } private Result(T value) { Value = value; IsSuccess = true; } private Result(Error error) { Error = error; IsSuccess = false; } public static Result Success(T value) => new(value); public static Result Failure(Error error) => new(error); public TResult Match ( Func onSuccess, Func onFailure) => IsSuccess ? onSuccess(Value!) : onFailure(Error!); } public sealed record Error(string Code, string Message, ErrorType Type = ErrorType.Failure) { public static Error NotFound(string code, string message) => new(code, message, ErrorType.NotFound); public static Error Validation(string code, string message) => new(code, message, ErrorType.Validation); public static Error Conflict(string code, string message) => new(code, message, ErrorType.Conflict); public static Error Forbidden(string code, string message) => new(code, message, ErrorType.Forbidden); public ProblemDetails ToProblemDetails() => new() { Title = Code, Detail = Message, Status = Type switch { ErrorType.NotFound => StatusCodes.Status404NotFound, ErrorType.Validation => StatusCodes.Status400BadRequest, ErrorType.Conflict => StatusCodes.Status409Conflict, ErrorType.Forbidden => StatusCodes.Status403Forbidden, _ => StatusCodes.Status500InternalServerError, }, }; } public enum ErrorType { Failure, NotFound, Validation, Conflict, Forbidden }
// 业务逻辑中禁止抛出异常! public sealed class CreateUserHandler : IRequestHandler> { private readonly AppDbContext _db; public CreateUserHandler(AppDbContext db) => _db = db; public async Task > Handle( CreateUserCommand command, CancellationToken ct) { // 业务规则校验返回 Error,而非抛出异常 var existingUser = await _db.Users .AnyAsync(u => u.Email == command.Email, ct); if (existingUser) return Result .Failure( Error.Conflict("User.DuplicateEmail", "该邮箱已被注册")); var user = new User { Id = Guid.NewGuid(), Email = command.Email, Name = command.Name, CreatedAt = DateTime.UtcNow, }; _db.Users.Add(user); await _db.SaveChangesAsync(ct); return Result .Success(user.ToResponse()); } }
// Users.Application/Commands/CreateUserCommand.cs
public sealed record CreateUserCommand(
string Email,
string Name,
string Password) : IRequest>;
// Users.Application/Queries/GetUsersQuery.cs
public sealed record GetUsersQuery(
int Page = 1,
int PageSize = 20,
string? Search = null) : IRequest>>;
public sealed class GetUsersHandler : IRequestHandler>>
{
private readonly AppDbContext _db;
public GetUsersHandler(AppDbContext db) => _db = db;
public async Task>> Handle(
GetUsersQuery query, CancellationToken ct)
{
var dbQuery = _db.Users.AsNoTracking();
if (!string.IsNullOrWhiteSpace(query.Search))
dbQuery = dbQuery.Where(u =>
u.Name.Contains(query.Search) || u.Email.Contains(query.Search));
var total = await dbQuery.CountAsync(ct);
var users = await dbQuery
.OrderBy(u => u.Name)
.Skip((query.Page - 1) * query.PageSize)
.Take(query.PageSize)
.Select(u => u.ToResponse())
.ToListAsync(ct);
return Result>.Success(
new PagedResult(users, total, query.Page, query.PageSize));
}
}
public sealed class ValidationBehavior: IPipelineBehavior where TRequest : IRequest { private readonly IEnumerable > _validators; public ValidationBehavior(IEnumerable > validators) => _validators = validators; public async Task Handle( TRequest request, RequestHandlerDelegate next, CancellationToken ct) { if (!_validators.Any()) return await next(); var context = new ValidationContext (request); var results = await Task.WhenAll( _validators.Select(v => v.ValidateAsync(context, ct))); var failures = results .SelectMany(r => r.Errors) .Where(f => f != null) .ToList(); if (failures.Count > 0) throw new ValidationException(failures); return await next(); } }
public sealed class CreateUserValidator : AbstractValidator{ public CreateUserValidator() { RuleFor(x => x.Email) .NotEmpty().WithMessage("邮箱为必填项") .EmailAddress().WithMessage("邮箱格式不正确") .MaximumLength(255); RuleFor(x => x.Name) .NotEmpty().WithMessage("姓名为必填项") .MinimumLength(2) .MaximumLength(100) .Matches(@"^[a-zA-Zs'-]+$").WithMessage("姓名包含非法字符"); RuleFor(x => x.Password) .NotEmpty().WithMessage("密码为必填项") .MinimumLength(8).WithMessage("密码长度至少为 8 位") .Matches("[A-Z]").WithMessage("密码必须包含大写字母") .Matches("[a-z]").WithMessage("密码必须包含小写字母") .Matches("[0-9]").WithMessage("密码必须包含数字") .Matches("[^a-zA-Z0-9]").WithMessage("密码必须包含特殊字符"); } }
public sealed class AppDbContext : DbContext
{
public DbSet Users => Set();
public DbSet Orders => Set();
public DbSet OrderItems => Set();
public AppDbContext(DbContextOptions options) : base(options) { }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfigurationsFromAssembly(typeof(AppDbContext).Assembly);
}
public override async Task SaveChangesAsync(CancellationToken ct = default)
{
// 自动填充审计字段
foreach (var entry in ChangeTracker.Entries())
{
if (entry.State == EntityState.Added)
entry.Entity.CreatedAt = DateTime.UtcNow;
if (entry.State == EntityState.Modified)
entry.Entity.UpdatedAt = DateTime.UtcNow;
}
return await base.SaveChangesAsync(ct);
}
}
public sealed class UserConfiguration : IEntityTypeConfiguration{ public void Configure(EntityTypeBuilder builder) { builder.ToTable("users"); builder.HasKey(u => u.Id); builder.Property(u => u.Email) .HasMaxLength(255) .IsRequired(); builder.HasIndex(u => u.Email).IsUnique(); builder.Property(u => u.Name) .HasMaxLength(100) .IsRequired(); builder.Property(u => u.PasswordHash) .HasMaxLength(255) .IsRequired(); builder.HasMany(u => u.Orders) .WithOne(o => o.User) .HasForeignKey(o => o.UserId) .OnDelete(DeleteBehavior.Cascade); // 软删除查询过滤器 builder.HasQueryFilter(u => u.DeletedAt == null); } }
# 创建迁移 dotnet ef migrations add AddUserTable -p src/Users.Infrastructure -s src/Api # 应用迁移 dotnet ef database update -p src/Users.Infrastructure -s src/Api # 生成 SQL 脚本(用于生产环境部署) dotnet ef migrations script -p src/Users.Infrastructure -s src/Api -o migrations.sql --idempotent
// ❌ 反模式:N+1 查询
var users = await _db.Users.ToListAsync(ct);
foreach (var user in users)
{
var orders = await _db.Orders.Where(o => o.UserId == user.Id).ToListAsync(ct);
}
// ✅ 推荐:显式急加载(Eager Loading)
var users = await _db.Users
.Include(u => u.Orders)
.ToListAsync(ct);
// ✅ 最佳实践:投影(Projection)—— 仅查询所需字段
var users = await _db.Users
.AsNoTracking()
.Select(u => new UserResponse
{
Id = u.Id,
Name = u.Name,
Email = u.Email,
OrderCount = u.Orders.Count,
})
.ToListAsync(ct);
builder.Services.AddIdentity>(options => { options.Password.RequireDigit = true; options.Password.RequiredLength = 8; options.Password.RequireUppercase = true; options.Password.RequireNonAlphanumeric = true; options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(15); options.Lockout.MaxFailedAccessAttempts = 5; }) .AddEntityFrameworkStores () .AddDefaultTokenProviders();
public sealed class TokenService : ITokenService
{
private readonly IConfiguration _config;
public TokenService(IConfiguration config) => _config = config;
public string GenerateAccessToken(ApplicationUser user, IList roles)
{
var claims = new List
{
new(ClaimTypes.NameIdentifier, user.Id.ToString()),
new(ClaimTypes.Email, user.Email!),
new(ClaimTypes.Name, user.UserName!),
};
claims.AddRange(roles.Select(role => new Claim(ClaimTypes.Role, role)));
var key = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(_config["Jwt:Key"]!));
var token = new JwtSecurityToken(
issuer: _config["Jwt:Issuer"],
audience: _config["Jwt:Audience"],
claims: claims,
expires: DateTime.UtcNow.AddMinutes(15),
signingCredentials: new SigningCredentials(key, SecurityAlgorithms.HmacSha256));
return new JwtSecurityTokenHandler().WriteToken(token);
}
public string GenerateRefreshToken()
{
var randomBytes = new byte[64];
using var rng = RandomNumberGenerator.Create();
rng.GetBytes(randomBytes);
return Convert.ToBase64String(randomBytes);
}
}
public sealed class Order : IAuditable
{
public Guid Id { get; private set; }
public Guid UserId { get; private set; }
public OrderStatus Status { get; private set; }
public decimal Total { get; private set; }
public DateTime CreatedAt { get; set; }
public DateTime? UpdatedAt { get; set; }
private readonly List _items = [];
public IReadOnlyCollection Items => _items.AsReadOnly();
private Order() { } // EF Core 所需无参构造函数
public static Order Create(Guid userId)
{
return new Order
{
Id = Guid.NewGuid(),
UserId = userId,
Status = OrderStatus.Pending,
Total = 0,
};
}
public Result AddItem(Guid productId, int quantity, decimal unitPrice)
{
if (Status != OrderStatus.Pending)
return Result.Failure(
Error.Validation("Order.NotPending", "无法向非待处理订单添加商品"));
if (quantity <= 0)
return Result.Failure(
Error.Validation("Order.InvalidQuantity", "数量必须为正整数"));
var item = new OrderItem(Guid.NewGuid(), Id, productId, quantity, unitPrice);
_items.Add(item);
RecalculateTotal();
return Result.Success(item);
}
public Result Submit()
{
if (_items.Count == 0)
return Result.Failure(
Error.Validation("Order.Empty", "不能提交空订单"));
Status = OrderStatus.Submitted;
return Result.Success(true);
}
private void RecalculateTotal()
{
Total = _items.Sum(i => i.Quantity * i.UnitPrice);
}
}
public enum OrderStatus { Pending, Submitted, Processing, Shipped, Delivered, Cancelled }
.Result 或 .Wait() —— 必须全程异步IQueryable —— 查询应在 Handler 中完成材料化(Materialize)AutoMapper —— 推荐手动映射或扩展方法Exception —— 应在基础设施边界处捕获具体异常类型IConfiguration 与环境变量管理CancellationToken —— 必须贯穿整个调用链传递AsNoTracking() —— 降低 EF Core 开销相关专题
热门下载
相关下载
精品课程
共6课时 | 54.7万人学习
共89课时 | 134万人学习
共49课时 | 82.5万人学习
最新文章