ASP.NET Core微服务跨API调用实现用户注册全字段写入
问题场景
我采用ASP.NET Core Web API微服务架构,共用一个数据库和一张User表,通过EF拆分实体实现业务分离:
- UserControl微服务的
User实体包含Id、UserName、Password、LastLogin字段 - ContactsControl微服务的
User实体包含Id、DisplayName字段
现在要构建Angular注册表单,要求UserControl的Post接口能完整写入全表字段(含DisplayName),计划在UserControl的Add方法中调用ContactsControl的Update接口,但不清楚HttpClient的具体使用方式和位置。现有代码如下:
UserControl仓储类:
public Guid Add(User user) { _context.Users.Add(user); _context.SaveChanges(); return user.Id; // **call Put method from ContactUser By using HttpClient and Response** }
UserControl控制器:
[HttpPost] public void Post([FromBody] UserPostDTO user) { _userRepository.Add(user.ToPostUser()); }
解决方案步骤
1. 配置并注入HttpClient
首先在UserControl微服务中配置HttpClient实例,通过IHttpClientFactory管理,避免手动创建HttpClient带来的资源问题。
- 在
Program.cs中注册HttpClient:
builder.Services.AddHttpClient("ContactsControlClient", client => { // 从配置文件读取ContactsControl微服务的基础地址 client.BaseAddress = new Uri(builder.Configuration["Services:ContactsControl:BaseUrl"]); client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); });
对应的appsettings.json配置:
"Services": { "ContactsControl": { "BaseUrl": "http://localhost:5001/" // 替换为实际的ContactsControl服务地址 } }
- 修改仓储类构造函数,注入
IHttpClientFactory:
private readonly AppDbContext _context; private readonly HttpClient _contactsClient; public UserRepository(AppDbContext context, IHttpClientFactory httpClientFactory) { _context = context; // 创建指定名称的HttpClient实例 _contactsClient = httpClientFactory.CreateClient("ContactsControlClient"); }
2. 修改Add方法调用Update接口
把原有同步方法改为异步(符合ASP.NET Core最佳实践),先保存User到数据库,再调用ContactsControl的Put接口更新DisplayName,同时处理响应和异常。
public async Task<Guid> AddAsync(User user, string displayName) { _context.Users.Add(user); await _context.SaveChangesAsync(); // 构造更新请求的模型(匹配ContactsControl的Update接口参数) var updatePayload = new { Id = user.Id, DisplayName = displayName }; // 调用ContactsControl的Put接口,假设接口路径为api/contactsuser/{id} var response = await _contactsClient.PutAsJsonAsync($"api/contactsuser/{user.Id}", updatePayload); // 校验响应状态,失败则抛出异常 if (!response.IsSuccessStatusCode) { throw new HttpRequestException($"更新DisplayName失败,状态码:{response.StatusCode}"); } return user.Id; }
3. 调整DTO和控制器逻辑
- 给
UserPostDTO添加DisplayName字段,传递注册时的用户昵称:
public class UserPostDTO { public string UserName { get; set; } public string Password { get; set; } public string DisplayName { get; set; } // 新增字段 public User ToPostUser() { return new User { UserName = this.UserName, Password = this.Password, LastLogin = DateTime.UtcNow // 注册时初始化最后登录时间 }; } }
- 修改控制器的Post方法为异步,接收DTO并传递给仓储的异步方法:
[HttpPost] public async Task<IActionResult> Post([FromBody] UserPostDTO userDto) { if (!ModelState.IsValid) { return BadRequest(ModelState); } var user = userDto.ToPostUser(); var userId = await _userRepository.AddAsync(user, userDto.DisplayName); // 返回201 Created状态,附带用户ID return CreatedAtAction(nameof(GetUser), new { id = userId }, userId); }
4. 可选:添加事务保证数据一致性
为了避免“User添加成功但DisplayName更新失败”的脏数据问题,用EF事务包裹两个操作:
public async Task<Guid> AddAsync(User user, string displayName) { using var transaction = await _context.Database.BeginTransactionAsync(); try { _context.Users.Add(user); await _context.SaveChangesAsync(); var updatePayload = new { Id = user.Id, DisplayName = displayName }; var response = await _contactsClient.PutAsJsonAsync($"api/contactsuser/{user.Id}", updatePayload); if (!response.IsSuccessStatusCode) { throw new HttpRequestException($"更新DisplayName失败,状态码:{response.StatusCode}"); } await transaction.CommitAsync(); return user.Id; } catch { // 更新失败则回滚User的添加操作 await transaction.RollbackAsync(); throw; } }
内容的提问来源于stack exchange,提问作者Inbar Manor
相关产品推荐
相关产品推荐

