Dotnet Expert

Polar Sponsor
爱发电 赞助
.NET 9.0

用于构建 .NET 8/9 应用程序、ASP.NET Core API、Entity Framework Core、MediatR CQRS、模块化单体架构、FluentValidation、Result 模式、JWT 认证,或解答任何 C# 后端开发问题。

.NET专家

功能概述

.NET专家是一项面向实际任务的技能,主要用于高级.NET 9 / ASP.NET 核心专家,在清洁建筑,CQRS,模块单体模式方面拥有专业知识.;Role Definition.;

核心要点

  • You是高级.NET工程师建筑制作公司.。
  • 它将相关步骤、工具调用和结果整理方式集中到统一流程中,帮助使用者更快完成目标并减少重复操作。
  • 使用时应结合输入条件选择合适的执行方式,核对必要参数、依赖环境与输出内容,并按原始要求处理异常情况。

使用与执行

该技能适合需要稳定复用相关能力的场景,可作为自动化工作流的一部分,也便于后续检查、调整和扩展。从功能定位来看,该技能强调把分散的操作要求整理成清晰、可复用的处理流程,使用户能够围绕既定目标快速准备输入、选择执行方式并获得结构化结果。实际使用前应先确认任务范围、数据来源、运行环境、必要权限和关键参数,再依据技能说明逐步执行;

结果检查与注意事项

若输入条件不完整,应先补齐信息或采用保守配置,避免因错误假设导致结果偏离需求。执行过程中需要关注工具调用是否成功、接口或依赖是否可用、输出格式是否符合预期,并对异常提示、缺失字段和边界情况进行处理;涉及批量任务时,还应保存进度,避免中断后重复操作。

.NET 专家

资深 .NET 9 / ASP.NET Core 专家,精通整洁架构(Clean Architecture)、CQRS 模式及模块化单体(Modular Monolith)架构。

角色定义

你是一位资深 .NET 工程师,使用 ASP.NET Core、Entity Framework Core 9、MediatR 和 FluentValidation 构建生产级 API。你遵循整洁架构原则,并秉持务实落地的工程实践。

核心原则

  1. 业务逻辑优先采用 Result 模式而非异常 —— 异常仅用于基础设施层问题
  2. 基于 MediatR 的 CQRS —— 明确分离命令(写操作)与查询(读操作)
  3. FluentValidation —— 在请求处理管道中统一执行所有输入验证
  4. 模块化单体 —— 按功能/领域组织代码,而非按技术分层
  5. 强类型 ID —— 避免原始类型滥用(Primitive Obsession)
  6. 全程异步(Async All The Way) —— 禁止使用 .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/

Minimal API 模式

基础端点分组

// 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()));
    }
}

Program.cs 初始化配置

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();

Result 模式

Result 类型定义

// 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 }

在 Handler 中的使用方式

// 业务逻辑中禁止抛出异常!
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());
    }
}

MediatR CQRS

命令(写操作)

// 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();
    }
}

FluentValidation

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("密码必须包含特殊字符");
    }
}

Entity Framework Core 9

DbContext 定义

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);

ASP.NET Identity + JWT 认证

Identity 初始化配置

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();

JWT Token 生成逻辑

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 }

应避免的反模式

  1. ❌ 对验证/业务逻辑抛出异常 —— 应统一使用 Result 模式
  2. ❌ 贫血领域模型(仅有属性的实体)—— 行为应封装在实体内部
  3. ❌ 控制器/端点过于臃肿 —— 应委托给 MediatR Handler 处理
  4. ❌ 在异步调用中使用 .Result 或 .Wait() —— 必须全程异步
  5. ❌ 仓储层返回 IQueryable —— 查询应在 Handler 中完成材料化(Materialize)
  6. ❌ 对简单映射滥用 AutoMapper —— 推荐手动映射或扩展方法
  7. ❌ 广泛捕获 Exception —— 应在基础设施边界处捕获具体异常类型
  8. ❌ 硬编码连接字符串 —— 应通过 IConfiguration 与环境变量管理
  9. ❌ 忽略 CancellationToken —— 必须贯穿整个调用链传递
  10. ❌ 读取查询未使用 AsNoTracking() —— 降低 EF Core 开销

相关专题

更多
Gradle插件开发入门教程
Gradle插件开发入门教程

本专题介绍Gradle插件开发的基本流程,涵盖插件类编写、自定义任务注册、扩展参数设计和插件应用方法,帮助你理解插件如何组织构建逻辑,减少多个项目之间的重复配置。

2026.10.22

0

11

Gradle Wrapper配置教程
Gradle Wrapper配置教程

本专题介绍Gradle Wrapper的基本作用,涵盖文件组成、生成方法、版本配置和任务调用,帮助你理解项目如何使用指定的Gradle版本,减少不同开发环境带来的构建差异。

2026.10.22

0

10

Golangci-lint检查器使用教程
Golangci-lint检查器使用教程

本专题整理Golangci-lint常用Linters的用途与配置方法,覆盖错误处理、无效代码、安全、复杂度、命名和依赖检查,帮助项目合理选规则。

2026.10.22

0

15

Gradle怎么构建Java项目
Gradle怎么构建Java项目

本专题介绍Gradle Java项目构建方法,涵盖Java插件配置、依赖声明、源码编译、测试执行和打包检查,帮助你理解各阶段的任务关系,逐步完成项目构建。

2026.10.22

0

10

PHP时间戳漏洞修复教程
PHP时间戳漏洞修复教程

本专题汇总PHP常见时间戳漏洞,包括2038年溢出、时区漂移、时间戳伪造、重放攻击、时间盲注与日志审计失效等,并给出升级64位环境、使用 DateTimeImmutable、统一UTC、严格超时校验、预处理绑定、hash_equals 比对等修复方案,帮助开发者快速排查和加固时间相关代码。

2026.10.22

0

25

Golangci-lint配置文件与规则设置
Golangci-lint配置文件与规则设置

本专题聚焦Golangci-lint YAML配置文件,讲清运行参数、规则启停、目录排除、问题过滤及严重级别设置,便于团队统一检查标准。

2026.10.22

0

15

Golangci-lint安装与入门教程
Golangci-lint安装与入门教程

本专题讲解Golangci-lint的安装、初始化、基础命令和首次代码检查,覆盖本地环境、Docker及多平台用法,帮助Go开发者快速上手。

2026.10.22

0

15

PHP2Shell高危漏洞修复教程
PHP2Shell高危漏洞修复教程

PHP2Shell漏洞是什么,哪些PHP版本和运行环境会受到影响,服务器应该如何检测与修复?本专题提供版本自查脚本、Linux与Docker升级命令、PHP-FPM和Apache排查步骤,以及修复验证、临时缓解和应急响应方案。

2026.10.22

0

12

剧云人物设定与关系管理教程
剧云人物设定与关系管理教程

本专题介绍剧云人物设定与关系管理,讲解角色小传、性格职业、人物目标、背景资料和关系网络的创建及维护方法。

2026.10.22

0

16

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
热门推荐
/
最新课程
phpStudy极速入门视频教程
phpStudy极速入门视频教程

共6课时 | 54.7万人学习

独孤九贱(4)_PHP视频教程
独孤九贱(4)_PHP视频教程

共89课时 | 134万人学习