You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在ASP.NET Core的PUT/POST/DELETE接口中传递变更作者信息?

问题背景

我定义了如下实体类:

Category 实体类

public class Category
{
    [Required]
    public Guid Id { get; set; }
    [Required]
    public string CategoryName { get; set; }
    public string CategoryDescription { get; set; }
    public DateTime CreatedDate { get; set; }
    public bool isDeleted { get; set; } = false;
    public virtual ICollection<UpdateInfo> Updates { get; set; } = new List<UpdateInfo>();
}

UpdateInfo 实体类(含枚举)

public enum Status
{
    Created,
    Changed,
    Deleted
}
public class UpdateInfo
{
    public Guid Id { get; set; }
    public string Author { get; set; }
    public DateTime Date { get; set; }
    public Status Status { get; set; }
    public virtual Category Category { get; set; }
}

我需要一种规范的实现方式,在PUT/POST/DELETE接口方法中,将作者信息与请求体中的Id或CategoryDTO一并传递。目前我已经完成了POST方法的实现,希望得到专业意见以及标准实现方案。

当前POST方法实现代码:

[HttpPost]
[ProducesResponseType(StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status409Conflict)]
public async Task<ActionResult<Category>> PostCategory(CategoryDTO categoryDTO, string author)
{
    _logger.LogInformation("Creating a new category DB object from a DTO objcect passed in PostOperation method.");
    var category = _mapper.Map<CategoryDTO, Category>(categoryDTO);

    if (CategoryRepeat(category.CategoryName))
    {
        return Conflict("Such category already exists.");
    }

    _logger.LogInformation("Adding an initial update status.");
    var initialUpdate = new UpdateInfo { Author = author, Status = Status.Created, Date = DateTime.UtcNow, Id = Guid.NewGuid(), Category = category };
    category.Updates.Add(initialUpdate);

    try
    {
        _logger.LogInformation($"Trying to save created category {category.Id} into the Database.");
        _context.Categories.Add(category);
        await _context.SaveChangesAsync();
        _logger.LogInformation($"Created category id: {category.Id} saved into the Database successfully.");
    }
    catch (DbUpdateConcurrencyException ex)
    {
        _logger.LogError(ex.Message);
        return BadRequest(new { message = ex.Message });
    }
    return CreatedAtAction("GetCategory", new { id = category.Id }, category);
}

专业意见与标准实现方案

1. 作者信息传递的规范方式

当前实现的问题

你当前把author作为接口独立参数传递,存在几个明显缺陷:

  • 不符合RESTful设计规范:接口参数应聚焦于资源本身,作者信息属于操作上下文,不属于资源属性范畴
  • 安全性不足:前端可随意篡改作者字段,无法保证信息真实性
  • 代码冗余:每个接口都需重复定义该参数,不利于维护

推荐方案:从请求上下文获取(优先选择)

如果是认证用户操作,作者信息应直接从当前登录用户的身份凭证中提取,比如JWT令牌、Cookie中的用户标识:

// 从当前用户身份中获取作者信息
var author = User.Identity.Name; 
// 若使用自定义Claims,可通过以下方式获取
// var author = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;

这种方式的优势:

  • 安全性高:用户无法自行篡改身份信息,确保作者信息真实可靠
  • 符合RESTful原则:接口仅关注资源操作,上下文信息从请求上下文自动获取
  • 代码简洁:无需在每个接口中重复定义作者参数

备选方案:封装到统一请求对象(适用于代操作场景)

如果业务允许代操作(比如管理员替用户执行操作),可将作者信息与资源DTO封装到统一的请求对象中,避免参数分散:

// 定义通用带作者信息的请求DTO
public class AuthorizedRequest<T>
{
    [Required(ErrorMessage = "作者信息不能为空")]
    public string Author { get; set; }
    public T Data { get; set; }
}

// 修改POST接口参数
public async Task<ActionResult<Category>> PostCategory(AuthorizedRequest<CategoryDTO> request)
{
    var author = request.Author;
    var categoryDTO = request.Data;
    // 后续业务逻辑保持不变
}

这种方式的优势:

  • 参数结构清晰,符合单一职责原则
  • 便于统一校验作者信息(通过数据注解自动校验)
  • 扩展性强:后续可添加操作IP、操作时间等更多上下文字段

2. 通用操作日志封装(减少重复代码)

由于PUT/POST/DELETE都需要生成UpdateInfo记录,建议将该逻辑封装为通用方法,避免每个接口重复编写:

// 在控制器基类或服务层封装通用方法
private void AddUpdateInfo(Category category, string author, Status status)
{
    var updateInfo = new UpdateInfo
    {
        Id = Guid.NewGuid(),
        Author = author,
        Status = status,
        Date = DateTime.UtcNow,
        Category = category
    };
    category.Updates.Add(updateInfo);
}

// POST接口中调用
AddUpdateInfo(category, author, Status.Created);

// PUT接口示例
[HttpPut("{id}")]
public async Task<IActionResult> PutCategory(Guid id, AuthorizedRequest<CategoryDTO> request)
{
    var category = await _context.Categories.FindAsync(id);
    if (category == null)
    {
        return NotFound();
    }

    _mapper.Map(request.Data, category);
    AddUpdateInfo(category, request.Author, Status.Changed);

    try
    {
        await _context.SaveChangesAsync();
    }
    catch (DbUpdateConcurrencyException)
    {
        if (!CategoryExists(id))
        {
            return NotFound();
        }
        throw;
    }

    return NoContent();
}

// DELETE接口示例
[HttpDelete("{id}")]
public async Task<IActionResult> DeleteCategory(Guid id)
{
    var category = await _context.Categories.FindAsync(id);
    if (category == null)
    {
        return NotFound();
    }

    category.isDeleted = true;
    var author = User.Identity.Name;
    AddUpdateInfo(category, author, Status.Deleted);
    await _context.SaveChangesAsync();

    return NoContent();
}

3. 其他优化建议

  • 数据校验:对作者信息添加必填校验,避免空值写入数据库
  • 日志增强:在操作日志中加入作者信息,便于后续追踪操作人
  • 并发处理:优化DbUpdateConcurrencyException的处理逻辑,返回更友好的错误提示,或使用乐观锁机制
  • DTO设计:确保CategoryDTO不包含Updates、isDeleted等无需前端传递的字段,避免不必要的参数暴露

内容的提问来源于stack exchange,提问作者Palamar66

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.05 08:31:07