如何使用 Web 服务正确抛出和处理异常

Posted

技术标签:

【中文标题】如何使用 Web 服务正确抛出和处理异常【英文标题】:How to properly throw and handle exceptions with web services 【发布时间】:2015-08-11 13:03:00 【问题描述】:

我在我的应用程序服务器中遵循 MVC 模式。我试图抛出一个异常,但它没有被正确抛出。

这是控制器的代码:

[HttpPost]
public IHttpActionResult PostAddSuperUser(SuperUserViewModel SU)

    try
    
        //more code
        blHandler.addSuperUser((SuperUser)SU.user, SU.Password);
        return Ok();
    
    catch (EUserAlreadyExist ex)
    
        var resp = new HttpResponseMessage(HttpStatusCode.NotAcceptable);
        resp.Content = new StringContent(string.Format("Already exist ", SU.user.Mail));
        resp.ReasonPhrase = "Already exist";
        throw new HttpResponseException(resp);
    

然后客户端调用如下:

try
            HttpClient client = new HttpClient();
            client.BaseAddress = new Uri("http://localhost:50687/");

            HttpResponseMessage response = await client.PostAsJsonAsync<SuperUserViewModel>("/api/SuperUser/PostAddSuperUser", SUVM);

            if (response.IsSuccessStatusCode)
            
                new SuccesPupUp("", "SuperUser " + SupUserName + " was added");
            
            this.Close();
        

catch (HttpResponseException Exc)

    new ErrorPopUp("", Exc.Message);

根据这个this,我正确地抛出了它,但是当我运行它时,我得到了这个错误

我该如何解决?

编辑:我想向客户端抛出异常,以便它可以要求用户提供新的电子邮件地址

【问题讨论】:

如您所见,它会抛出您没有捕获的 HttpResponseException,只需添加第一个捕获 - catch(HttpResponseException ex) 由于您询问的是“正确处理异常”,请注意使用 Exception 后缀而不是前缀“E”来命名异常是“最佳实践”。例如,EInvalidPassword 应该命名为 InvalidPasswordException,是的,它更长,但更明显的是它是什么。 catch() 中你必须处理HttpResponseException,而不是EUserAlredyExist,因为你不会抛出EUserAlredyExist 异常。 这个问题与***.com/questions/19287426/…有关。如果您从一个 catch 块中抛出一个异常,并且在该块之外没有 catch 块,则该异常是未处理的。 @DanielPetrovaliev,他的 blHandler.addSuperUser() 方法可能会抛出 EUserAlreadyExist 异常,这就是他捕捉它的原因。 【参考方案1】:

问题出在这段代码中(看起来很明显,但请耐心等待):

  catch (EUserAlreadyExist ex)
    
        var resp = new HttpResponseMessage(HttpStatusCode.NotAcceptable);
        resp.Content = new StringContent(string.Format("Already exist ", SU.user.Mail));
        resp.ReasonPhrase = "Already exist";
        throw new HttpResponseException(resp);
    

发生的情况是,您从上述尝试中捕获了 EUserAlreadyExist 异常,然后抛出了一个新异常,而没有伴随的 try-catch 块。所以这意味着仅仅因为你在 catch 中抛出了一个异常,它不会自动捕捉它,你必须在你的 catch 中有一个单独的 try-catch

更进一步,对此try-catch 的客户端调用也不会捕获抛出的异常,因为它们(catch 语句)捕获的异常类型与抛出的异常类型不同。

要修复它,您需要捕获在这种情况下引发的异常 HttpResponseException 异常。所以这意味着你的代码可能看起来像这样:

  catch (EUserAlreadyExist ex)
    
    try
        var resp = new HttpResponseMessage(HttpStatusCode.NotAcceptable);
        resp.Content = new StringContent(string.Format("Already exist ", SU.user.Mail));
        resp.ReasonPhrase = "Already exist";
        throw new HttpResponseException(resp);
       
       catch(HttpResponseException ex2)
       
         //do something here with the exception
       
    

【讨论】:

这好像不太对劲,为什么要抛出异常然后马上捕获呢? 根据@Rodrigo 的问题和代码,这个答案在技术上应该可行,但我同意以这种方式抛出异常没有多大意义。 @Pseudonym 我想在服务器端使用我的 try catch 所做的就是将异常抛出到客户端,那么捕获我自己的异常将如何帮助我做到这一点?如我所见,如果我添加一个新的 try-catch,我会再次捕获它,但我仍然不知道如何处理它或如何将其发送到客户端【参考方案2】:

服务器端代码不需要捕获异常并重新抛出它 - 有什么意义?让它冒泡给客户。这就是例外的美妙和力量。问题是 MVC 想要返回一个视图或一个 HTTP 状态码,而不是丰富的强类型异常信息。

您似乎在富客户端(客户端-服务器)情况下使用 MVC 模式,这没有任何意义。 MVC 最适合瘦客户端情况,服务器提供视图,客户端只呈现它们。当你有一个富客户端时,MVVM 模式更有意义。服务器可以提供视图模型(数据),客户端可以接管所有的用户交互、视图等。

考虑使用 MVVM 方法(使用 WCF)而不是 MVC。那么客户端和服务端都会对可以抛出的异常类型有一个共同的认识。服务器可以抛出,客户端可以捕捉,很简单。

您的客户端代码将如此简单:

try

    c.AddSuperUser(SUVM);

catch (Exception e)

    // etc

其中 c 是 WCF 客户端代理。

【讨论】:

以上是关于如何使用 Web 服务正确抛出和处理异常的主要内容,如果未能解决你的问题,请参考以下文章

在身份验证 Spring Security + WebFlux 期间抛出和处理自定义异常

抛出和处理异常

php 异常处理 如何捕获异常??必须要抛出才可以吗?

异常的抛出和捕获

当它被抛出和捕获时,不要在那个异常处停止调试器

如何在 Spring 中为 GraphQL 实现异常处理程序