异常处理是每个系统必不可少的一个重要部分,它可以让我们的程序在发生错误时友好地提示、记录错误信息,更重要的是不破坏正常的数据和影响系统运行。
在MVC中处理异常,相信开始很多人都是继承HandleErrorAttribute,然后重写OnException方法,加入自己的逻辑,例如将异常信息写入日志文件等。当然,这并没有任何不妥,但良好的设计应该是场景驱动的,是动态和可配置的。
简单地说,不同的场景有不同的需求,而我们的程序需要更好的面对变化。当然,继承HandleErrorAttribute也完全可以实现上面所说的,只不过这里我不打算去扩展它,而是重新编写一个模块,并且可以与原有的HandleErrorAttribute共同使用。
下面让我们一起来学习ASP.NET MVC异常处理模块编程实例吧!
2.1 定义配置信息
从上面已经可以知道我们要做的事了,针对不同的异常,我们希望可以配置它的处理程序,错误页等。如下一个配置:
<!--自定义异常配置-->
<settingException>
<exceptions>
<!--add优先级高于group-->
<add exception="Exceptions.PasswordErrorException"
view ="PasswordErrorView"
handler="ExceptionHandlers.PasswordErrorExceptionHandler"/>
<groups>
<!--group可以配置一种异常的view和handler-->
<group view="EmptyErrorView" handler="ExceptionHandlers.EmptyExceptionHandler">
<add exception="Exceptions.UserNameEmptyException"/>
<add exception="Exceptions.EmailEmptyException"/>
</group>
</groups>
</exceptions>
</settingException>
其中,add 节点用于增加具体的异常,它的 exception 属性是必须的,而view表示错误页,handler表示具体处理程序,如果view和handler都没有,异常将交给默认的HandleErrorAttribute处理。而group节点用于分组,例如上面的UserNameEmptyException和EmailEmptyException对应同一个处理程序和视图。
程序会反射读取这个配置信息,并创建相应的对象。我们把这个配置文件放到Web.config中,保证它可以随时改随时生效。
2.2 异常信息包装对象
这里我们定义一个实体对象,对应上面的节点。如下:
public class ExceptionConfig
{
/// <summary>
/// 视图
/// </summary>
public string View{get;set;}
/// <summary>
/// 异常对象
/// </summary>
public Exception Exception{get;set;}
/// <summary>
/// 异常处理程序
/// </summary>
public IExceptionHandler Handler{get;set;}
}
2.3 定义Handler接口
上面我们说到,不同异常可能需要不同处理方式。这里我们设计一个接口如下:
public interface IExceptionHandler
{
/// <summary>
/// 异常是否处理完成
/// </summary>
bool HasHandled{get;set;}
/// <summary>
/// 处理异常
/// </summary>
/// <param name="ex"></param>
void Handle(Exception ex);
}
各种异常处理程序只要实现该接口即可。
2.3 实现IExceptionFilter
这是必须的。如下,实现IExceptionFilter接口,SettingExceptionProvider会根据异常对象类型从配置信息(缓存)获取包装对象。
public class SettingHandleErrorFilter : IExceptionFilter
{
public void OnException(ExceptionContext filterContext)
{
if(filterContext == null)
{
throw new ArgumentNullException("filterContext");
}
ExceptionConfig config = SettingExceptionProvider.Container[filterContext.Exception.GetType()];
if(config == null)
{
return;
}
if(config.Handler != null)
{
//执行Handle方法
config.Handler.Handle(filterContext.Exception);
if (config.Handler.HasHandled)
{
//异常已处理,不需要后续操作
filterContext.ExceptionHandled = true;
return;
}
}
//否则,如果有定制页面,则显示
if(!string.IsNullOrEmpty(config.View))
{
//这里还可以扩展成实现IView的视图
ViewResult view = new ViewResult();
view.ViewName = config.View;
filterContext.Result = view;
filterContext.ExceptionHandled = true;
return;
}
//否则将异常继续传递
}
}
2.4 读取配置文件,创建异常信息包装对象
这部分代码比较多,事实上,你只要知道它是在读取web.config的自定义配置节点即可。SettingExceptionProvider用于提供容器对象。
public class SettingExceptionProvider
{
public static Dictionary<Type, ExceptionConfig> Container =
new Dictionary<Type, ExceptionConfig>();
static SettingExceptionProvider()
{
InitContainer();
}
//读取配置信息,初始化容器
private static void InitContainer()
{
var section = WebConfigurationManager.GetSection("settingException") as SettingExceptionSection;
if(section == null)
{
return;
}
InitFromGroups(section.Exceptions.Groups);
InitFromAddCollection(section.Exceptions.AddCollection);
}
private static void InitFromGroups(GroupCollection groups)
{
foreach (var group in groups.Cast<GroupElement>())
{
ExceptionConfig config = new ExceptionConfig();
config.View = group.View;
config.Handler = CreateHandler(group.Handler);
foreach(var item in group.AddCollection.Cast<AddElement>())
{
Exception ex = CreateException(item.Exception);
config.Exception = ex;
Container[ex.GetType()] = config;
}
}
}
private static void InitFromAddCollection(AddCollection collection)
{
foreach(var item in collection.Cast<AddElement>())
{
ExceptionConfig config = new ExceptionConfig();
config.View = item.View;
config.Handler = CreateHandler(item.Handler);
config.Exception = CreateException(item.Exception);
Container[config.Exception.GetType()] = config;
}
}
//根据完全限定名创建IExceptionHandler对象
private static IExceptionHandler CreateHandler(string fullName)
{
if(string.IsNullOrEmpty(fullName))
{
return null;
}
Type type = Type.GetType(fullName);
return Activator.CreateInstance(type) as IExceptionHandler;
}
//根据完全限定名创建Exception对象
private static Exception CreateException(string fullName)
{
if(string.IsNullOrEmpty(fullName))
{
return null;
}
Type type = Type.GetType(fullName);
return Activator.CreateInstance(type) as Exception;
}
}
以下是各个配置节点的信息:
settingExceptions节点:
/// <summary>
/// settingExceptions节点
/// </summary>
public class SettingExceptionSection : ConfigurationSection
{
[ConfigurationProperty("exceptions",IsRequired=true)]
public ExceptionsElement Exceptions
{
get
{
return (ExceptionsElement)base["exceptions"];
}
}
}
exceptions节点:
/// <summary>
/// exceptions节点
/// </summary>
public class ExceptionsElement : ConfigurationElement
{
private static readonly ConfigurationProperty _addProperty =
new ConfigurationProperty("", typeof(AddCollection), null, ConfigurationPropertyOptions.IsDefaultCollection);
[ConfigurationProperty("", IsDefaultCollection = true)]
public AddCollection AddCollection
{
get
{
return (AddCollection)base[_addProperty];
}
}
[ConfigurationProperty("groups")]
public GroupCollection Groups
{
get
{
return (GroupCollection)base["groups"];
}
}
}