2

我重写了我的问题,因为我认为它太罗嗦了,也许我想要实现的目标丢失了。

我在记事本中编写了这段代码,所以它可能有错误,有些东西可能不是很好,但它是为了说明我看到我的选择是什么。

// I wrap all code send back from service layer to controller in this class.
  public class ResponseResult
    {

        public ResponseResult()
        {
            Errors = new Dictionary<string, string>();
            Status = new ResponseBase();
        }

        public void AddError(string key, string errorMessage)
        {
            if (!Errors.ContainsKey(key))
            {
                Errors.Add(key, errorMessage);
            }
        }

        public bool IsValid()
        {
            if (Errors.Count > 0)
            {
                return false;
            }

            return true;
        }


        public Dictionary<string, string> Errors { get; private set; }


        public ResponseBase Status { get; set; }
    }

    public class ResponseResult<T> : ResponseResult
    {

        public T Response { get; set; }
    }

    public class ResponseBase
    {
        public HttpStatusCode Code { get; set; }
        public string Message { get; set; }
    }

选项1(我现在使用的)

//controller
    public HttpResponseMessage GetVenue(int venueId)
            {
                if (venueId == 0)
                {
                    ModelState.AddModelError("badVenueId", "venue id must be greater than 0");

                if (ModelState.IsValid)
                {
                    var venue = venueService.FindVenue(venueId);
                    return Request.CreateResponse<ResponseResult<Venue>>(venue.Status.Code, venue);
                }

                // a wrapper that I made to extract the model state and try to make all my request have same layout.
                var responseResult = new ResponseResultWrapper();
                responseResult.Status.Code = HttpStatusCode.BadRequest;
                responseResult.Status.Message = GenericErrors.InvalidRequest;
                responseResult.ModelStateToResponseResult(ModelState);

                return Request.CreateResponse<ResponseResult>(responseResult.Status.Code, responseResult);
            }

// service layer        
             public ResponseResult<Venue> FindVenue(int venueId)
            {
                ResponseResult<Venue> responseResult = new ResponseResult<Venue>();

                try
                {
                    // I know this check was done in the controller but pretend this is some more advanced business logic validation.
                    if(venueId == 0)
                    {
                       // this is like Model State Error in MVC and mostly likely would with some sort of field.
                       responseResult.Errors.Add("badVenueId", "venue id must be greater than 0");
                       responseResult.Status.Code = HttpStatusCode.BadRequest;
                    }

                    var venue = context.Venues.Where(x => x.Id == venueId).FirstOrDefault();

                    if(venue == null)
                    {
                        var foundVenue = thirdPartyService.GetVenue(venueId);

                        if(foundVenue == null)
                        {
                           responseResult.Status.Code = HttpStatusCode.NotFound;
                           responseResult.Status.Message = "Oops could not find Venue";

                           return responseResult;
                        }
                        else
                        {
                           var city = cityService.FindCity(foundVenue.CityName);

                           if(city == null)
                           { 
                              city = cityService.CreateCity(foundVenue.CityName);

                              if(city.Response == null)
                              {
                                 responseResult.Status.Code = city.Status.Code;
                                 responseResult.Status.Message = city.Status.Message;

                                 return responseResult;
                              }

                              CreateVenue(VenueId, city.Response, foundVenue.Name);

                               responseResult.Status.Code = HttpStatusCode.Ok;
                               // I don't think I would return a success message here as the venue being displayed back to the user should be good enough.
                               responseResult.Status.Message = "";

                               reponseResult.Response = foundVenue;
                           }
                        }

                        return responseResult;
                    }

                }
                catch (SqlException ex)
                {
                    ErrorSignal.FromCurrentContext().Raise(ex);
                    responseResult.Status.Code = HttpStatusCode.InternalServerError;
                    responseResult.Status.Message = GenericErrors.InternalError;

                    // maybe roll back statement here depending on the method and what it is doing.
                }
               // should I catch this, I know it should be if you handle it but you don't want nasty messages going back to the user.
                catch (InvalidOperationException ex)
                {
                    ErrorSignal.FromCurrentContext().Raise(ex);
                    responseResult.Status.Code = HttpStatusCode.InternalServerError;
                    responseResult.Status.Message = GenericErrors.InternalError;
                }
               // should I catch this, I know it should be if you handle it but you don't want nasty messages going back to the user.
                catch (Exception ex)
                {
                    ErrorSignal.FromCurrentContext().Raise(ex);
                    responseResult.Status.Code = HttpStatusCode.InternalServerError;
                    responseResult.Status.Message = GenericErrors.InternalError;
                }

                return responseResult;
            }

// another service layer. 

        // it is ResponseResult<City> and not city because I could have a controller method that directly calls this method.
            // but I also have a case where my other method in another service needs this as well.
            public ResponseResult<City> CreateCity(string CityName)
            {
               ResponseResult<City> responseResult = new ResponseResult<City>();
               try
               {
                   City newCity = new City {  Name = "N" };
                   context.Cities.Add(newCity);
                   context.SaveChanges();

                    responseResult.Status.Code = HttpStatusCode.Ok;
                    responseResult.Status.Message = "City was succesfully added";
               }           
               // same catch statmens like above
               catch (SqlException ex)
                {
                    ErrorSignal.FromCurrentContext().Raise(ex);
                    responseResult.Status.Code = HttpStatusCode.InternalServerError;
                    responseResult.Status.Message = GenericErrors.InternalError;

                    // maybe roll back statement here depending on the method and what it is doing.
                }
                return responseResult;
            }

如您所见,这些方法都包含在状态代码中,因为它们可以由公共控制器直接调用。FindCity() 和 CreateVenue() 也可以有这种包装。

选项 2

   public HttpResponseMessage GetVenue(int venueId)
        {
            try
            {
                if (venueId == 0)
                {
                    ModelState.AddModelError("badVenueId", "venue id must be greater than 0");

                if (ModelState.IsValid)
                {
                    var venue = venueService.FindVenue(venueId);
                    return Request.CreateResponse<ResponseResult<Venue>>(HttpSatusCode.Ok, venue);
                }

                // a wrapper that I made to extract the model state and try to make all my request have same layout.
                var responseResult = new ResponseResultWrapper();
                responseResult.Status.Code = HttpStatusCode.BadRequest;
                responseResult.Status.Message = GenericErrors.InvalidRequest;
                responseResult.ModelStateToResponseResult(ModelState);

                return Request.CreateResponse<ResponseResult>(responseResult.Status.Code, responseResult);
            }
            catchcatch (SqlException ex)
            {
               // can't remember how write this and too tried to look up.
               return Request.CreateResponse(HttpStatusCode.InternalServerError;, "something here");
            }
        }

 public Venue FindVenue(int venueId)
        {
            try
            {
                // how to pass back business logic error now without my wrapper?
                if(venueId == 0)
                {
                   // what here?
                }

                var venue = context.Venues.Where(x => x.Id == venueId).FirstOrDefault();

                if(venue == null)
                {
                    var foundVenue = thirdPartyService.GetVenue(venueId);

                    if(foundVenue == null)
                    {
                       // what here?
                    }
                    else
                    {
                       var city = cityService.FindCity(foundVenue.CityName);

                       if(city == null)
                       { 
                          city = cityService.CreateCity(foundVenue.CityName);

                          if(city  == null)
                          {
                             // what here?
                          }

                          CreateVenue(VenueId, city.Response, foundVenue.Name);


                       }
                    }

                    return venue;
                }

            }
            catch (SqlException ex)
            {
                // should there be a try catch here now? 
                // I am guessing I am going to need to have this here if I need to do a rollback and can't do it in the controller

                // throw exception here. Maybe this won't exist if no rollback is needed.
            }
            return null;
        }

        public City CreateCity(string CityName)
        {
           // if it crashes something I guess will catch it. Don't think I need to rollback here as only one statement being sent to database.
            City newCity = new City {  Name = "N" };
            context.Cities.Add(newCity);
            context.SaveChanges();

            return newCity;            
        }

正如您在选项 2 中看到的那样,我可能仍需要将其包装在 try catch 中以进行回滚,并且我不确定如何处理高级业务验证。

此外,在控制器中捕获所有内容并发送回香草对象(没有我的包装器)我不确定如何做细粒度的 HttpStatus 代码(比如 notFound、Create 等)

4

6 回答 6

1

很抱歉简短的回复,但这是我的一般规则 - 如果发生您期望可能发生的异常,请处理它 - 通过重试或告诉用户出现问题并为他们提供修复它的选项。

如果发生意外的异常,如果这是您可以处理的事情(例如,您可以重试的超时)尝试处理它,否则退出 - 想想任何 MS 应用程序做了什么 - 例如办公室 - 你会得到道歉,说发生了什么事错误,应用程序结束。最好以优雅的方式结束,而不是潜在地破坏数据并将事情弄得一团糟。

于 2013-10-10T00:37:10.037 回答
0

只要您的代码确定出现问题,就抛出异常。

您总是需要处理由最终用户直接调用的方法中的异常。这是为了迎合您的代码没有特定处理的意外错误。您的通用处理代码通常会记录错误,并且可能包括也可能不包括让用户知道发生了意外错误。

但是,如果有您可以提前预料到的错误,您通常希望在代码中处理这些错误,更接近它们发生的位置,以便您的应用程序可以从错误中“恢复”并继续。

于 2013-10-10T00:28:35.987 回答
0

我认为,当您需要从方法返回失败的详细信息时,异常很有用,同时能够为您正在调用的方法使用理想的返回类型。

你在你的问题中说:

现在对我来说,我尝试将错误消息返回给控制器,并尽量不要真正捕获控制器中的任何内容。

如果服务方法应该理想地返回一个 Venue 对象,你如何将这个潜在的错误消息返回给控制器?输出参数?将返回类型更改为具有错误消息属性的内容?

如果您正在执行这些选项中的任何一个,我认为您正在重新发明轮子......即创建一种在已经存在异常信息时返回异常信息的方法。

最后,异常是错误的强类型表示。如果您返回错误消息,则可以将其发送回用户,但如果您需要根据错误的详细信息以编程方式执行不同的操作,那么您不想打开魔术字符串。

例如,区分授权错误和未找到错误以便您可以将最合适的 http 状态代码返回给用户不是很方便吗?

不要忘记 Exception 类有一个 Message 属性,如果你想以这种方式使用它,你可以简单地返回给用户

于 2013-10-10T00:32:31.733 回答
0

是一篇包含特定于 Java 的概念和示例的文章,但这里的广泛原则是要走的路。

区分故障异常(灾难性和不可恢复)和意外异常(非常可恢复)。让故障“冒泡”到故障屏障,您可以在此处适当处理。例如,您可能会记录错误、给某人发电子邮件或向消息队列发送消息,并为用户提供一个漂亮的、信息丰富的错误页面。

无论您做什么,请务必从源头中保留所有异常信息。

希望有帮助。

于 2013-10-10T00:23:56.373 回答
0

为了确保我理解这个问题,您正在创建一个 Web 服务并想知道何时处理以及何时抛出异常。

在这种情况下,我强烈建议您捕获所有异常。“未处理”异常是非常糟糕的形式。在网站上,它们会暴露您不希望公众看到的内部信息,从而导致显示范围从无意义到危险。

如果这是一个大小合适的程序,我建议您创建自己的 MyException 类,该类派生自 System.Exception。这样做的目的是为您提供一个添加特定于您的应用程序的附加信息的地方。以下是我喜欢添加到 MyException 类中的一些典型内容:

  1. 一个 ID 号,可帮助我在代码中找到问题发生的位置。
  2. 一种记录异常的“LogMessage”方法,有时记录到 Windows 事件日志中。您是否记录以及写入哪个日志取决于您要记录的内容以及情况的严重性。
  3. 显示异常的指示器已被记录,因此上述方法即使被多次调用也不会记录两次。
  4. 考虑到这种情况,任何其他可能有用的东西。
  5. 我还喜欢将消息文本放入外部资源文件(如 XML 文档)中,并将它们键入您分配的错误号。这允许您更改错误文本以提高清晰度,而无需重新部署应用程序。

捕获所有异常并创建 MyException 类型的新实例并将原始异常放入内部异常属性中。在我的应用程序的第一级之下,我总是抛出一个 MyException 实例而不是原始异常。

在顶层(应用程序级别),永远不要让异常未经处理,也永远不要抛出自己的异常。更好的方法是在数据合约中返回错误代码和消息。这样,客户端应用程序只会获得您希望他们看到的内容。他们需要担心的唯一例外是您范围之外的例外,即配置错误或通信故障。换句话说,如果他们能够调用您的服务并且网络保持连接,您应该给他们一个他们可以解释的响应。

希望这可以帮助。

PS 我没有包含示例异常,因为我相信稍微搜索一下就会发现很多。如果您希望我提供一个简单的示例,请发布。

于 2013-10-10T01:46:02.860 回答
-2

在所有级别使用 try catch 并将其冒泡。或者,将错误记录在文件或数据库中。我使用文本文件 - 制表符分隔。在每个级别捕获 1. 模块名称(使用 C# 提供的方法来获取) 2. 方法名称 3. 正在执行的代码(用户创建 - “连接到数据库”) 4. 错误号 5. 错误描述 6. 正在执行的代码(用户创建 - “访问数据库”) 7. 最终用户的错误编号 8. 最终用户的错误描述 此外,我还传递了一个唯一标识符,例如 - Web 的会话 ID、登录的用户 ID、用户名(如果可用的)

我总是有异常捕获块。在这里,我将错误编号设置为 -0,并将来自异常对象的消息设置为错误描述。如果它与 SQL Server 相关 - 我捕获 SQL 异常。这会生成一个错误号 - 我使用它。

不过,我想进一步扩展它。

于 2013-10-10T01:18:07.660 回答