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

ASP.NET 8 MVC中Controller-Service-Repository模式异常处理咨询

ASP.NET 8 MVC 项目错误传递的优雅处理方案建议

我正在开发一个采用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 08:58:10