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