SuperAPI AI Directory

aspnet

General & other

View repository

Updated 10-16 09:35

About this skill

ASP.NET Core Backend Development Standards

Trigger Conditions

  • Develop ASP.NET Core projects
  • Implement REST APIs
  • Use Entity Framework Core for data access
  • Implement JWT authentication
  • Implement permission control

Part 1: Technology Stack

Layer Selection Description
Runtime Environment .NET 8+ Cross-platform Runtime
Web Framework ASP.NET Core Built-in Dependency Injection, Middleware Pipeline
ORM Entity Framework Core LINQ Queries, Code First, Migrations
Database MySQL 8.x InnoDB Engine
Cache Redis 7.x Distributed Cache
Authentication JWT (Microsoft.AspNetCore.Authentication.JwtBearer) Token Authentication
API Documentation Swashbuckle OpenAPI Generation

Part 2: Directory Structure

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/         # 集成测试

Design Principles:

  • Youlai.Domain is the thinnest layer and does not depend on any layer
  • Youlai.Application depends on Domain, does not depend on Api and Infrastructure
  • Youlai.Infrastructure implements interfaces, depends on Application
  • Youlai.Api serves as the entry point, composing all layers

Part 3: Naming Conventions

3.1 File Naming

Type Convention Example
Controller {Entity}Controller.cs UserController.cs
Service Interface I{Entity}Service.cs IUserService.cs
Service Implementation {Entity}Service.cs UserService.cs
Entity PascalCase SysUser.cs
DTO Feature + Type Suffix UserForm.cs, UserVO.cs
Enums PascalCase + No Suffix ActionType.cs

3.2 Class Naming

Type Convention Example
Controller {Entity}Controller UserController
Service Interface I{Entity}Service IUserService
Service Implementation {Entity}Service UserService
Entity PascalCase SysUser
DTO Feature + Type UserVO, UserForm, UserPageQuery

DTO Suffix Conventions:

Suffix Purpose Example
VO View Object (returned to frontend) UserVO, RolePageVO
Form / RequestDto Form/Request Object UserForm, LoginRequestDto
Query Query Parameters UserQuery, RolePageQuery
Dto Transfer Object CurrentUserDto, ExcelResultDto

3.3 Method Naming

Action Prefix Example
Query Single Get GetByIdAsync()
Query List List ListUsersAsync()
Paged Query Page PageUsersAsync()
Create Create CreateUserAsync()
Update Update UpdateUserAsync()
Delete Delete DeleteUserAsync()
Dropdown Options GetXxxOptions GetUserOptionsAsync()

3.4 Variable Naming

Type Convention Example
Local Variable camelCase userList
Constant PascalCase MaxSize
Private Field _ prefix camelCase _userService
Boolean Is/Has/Can Prefix IsDeleted, HasPermission

⚠️ ASP.NET Core convention uses the _ prefix for private fields (unlike Spring Boot), consistent with official documentation and EF Core.


Part 4: RESTful API Standards

4.1 Standard CRUD Paths

Operation Method Path Description
Paged List GET /api/v1/users/page Query Parameters
Details GET /api/v1/users/{id} Path Parameters
Form Data GET /api/v1/users/{id}/form Edit Echo
Create POST /api/v1/users Body: UserForm
Update PUT /api/v1/users Body: UserForm
Delete DELETE /api/v1/users/{id} Path Parameters
Batch Delete DELETE /api/v1/users/batch Body: List<long>
Dropdown Options GET /api/v1/users/options Return List<OptionType>

4.2 Controller Template

[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: Response Format and Exception Handling

5.1 Unified Response

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 Business Exceptions

public class BusinessException : Exception
{
    public BusinessException(string message) : base(message) { }
}

5.3 Global Exception Handling

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: Entity Specifications

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 Specifications

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: Authentication Specifications

8.1 JWT Configuration

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 Permission Validation

// Controller 中使用 Policy 进行权限校验
[Authorize(Policy = "sys:user:add")]

// 权限标识命名:模块:实体:操作
// sys:user:create / sys:user:edit / sys:user:delete / sys:role:list / sys:menu:assign

8.3 Current User

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: Comment Specifications

  • Public methods use /// <summary> XML comments(recognizable by IDE IntelliSense)
  • Swagger uses [SwaggerOperation(Summary = "...")]
  • When modifying code, comments must be updated accordingly
/// <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: Code Quality Checklist

  • Follow RESTful API path specifications
  • Use a unified response format
  • Implement global exception handling middleware
  • Permission validation uses Authorize(Policy = "...")
  • Pagination interfaces use PageResult
  • Entities inherit BaseEntity
  • Use EF Core soft delete
  • APIs add [SwaggerOperation] annotations
  • Layering: Controller → Service → Repository
  • Asynchronous methods end with Async suffix
  • Public methods have /// <summary> XML comments

Common Anti-Patterns

Anti-Pattern Correct Practice
throw new Exception("用户不存在") throw new BusinessException("用户不存在")
catch { } Empty catch Catch and log or throw a business exception
public async Task<T> Xxx() No Async suffix Add Async suffix:XxxAsync()
Writing business logic in Controller Delegate to the Service layer
Synchronous methods call SaveChangesAsync().Result Use await SaveChangesAsync()