回到基础:n层ASP的异常管理设计指南。网络应用

介绍 “你怎么定义好异常管理一个n层ASP。网络应用程序?” 很简单的问题,但不是简单的回答。 我们擅长做的事情。但是,可能我们不是同样擅长设计一个系统,妥善处理错误与优雅,为用户提供了一个礼貌的信息错误,不离开他/她在一个没有发展前途的,和内部,通知系统开发人员提供足够的细节,这样穷人开发者不想他们需要学习一些火箭科学解决这些错误。 所以,如果你问我同样的问题,我将告诉你以下几点: 你的系统有很好的异常管理如果: 它不显示不必要的技术错误描述当一个错误发生,相反,向用户提供一个屏幕出了问题道歉,让他/她回到系统。当发生错误时,它立即通知技术团队对故障的详细信息,以及日志记录错误的细节。异常管理在中央和可控的方式没有不必要的try…catch……把分布在整个代码库。 所以,如果我们想要确保良好的例外管理在我们的ASP。网络应用程序中,我们需要满足这三个高水平的目标。 最低限度的事情你应该做的 如果你是世界上最懒的开发人员(如几年前我),你至少应该利用ASP。网络为您提供优雅地处理异常。所有你需要的是执行以下两个简单的步骤: 在web . config中启用customError 隐藏,复制Code

<customerrorsdefaultredirect="Error.aspx"mode="On">
</customerrors>

您可能已经知道,这个小配置指示ASP。网运行时错误重定向。aspx当一个错误发生在您的ASP。网络应用程序。设置模式= "上"指示重定向,这可能不是一个好的选择,如果您正在开发您的系统。设置模式= " RemoteOnly "应该是完美的选择为你这结果重定向错误页面只有当页面浏览从远程机器。 定义一个错误页面 显然你需要创建一个错误页面,对吧?这个错误。aspx页面你可能喜欢如下: 图:最简单的错误页面ASP.NET 所以,我创建了错误。aspx如下,ASP。NET使用Visual Studio 2010中创建的网站: 标记: 隐藏,复制Code

<@PageTitle="Home Page"Language="C#"MasterPageFile="~/Site.master"AutoEventWireup="true"CodeFile="Error.aspx.cs"Inherits="Error">
<asp:contentcontentplaceholderid="HeadContent"id="HeaderContent"runat="server">
</asp:content>
<asp:contentcontentplaceholderid="MainContent"id="BodyContent"runat="server">
  <asp:panelid="pnlError"runat="server"visible="false">
    <asp:labelid="lblError"runat="server"text="Oops! 
	An error occurred while performing your request. 
	Sorry for any convenience."></asp:label>
    <asp:labelid="lblGoBack"runat="server"text="You may want to get back to the previous page 
	and perform other activities."></asp:label>
    <asp:hyperlinkid="hlinkPreviousPage"runat="server">Go back</asp:hyperlink>
  </asp:panel>
</asp:content>

后台代码: 隐藏,复制Code

public partial class Error : System.Web.UI.Page
{
  protected void Page_Load(object sender, EventArgs e)
  {
    Page.Title = "Error occurred";
    string PreviousUri = Request["aspxerrorpath"];
    if (!string.IsNullOrEmpty(PreviousUri))
    {
      pnlError.Visible = true;
      hlinkPreviousPage.NavigateUrl = PreviousUri;
    }
  }
}

执行这两个步骤后,一旦出现未处理的异常,现在,ASP。网运行时将当前请求重定向到错误页面(在这种情况下,Error.aspx)查询字符串参数“aspxerrorpath”值设置与前一页的URL。Error.aspx.cs是能够阅读前一页的URL从这个请求参数和显示一个优雅的错误消息“返回”链接,让用户返回到前一页。 为了测试是否有效,我创建了一个异常情况如下: 用户浏览违约。aspx和点击“做些什么”按钮。按钮单击事件处理程序方法抛出一个异常。ASP。网运行时捕获这个异常并将用户重定向到错误页面。 它工作得很好。 谁会修复这个错误? 非常重要的问题。很好,现在你的系统优雅地处理异常。但这是不够的,对吗?我们需要一种机制来捕获异常并通知我们的技术团队或日志除了细节的地方,这样他们可以分析和修复错误尽可能早。 所以,我们需要一种方法来捕捉异常和做一些我们自己的。让我们看看我们如何能做到这一点。 Page.OnError()事件的后台代码类方法 每一个ASP。网的后台代码类页面继承了System.Web.UI。页面类和可以覆盖OnError()事件的基类。这样做允许页面捕获任何未处理的异常使用以下的代码: 隐藏,复制Code

