何时引发异常?何时处理异常(服务层、控制器)的位置
本文关键字:异常 控制器 位置 何时 处理 何时引 服务 | 更新日期: 2023-09-27 18:34:44
我重写了我的问题,因为我认为它太罗嗦了,也许我想要实现的目标已经丢失了。
我在记事本中编写了这段代码,所以它可能有错误,有些东西可能不太好,但它是为了说明我看到我的选择是什么。
// 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 等(
很抱歉简短的回复,但这是我的一般规则 - 如果发生您期望可能发生的异常,请处理它 - 通过重试或告诉用户出了什么问题并为他们提供修复它的选项。
如果发生意外异常,如果这是您可以处理的事情(例如您可以重试的超时(,请尝试处理它,否则退出 - 想想任何 MS 应用程序做什么 - 例如办公室 - 你会道歉出了问题,应用程序结束了。 最好优雅地结束,而不是可能损坏数据并使事情陷入真正的混乱。
这是一篇包含 Java 特定概念和示例的文章,但这里的广泛原则是要走的路。
区分灾难性且不可恢复的故障异常和非常可恢复的意外异常。让故障"冒泡"到故障屏障,在那里您可以适当地处理。例如,您可以记录错误、向某人发送电子邮件或向邮件队列发送邮件,并向用户显示一个很好的、信息丰富的错误页面。
无论您做什么,请确保从源中保留所有异常信息。
希望有帮助。
在代码确定出现问题的地方引发异常。
您始终需要在最终用户直接调用的方法中处理异常。这是为了迎合您的代码没有特定处理的意外错误。通用处理代码通常会记录错误,并且可能包括也可能不包括让用户知道发生了意外错误。
但是,如果存在可以提前预料到的错误,则通常希望在代码中处理这些错误,更接近它们发生的时间点,以便应用程序可以从错误中"恢复"并继续。
您需要从方法返回失败的详细信息时,异常都很有用,同时能够为您调用的方法使用理想的返回类型。
你在问题中说:
现在对我来说,我尝试将错误消息返回给控制器 并尽量不要真正捕获控制器中的任何内容。
如果服务方法应该理想地返回 Venue 对象,如何将此潜在的错误消息返回给控制器? 一个 out 参数? 将返回类型更改为具有错误消息属性的内容?
如果你正在做这些选项中的任何一个,我认为你正在重新发明轮子......即创建一种在已经存在异常信息时返回异常信息的方法。
最后,异常是出错情况的强类型表示形式。如果您返回错误消息,则可以将其发送回用户,但是如果您需要根据错误的详细信息以编程方式执行不同的操作,那么您不希望打开魔术字符串。
例如,区分授权错误和未找到错误,以便向用户返回最合适的 http 状态代码,这不是很方便吗?
不要忘记 Exception 类有一个 Message 属性,如果你想以这种方式使用它,你可以简单地返回给用户
为了确保我理解这个问题,您正在创建一个 Web 服务,并想知道何时处理以及何时抛出异常。
在这种情况下,我强烈建议您捕获所有异常。 "未处理"异常是非常糟糕的形式。 在网站上,它们通过暴露您不希望公众看到的内部信息,导致从无意义到危险的显示。
如果这是一个大小合适的程序,我建议您创建自己的 MyException 类,该类派生自 System.Exception。 这样做的目的是为您提供一个位置来添加特定于您的应用程序的其他信息。 以下是我喜欢添加到我的 MyException 类中的一些典型内容:
- 一个 ID 号,可帮助我在代码中找到出现问题的位置。
- 一种"日志消息"方法,用于记录异常,有时记录到 Windows 事件日志中。 是否记录以及写入哪个日志取决于要记录的内容以及情况的严重性。 显示异常的指示器已记录,
- 因此即使多次调用上述方法,也不会记录两次。
- 在这种情况下,任何其他可能有用的东西。
- 我还喜欢将消息的文本放在外部资源文件(如 XML 文档(中,并将它们键入您分配的错误号。 这允许您更改错误文本以提高清晰度,而无需重新部署应用程序。
捕获所有异常并创建 MyException 类型的新实例,并将原始异常放入内部异常属性中。 在我的应用程序的第一级下面,我总是抛出我的一个 MyException 实例,而不是原始异常。
在顶层(应用程序级别(,永远不要让异常得不到处理,也不要抛出自己的异常。 更好的方法是在数据协定中返回错误代码和消息。 这样,客户端应用程序将只获取您希望他们看到的内容。 他们需要担心的唯一例外是超出您范围的异常,即配置错误或通信故障。 换句话说,如果他们能够调用您的服务并且网络保持连接,您应该给他们一个他们可以解释的响应。
希望这有帮助。
PS 我没有包含示例例外,因为我相信稍微搜索一下就会发现很多。 如果您希望我提供一个简单的样本,请发布。
使用尝试在所有级别捕获并冒泡它。(可选(在文件或数据库中记录错误。我使用文本文件 - 制表符分隔。在每个级别捕获1. 模块名称(使用 C# 提供的方法获取此名称(2. 方法名称3. 正在执行的代码(用户创建 - "连接到数据库"(4. 错误号5. 错误描述6. 正在执行的代码(用户创建 - "访问数据库"(7. 最终用户的错误编号8. 最终用户的错误描述此外,我还传递了一个唯一标识符,例如 - Web 情况下的会话 ID、登录用户 ID、用户名(如果可用(
我总是有异常捕获块。在这里,我将错误号设置为 -0,将来自异常对象的消息设置为错误描述。如果它与SQL Server相关 - 我捕获SQL异常。这会生成一个错误号 - 我使用它。
不过,我想再扩展一下。