ASP.NET 8 MVC中Controller-Service-Repository模式异常处理咨询
我正在开发一个采用Controller-Service-Repository模式的ASP.NET 8 MVC项目,当前Service层业务逻辑较少,主要负责调用Repository层。Repository层调用SOAP API,无法依赖HTTP错误判断状态,只能通过API返回的Code值识别(0为正常状态)。由于多数API方法包含复杂业务逻辑,无法在MVC的Service层复现(甚至因无法获取API全部数据而完全不可能实现),因此主要依赖API返回的业务逻辑错误。当判断API存在错误时,我会抛出RepositoryException,但不确定将错误传递给客户端的最佳方式:是否需要在Service层通过try-catch捕获RepositoryException并抛出自定义的ServiceException?是否需要在Controller层同样通过try-catch处理并传递到View?希望获取优雅的处理建议。
相关代码实现
SoapClient 注册代码
public static IServiceCollection AddSoapClient(this IServiceCollection services) { services.AddSingleton<ISoapService>(options => { string uri = "Some API URL"; System.Net.ServicePointManager.SecurityProtocol = System.Net.SecurityProtocolType.Tls12 | System.Net.SecurityProtocolType.Tls13; BasicHttpBinding binding = new BasicHttpBinding(); binding.MaxReceivedMessageSize = 2147483647; binding.TransferMode = TransferMode.Streamed; binding.ReaderQuotas.MaxStringContentLength = 2147483647; binding.ReaderQuotas.MaxArrayLength = 67108864; // 64 megabytes binding.SendTimeout = new TimeSpan(0, 15, 0); binding.ReceiveTimeout = new TimeSpan(0, 15, 0); if (uri.ToLower().StartsWith("https")) { binding.Security.Mode = BasicHttpSecurityMode.Transport; } return new SoapServiceClient(binding, new EndpointAddress(uri)); }); return services; }
Repository 层代码
public class ContactPersonRepository : SoapRepository, IContactPersonRepository { public ContactPersonRepository(ISoapService client, ISoapAuthentication authentication) : base(client, authentication) { } public async Task<ContactPerson> UpdateAsync(ContactPerson contactPerson) { var response = await client.UpdateContactPersonAsync(DeviceId, AuthToken, contactPerson); if (response.HasErrors()) { throw new SoapRepositoryException(response); } return response.ContactPerson; } }
错误判断扩展方法 HasErrors()
public static class ServiceResponseExtensions { public static bool HasErrors(this ServiceResponse response) { int[] errorCodeWhitelist = [0, 1, 2, 10]; return !errorCodeWhitelist.Contains(response.Error); } }
Service 层代码
public class ContactPersonService : IContactPersonService { private IContactPersonRepository contactPersonRepository; public ContactPersonService(IContactPersonRepository contactPersonRepository) { this.contactPersonRepository = contactPersonRepository; } public async Task<ContactPerson> UpdateAsync(ContactPerson contactPerson) { try { return await contactPersonRepository.UpdateAsync(contactPerson); } catch(RepositoryException ex) { throw new ServiceException(ex); } } }
Controller 层代码
public class ContactPersonController : BaseController { private IContactPersonService contactPersonService; public ContactPersonController(IContactPersonService contactPersonService) { this.contactPersonService = contactPersonService; } [HttpPost] [ValidateAntiForgeryToken] public async Task<IActionResult> Edit(ContactPersonEditModel model) { if (ModelState.IsValid) { ContactPerson toUpdate = model.MapTo<ContactPerson>(); try { await contactPersonService.UpdateAsync(toUpdate); return RedirectToAction("Index"); } catch(ServiceException ex) { AddMessage(ex.Message); } } return View(model); } }
优雅处理建议
1. 关于Repository到Service的异常传递
不需要在Service层刻意捕获RepositoryException并包装为ServiceException:
- 当前Service层仅做转发调用,额外包装异常只会增加冗余代码,无实际业务价值。
- 若后续Service层新增业务逻辑,需要对Repository异常做业务转换或补充信息时,再考虑包装为
ServiceException才合理。 - 保持异常传递简洁性,让Controller直接处理底层抛出的
SoapRepositoryException即可。
2. Controller层异常处理优化
避免在每个Action中重复编写try-catch,推荐两种统一处理方式:
方式一:全局异常过滤器
实现IExceptionFilter统一处理所有异常:
public class GlobalExceptionFilter : IExceptionFilter { private readonly ILogger<GlobalExceptionFilter> _logger; public GlobalExceptionFilter(ILogger<GlobalExceptionFilter> logger) { _logger = logger; } public void OnException(ExceptionContext context) { if (context.Exception is SoapRepositoryException repoEx) { // 提取API返回的友好错误信息 ((BaseController)context.Controller).AddMessage(repoEx.Message); context.ExceptionHandled = true; // 根据请求类型返回对应结果 if (context.HttpContext.Request.IsAjaxRequest()) { context.Result = new JsonResult(new { Success = false, Message = repoEx.Message }); } else { // 返回当前Action对应的View,保留用户输入数据 context.Result = context.Controller.View(context.RouteData.Values["action"].ToString(), context.Controller.ViewData.Model); } } else { // 系统异常记录日志,跳转到统一错误页 _logger.LogError(context.Exception, "系统异常"); context.ExceptionHandled = true; context.Result = new RedirectResult("/Error"); } } }
在Program.cs中注册过滤器:
builder.Services.AddControllersWithViews(options => { options.Filters.Add<GlobalExceptionFilter>(); });
方式二:基类Controller统一处理
在BaseController中重写OnException方法:
public class BaseController : Controller { private readonly ILogger<BaseController> _logger; public BaseController(ILogger<BaseController> logger) { _logger = logger; } protected override void OnException(ExceptionContext filterContext) { if (filterContext.Exception is SoapRepositoryException repoEx) { AddMessage(repoEx.Message); filterContext.ExceptionHandled = true; filterContext.Result = View(filterContext.RouteData.Values["action"].ToString(), filterContext.Controller.ViewData.Model); } else { _logger.LogError(filterContext.Exception, "系统异常"); filterContext.ExceptionHandled = true; filterContext.Result = RedirectToAction("Index", "Error"); } base.OnException(filterContext); } }
所有继承BaseController的Controller会自动处理异常,无需重复编写try-catch。
3. 异常信息封装优化
扩展SoapRepositoryException,直接携带API错误码和描述,方便上层直接使用:
public class SoapRepositoryException : Exception { public int ErrorCode { get; } public string ErrorDetail { get; } public SoapRepositoryException(ServiceResponse response) : base(response.ErrorMessage) { ErrorCode = response.Error; ErrorDetail = response.ErrorMessage; } }
4. 业务异常与系统异常区分处理
- 业务异常(如参数错误、权限不足):提取友好提示展示给用户,保留当前页面和用户输入。
- 系统异常(如网络超时、SOAP客户端报错):记录详细日志,跳转到统一错误页面,避免向用户暴露技术细节。
内容的提问来源于stack exchange,提问作者Valition