protected override void OnError(EventArgs e)
{
  Response.Redirect(string.Format
	("Error.aspx?aspxerrorpath={0}",Request.Url.PathAndQuery));
}

我们在ASP通常有多个页面。网络应用,这将是更聪明,如果我们定义OnError()在一个基类(BasePage)所有后台代码页面如下: 隐藏,复制Code

public class BasePage : System.Web.UI.Page
{
  public BasePage()
  {
    //
    // TODO: Add constructor logic here
    //
  }
  protected override void OnError(EventArgs e)
  {
    //Report Error
    Exception ex = Server.GetLastError();
    ErrorHandler.ReportError(ex);
    Server.ClearError();
    Response.Redirect(string.Format
	("Error.aspx?aspxerrorpath={0}",Request.Url.PathAndQuery));
  }
}

这将使我们能够摆脱实现OnError事件页面上每个后台代码的方法。 在Global.asax Application_Error()事件 您可能已经知道,你可以定义应用程序级别事件在全球。asax文件,它允许您定义一个全局错误处理程序的事件方法Application_Error()。如果你定义一个Application_Error()事件方法在此文件中,每一个未处理的异常将会被这个事件的方法,你可以做任何你想做的。主要在我们的例子中,我们要报告异常的细节在这个事件的方法,随着重定向用户到错误页面: 隐藏,复制Code

void Application_Error(object sender, EventArgs e)
{
  // Code that runs when an unhandled error occurs
  //Report Error
  Exception ex = Server.GetLastError();
  ErrorHandler.ReportError(ex); //Notifies technical team about the error
  Server.ClearError();
  Response.Redirect(string.Format
	("Error.aspx?aspxerrorpath={0}",Request.Url.PathAndQuery));
}

如果你通过Application_Error处理异常事件处理程序在全球。asax,你不需要通过Page.OnError处理异常()事件处理程序。所以,生活变得事件更容易。 异常不应该在CodeBehind类中处理吗? 这是一个好问题。显然,我们可以建议在CodeBehind类中处理异常如下: 隐藏,复制Code

try
{
  //Do some stuff
  businessObject.SaveObject(object);
}
catch(Exception ex)
{
  ErrorHandler.ReportError(ex); //Notifies technical team about the error
  Response.Redirect(string.Format
	("Error.aspx?aspxerrorpath={0}",Request.Url.PathAndQuery));
}

但是,CodeBehind类可以有许多代码,这些代码可以调用其他层上的方法,并且可以从这些方法中抛出异常。此外,还会有许多代码隐藏类。所以,以上述方式,尝试…捕获异常处理代码必须在数百个地方编写(不要忘记这样一个事实,如果我们需要更改异常处理机制,我们也需要更改数百个地方的代码)。当我们可以在一个中心位置(Application_Error())处理异常时,这听起来真的像一件聪明的事情吗?绝对没有。 所以,不要写任何尝试…catch块在CodeBehind类中,并使其完全不受任何丑陋的错误处理机制的影响。(好吧,这个建议可能有个例外,我们很快就会看到。)利用ASP的强大功能。NET,并从中心位置保持异常处理机制简单和可管理。 我有一个n层的ASP。网络应用程序。我应该如何处理异常? 这是一个经典的问题。如今,多层应用程序非常常见,在这种应用程序中应该遵循什么样的异常处理策略呢?我们是否应该在每个层中使用try…catch块吗?我们应该将异常从一个层抛出到更上层吗?我们应该在每一层中记录异常吗? 我试着把答案简化如下。你应该尽一切努力来达到你的高水平目标。也就是说,向技术团队报告错误,并向最终用户提供优雅的异常处理。您还需要确保没有在多个层中编写相同的重复代码来处理多个位置的异常。 因此,让我们尝试为n层ASP派生一个异常处理策略。NET应用程序,通过分析我们的高层目标,并使用一些常识。 我们需要优雅地处理异常 在ASP中有多少层并不重要。网络应用程序。只要我们在全局中定义了一个Application_Error事件。asax,没有什么能逃脱它。因此,无论在多层应用程序中何处发生异常,都将在全局中捕获异常。asax和它会做任何它被指示去做(重定向用户到一个错误页面)。我们需要将异常细节通知技术团队。 是的,Application_Error事件方法还通过某种机制向技术团队报告异常细节。但是,等一下!我们真的需要将每个异常都通知技术团队吗?可能不是。我们需要向技术团队报告哪些例外情况? 假设,数据访问层方法在应用程序的DAL层(一个单独的DLL)中被调用,并且,DAL方法可能会从数据库中抛出一个异常。我们应该从数据访问层向技术团队报告这些异常吗?显然yes.Why吗?因为这个错误必须在数据库例程中修复。 场景1 如果任何未处理的异常(比如NullReferenceException)从代码的任何部分抛出(它是从CodeBehind类中生成的,还是从任何其他层生成的),应该将其报告给技术团队?显然是的。为什么?出于同样的原因,这个错误必须在代码中通过一些良好的防御性编程加以修正。 场景2 假设我们从用户那里获取一个输入,并使用输入参数调用一个业务方法。业务方法期望特定的参数值在一定的范围内。如果它不在此范围内,则会抛出带有适当消息的异常。我们应该向技术团队报告这种异常吗?显然不是。为什么?因为,抛出这个异常通常是为了让用户知道他做错了什么,并使用异常消息引导用户做正确的事情。 场景3 假设在CodeBehind类中使用Response.Redirect()生成了ThreadAbortException。在捕获Application_Error事件处理程序内部后,我们应该通知开发人员这个异常吗?显然不是。为什么?因为,这个特殊的异常不是作为编程或系统故障生成的,而且没有什么需要修复的。 因此,在多层应用程序中,可以从每一层生成异常,并且这些异常可以属于不同的一般类别,根据它们的类别,需要用不同的方法处理这些异常。某些特殊类别的例外需要报告给某些特定的技术团队,而某些特殊类别的例外不需要报告给任何的技术团队,而是用来指导用户正确地使用系统,以及一些特殊类别的o例外只需要被忽略。因此,这就需要开发一个异常层次结构,它可以在应用程序的所有不同层之间通用。 异常产生的每一层会根据相应的一般类型分类(当异常发生时,这将是由包装异常与特定类型的异常的异常层次结构),并将被扔到上层,例外管理策略可以确定异常和处理那些明智的类别。 自定义异常层次结构 下面是一个非常基本的异常层次结构,可用于包装原始异常并对其进行分类,以便使用良好的异常管理策略来处理这些异常。如果我们有一个应用程序,其中有一个数据访问层和一个业务层(两个独立的类库),下面的自定义异常层次可以使用: 图:n层应用程序的基本自定义异常层次结构 异常类名是不言自明的。它们都继承了一个基异常类BaseException,该基异常类本身继承了异常类。 因此,这个异常层次结构可以如下使用: 在木豆的方法 捕获通过调用数据库操作可能抛出的任何异常,将其封装在DALException中,并抛出给上层。 隐藏,复制Code

public List<User> GetUsers()
{
  string SP = AppConstants.StoredProcedures.GET_ALL_USERS;
  IDataReader reader = null;
  List<User> users = new List<User>();
  try
  {
    using (DbCommand dbCommand = Database.GetStoredProcCommand(SP))
    {
      using (reader = Database.ExecuteReader(dbCommand))
      {
        while (reader.Read())
        {
          User user = User.CreateFromReader(reader);
          users.Add(user);
        }
      }
    }
  }
  catch(Exception ex)
  {
    throw new DALException("Failed to retrieve User.", ex);
  }
  return users;
}

内部业务方法 通常,在调用这个(或任何)DAL方法时,我们不需要处理业务方法内部的异常。为什么?因为DAL方法已经在处理异常了。因此,每当从业务方法调用DAL方法时发生任何异常时,该异常将跨上层传播到调用层次结构中,并将被全局异常处理程序Application_Error()捕获。 所以,你只需在你的业务方法中调用DAL方法,如下所示: 隐藏,复制Code

public List<User> GetUsers()
{
  UserDAO userDAO = new UserDAO();
  List<User> users = userDAO.GetUsers();
  if(users != null)
  {
    //Do other stuffs
  }
  //Do other stuffs
  return users;
}

但是,正如已经说过的,我们可能需要在一些业务方法中抛出一些自定义异常,如下所示(让调用者通知必须满足特定的条件才能继续操作): 隐藏,复制Code

public void Add(User user)
{
  if(user.Age > 18)
  {
    throw new BLLException("Not allowed for kids!");
  }
  UserDAO userDAO = new UserDAO();
  userDAO.Add(user);
}

的后台代码类里面 这里只处理BLLException类型的异常。为什么?因为,BLLException实际上表示一个业务逻辑验证错误(带有验证错误消息,请参阅上面的示例),需要将其显示给最终用户,以指导他/她提供正确的输入或正确执行其操作。如果我们不在CodeBehind类中处理BLLException异常,这些异常将被传播到全局错误处理程序Application_Error,它将忽略这个异常,或者将这个异常报告给技术团队,这两者都不是我们想要的。 下面是我们在CodeBehind类中处理BLLExceptions的方法: 隐藏,复制Code

protected bool AddUser()
{
  UserManager userManager = new UserManager();
  User user = PopulateUserFromInput();
  try
  {
    userManager.Add(user);
  }
  catch(BLLException ex)
  {
    ShowErrorMessage(ex.Message);
    return false;
  }
  return true;
}

在全局.asax中的Application_Error()事件中 当一个异常被气泡到这个全局异常处理程序方法时,它可以是ThreadAbortException,或者DALException,或者BLLException,或者是一个未处理的异常,我们没有使用任何try…应用程序中的catch块。 这个Application_Error事件处理程序方法只处理DALException和任何未处理的异常。我们已经知道,它会报告异常细节(可能会将关于异常的电子邮件发送到一些预先配置的电子邮件地址,并在某个地方记录异常细节)。它将简单地忽略ThreadAbortExceptions(因为,没有什么需要修复的)和BLLException(因为,这实际上是一个自定义异常,应该在CodeBehind类内部处理,以便向用户显示验证消息)。 下面是Application_Error事件方法的样子: 隐藏,复制Code

void Application_Error(object sender, EventArgs e)
{
  Exception ex = Server.GetLastError();
  if (ex as System.Threading.ThreadAbortException != null &amp;&amp; 
			ex as BLLException != null)
  {
    //Do not handle any ThreadAbortException or BLLException here
    //Only handle other custom exception (DALException in this example)
    //and any other unhandled Exceptions here
    ErrorHandler.ReportError(ex);
    Response.Redirect(string.Format("Error.aspx?aspxerrorpath={0}", 
			Request.Url.PathAndQuery));
  }
}

ASP。NET和Microsoft异常处理应用程序块 你可以使用我的微软提供的异常处理应用程序块,它可以完美地满足你的需要。您可以用声明的方式为每种异常定义异常策略,并且您可以配置块来处理每种异常。安装之后,您可以在Visual Studio中使用GUI工具配置应用程序块。 此链接可以帮助您在ASP中配置异常处理应用程序块。网络应用程序。我发现它很方便。 结论 当您在n层ASP中设计异常处理机制时,请记住这些简单的规则。网络应用程序: 定义一个全局异常处理程序,通过异常报告机制(可能通过发送电子邮件和记录异常)向技术团队报告异常细节,然后用礼貌的消息将用户重定向到一个错误页面,并为用户提供链接以到达原始页面。使用符合需要的异常层次结构对异常进行分类d和定义一个考虑周全的政策来处理每个类别的例外。让所有的异常传播全球异常处理程序,除了自定义异常(BLLException在本例中),这是向用户显示一个错误消息来指导他/她正确地执行他/她的工作。一旦抛出异常(无论是抛出由您或CLR)在n层应用程序从任何层,抓住它只在一个地方的后台代码类或(无论是在全局异常处理程序),你发现后不抛出收到它。扔一次,抓一次。 记住,你之前计划。安排清洁深思熟虑的异常处理机制的n层ASP。网络应用程序将让你系统顺利运行,并让你知道任何错误发生时立即修复。最终的结果是,你的开发团队,管理和客户端,每个人都很开心。 历史 2011年2月9日:最初的帖子 本文转载于:http://www.diyabc.com/frontweb/news2026.html

原文地址:https://www.cnblogs.com/Dincat/p/13456891.html