【ASP.NET Core】处理异常

依照老周的良好作风,开始之前先说点题外话。

前面的博文中,老周介绍过自定义 MVC 视图的搜索路径,即向 ViewLocationFormats 列表添加相应的内容,其实,对 Razor Page 模型,也可以向 PageViewLocationFormats 列表添加相应的搜索路径,比如 /MyPages/{1}/{0}.cshtml。其中,0 是视图名,1 是页面名称。比如这样。

            services.AddMvc().AddRazorOptions(opt =>{opt.ViewLocationFormats ...                opt.PageViewLocationFormats ...});

然而,我们知道,基于 Razor 的 Web Page 模型是以页面为单位的,也就是说路径路由是直接指向页面的(不包含.cshtml 扩展名),即不需要 MVC 模型的路由方式。所以,我们并不需要修改 PageViewLocationFormats 中的内容。许多时候,我们只要告诉应用程序在哪个目录下查找 Page 就行了。

默认的搜索位置是 /Pages 目录,我们可以通过以下代码来修改。

public void ConfigureServices(IServiceCollection services)

        {

            services.AddMvc().AddRazorPagesOptions(opt =>

            {

                opt.RootDirectory = "/UI";

            });

        }

以上代码写在 Startup 类中,这个应该明白吧。RootDirectory 就是用来指定应用程序查找 Razor 页面的根目录路径,此处我指写了 /UI,所以,在我的项目中,我只要建一个 UI 目录,然后各类 Razor 页就往里面放就行了。

 

好了,题外话扯完了,开始说正题吧。今天咱们聊聊有关异常处理的破事吧,也可以说是错误处理,反正就这个意思,你理解就好,专业名词不必较劲,只有那些吃饱了撑着的“学术人才”才会跟名词较劲。

老办法,咱们结合示例来讲述,这样各位观众不会乏味。

大家知道,娱乐产品肾Phone已经成为流行玩具,近年来,购买肾Phone不一定只能用货币,比较典型的一种支付方式是卖肾买Phone。说实话,现在许多国产娱乐产品也很便宜,配置也不错,几百块钱就能玩得刷刷响了,割肾真没什么必要。

为了方便人们以肾换 Phone ,老周特意开发了一个在线卖肾系统。大致流程是这样的,如果你有闲置的肾,可以打开主页,输入你的一些信息,然后报个价,其他用户看见后,如果觉得合理,就认购此肾。

 

 为了使操作流程更简单,易上手,轻入门,该平台只需要输入姓名和肾的价格即可参加报价。

 

大致的页面代码如下。

  <form method="post"><div class="form-group"><label for="name">姓名:</label><input type="text" class="form-control" name="name"/></div><div class="form-group"><label for="price">价格:</label><input type="number" name="price" class="form-control"/></div><div class="form-group"><button type="submit" class="btn btn-success w-100">提  交</button></div></form>

Razor 页面很像我们以前玩过的 aspx 页面,每个页面都配套一个隐藏代码文件。Razor 页也会配有一个页面模型类,注意这个模型类要从 PageModel 派生,不是 Page 类,别搞错了,Page 类只是作为生成 HTML 代码的基类,我们的 .cshtml 文件在预编译后,是隐式继承自 RazorPage 类的。除非你要开发自己的标记语言,否则你不必理会这些类。

记住了,与 Razor 页关联的模型类是从 PageModel 类派生的,比如,本例中,当有人填写了闲置肾的相关信息后,以 POST 方式提交,这是候,如果页面模型类中包含了名字为 OnPost、OnPostAsync ……的方法时,就会自动调用。如果想把我们上面那个 form 中的 name 和 price 的值传递给方法,直接让 OnPost 方法的参数与 form 中的元素名称相同就可以了。

public class IndexModel : PageModel

    {

       public IActionResult OnPost(string name, decimal price)

        {

            if (string.IsNullOrWhiteSpace(name))

            {

                throw new Exception("你怎么不留下姓名啊,卖肾又不是丢人的事。");

            }

            if(price <= 0.0M)

            {

                throw new Exception("靠!你的肾这么不值钱吗?还免费送,包邮不?");

            }

            return RedirectToPage("/Success");

        }

    }

OnPost 不是 PageModel 基类的方法,而是我们自己写的,只是代码约定,Asp.net Core 里面用到很多代码约定,它在运行的时候会查找这些特定的名字。

上面代码中,还对传递进来的 form 值进行验证,如果不符合要求,会抛出异常。

 

一般来说,在 Startup 类的 Configure 方法中,我们会判断一下,如果应用程序处于开发阶段,为了方便测试,应该加入这些代码。

      if (env.IsDevelopment()){app.UseDeveloperExceptionPage();}

这样,我们在测试时能看到详细的异常信息。

 

但是,在实际便用时,我们不能公开这么详细的信息,这样容易勾起人们的犯罪冲动。所以,一般会添加一个页面,专门用来显示错误信息。比如:

@page


<div class="card">

    <div class="card-header bg-danger">

        <span class="text-light">错误</span>

    </div>

    <div class="card-body">

        <span class="card-text">唉,真抱歉。你提交的肾不符合国际标准,没人要的。</span>

    </div>

</div>

然后我们要在 Startup.Configure 方法中配置一下。

  app.UseExceptionHandler("/Error");

加上这一行后,当发生异常时,就会跳转到 /Error 页面。

 

 不过,你也许会觉得,虽然不能公开异常信息,但一些必要的描述应该要的,不然,用户不知道发生了啥事。我们可以通过 HttpContext 的 Features 集合获取一个用来处理异常的 Feature,它的原型接口是 IExceptionHandlerFeature,我们不必关心它的实现类型是谁,只要访问它的 Error 属性就能得到关联的 Exception 实例。

因此,我们的错误页可以改一下。

@page

@using Microsoft.AspNetCore.Diagnostics

@{

    IExceptionHandlerFeature exf = HttpContext.Features.Get<IExceptionHandlerFeature>();

    Exception ex = exf?.Error;

}


<div class="card">

    <div class="card-header bg-danger">

        <span class="text-light">错误</span>

    </div>

    <div class="card-body">

        @if (ex == null)

        {

            <span class="card-text">唉,真抱歉。你提交的肾不符合国际标准,没人要的。</span>

        }

        else

        {

            <span class="card-text">@ex.Message</span>

        }

    </div>

</div>

通过以下代码获得异常实例的引用。

    IExceptionHandlerFeature exf = HttpContext.Features.Get<IExceptionHandlerFeature>();Exception ex = exf?.Error;

这样就可以在页面上显示异常的描述信息了。

 可能你又想到了,我不想输出个页面,我只想返回一些简单的文本,那么,你在 Startup.Configure 中可以这样写。

app.UseExceptionHandler(x =>

            {

                x.Run(async context =>

                {

                    var ex = context.Features.Get<Microsoft.AspNetCore.Diagnostics.IExceptionHandlerFeature>()?.Error;

                    string msg = ex == null ? "发生错误。" : ex.Message;

                    context.Response.ContentType = "text/plain;charset=utf-8";

                    await context.Response.WriteAsync(msg);

                });

            });

里面的变量 x 就是当前的 IApplicationBuilder ,与传递给 Configure 方法的 app 参数类型一样,这时候我们可以用 Reponse 的方法返回自定义的文本。

 

 好了,今天的内容就介绍到这儿吧,其实异常处理还有一种方法——使用 Filter,这个咱们留到下一篇博文再和大伙分享。

上一篇中,老周给大伙伴们扯了有关 ASP.NET Core 中异常处理的简单方法。按照老周的优良作风,我们应该顺着这个思路继续挖掘。

本文老周就不自量力地介绍一下如何使用 MVC Filter 来处理异常。MVC 模型(当然适用于 Razor Page 、Web API 模型)可以用一系列的 Filter 来对请求与回应消息进行过滤处理。其中,在 Microsoft.AspNetCore.Mvc.Filters 命名空间下,你会发现有两个接口,它们跟异常处理有关:

IExceptionFilter:实现 OnException 方法,可以自定义回传给客户端的异常信息。

IAsyncExceptionFilter:跟上面的一样的,只不过这厮支持异步等待而已。

 

在实现处理异常的 Filter 时,传给 OnException / OnExceptionAsync 方法的有一个 ExceptionContext 类型参数,我们可以通过它来设置自定义的返回结果。

访问 Exception 属性,你可以得到相关的异常实例,当然这个属性是可写的,所以你可以获取异常实例后,将它改为其他异常实例,再重新赋给这个属性,比如,你用你自己编写的异常类来重新封装。通过 Result 属性设置返回结果,这个与 MVC Action 方法的返回方法一样,不同的是,在 Action 方法中,你可以调用 Controller 基类的方法来返回对应的 Result ,而对于 Result 属性,你必须显式地去创建实现了 IActionResult 接口的类型实例。

另外,值得注意的是,ExceptionContext 类还有一个 ExceptionHandled 属性,该属性值可读可写,主要是用于标识当前发生的异常是否已经过处理。这主要是应对 Filter 的执行顺序的,一种情况是你可能使用了多个 Filter 来处理异常,在处理过程中你就可以将这个属性值设为 true 以表示这个错误已处理过了,后面的就不必处理了;另一种情况是,以 Attribute 方式使用的 Filter 的优先级会比全局使用的 Filter 高,也许在 Attribute 上我没有对异常进行处理,那么到了全局 Filter 执行的时候,我就可以检查一下这个属性,如果没有处理就进行一下处理。关于 Attribute 方式使用 Filter 老周随后会说的,这里先提一下。

 

好了,咱们先说说如何实现自己的异常处理 Filter,其实很简单,看下面代码。

public class MyExceptionFilter : IExceptionFilter, IFilterMetadata

    {

        public void OnException(ExceptionContext context)

        {

            if(context.ExceptionHandled == false)

            {

                string msg = context.Exception.Message;

                context.Result = new ContentResult

                {

                    Content = msg,

                    StatusCode = StatusCodes.Status200OK,

                    ContentType = "text/html;charset=utf-8"

                };

            }

            context.ExceptionHandled = true; //异常已处理了

        }

在 OnException 方法中,我直接获取异常信息,然后用一个 ContentResult 对象来返回,这个是类似于 MVC 中 Controller . Action 方法返回结果,我这里简单地以 HTML 文本形式返回,一旦处理到异常,应用程序会自动把这个 Result 返回给客户端。

你可能发现了,我除了实现 IExceptionFilter 接口外,还实现了一个 IFilterMetadata 接口,这个接口是必须的,不然待会儿我们无法应用这个 Filter 了,为什么呢,等一下你就会明白了。

这里实现的这个是同步调用的,如果你希望有一个可异步等待的版本,那么,你就顺便实现一下 IAsyncExceptionFilter 接口。把上面的代码改为:

public class MyExceptionFilter : IExceptionFilter, IAsyncExceptionFilter, IFilterMetadata

    {

        public void OnException(ExceptionContext context)

        {

            if(context.ExceptionHandled == false)

            {

                string msg = context.Exception.Message;

                context.Result = new ContentResult

                {

                    Content = msg,

                    StatusCode = StatusCodes.Status200OK,

                    ContentType = "text/html;charset=utf-8"

                };

            }

            context.ExceptionHandled = true; //异常已处理了

        }


        public Task OnExceptionAsync(ExceptionContext context)

        {

            OnException(context);

            return Task.CompletedTask;

        }

    }

好了,接下来咱们得考虑怎么用它了。在 Startup.ConfigureServices 方法中,添加 MVC 功能后可以把咱们自己写的 Filter 添加进去。


public void ConfigureServices(IServiceCollection services)

        {

            services.AddMvc(opt =>

            {

                opt.Filters.Add<MyExceptionFilter>();

            });

        }


上面代码添加 Filter 后,是用于全局的,说白了,当应用程序内不管哪个 Controller 里面发生的异常,都会经过咱们添加的 Filter 处理。

 

现在我们测试一下这个异常处理的 Filter 起到什么作用。为了不影响测试,请把 Configure 方法中这段代码删除。


public void Configure(IApplicationBuilder app, IHostingEnvironment env)

        {

            if (env.IsDevelopment())

            {

                app.UseDeveloperExceptionPage();

            }

            app.UseMvc();

        }


变成这样

        public void Configure(IApplicationBuilder app, IHostingEnvironment env){app.UseMvc();}

 

然后,随便弄段代码来测试。


       [HttpPost("/code")]

        public IActionResult SubmitSome(int val)

        {

            if(val <= 0)

            {

                throw new ArgumentException("号码不能小于或等于 0。");

            }

            return Content($"恭喜你,中奖了。\n中奖号码为:{val}", "text/html;charset=utf-8");

        }

这个逻辑很简单,就是在前台页面输入一个数值,然后 POST 上来,如果数值不是大于 0 的值就抛异常。

 

然后我故意输入一个 -10。

 

 POST 后在服务器上引发异常。

继续执行,让 Filter 对异常进行处理。

最后,异常信息就返回给浏览器了。

 

 这样说明咱们写的 Filter 起作用了。

刚刚说过,在 ConfigureServices 方法中添加的 Filter 是用于全局的,如果我们的项目中有个别的 Controller 或者 Controller 中的个别方法,希望使用专门的 Filter 去处理异常,这时候就可以考虑以 Attribute 的方式去处理。

要用 Attribute 方式处理异常,需要实现 ExceptionFilterAttribute 抽象类。该抽象类已实现了咱们上面提到过的几个接口。

这个类还实现了 IOrderedFilter 接口,可以用来安排多个 Attribute 实例在处理异常上的顺序(假设你用了多个实例来处理)。

 

下面咱们自己实现一个 Attribute ,用来处理异常。

public class MyExceptionFilterAttribute : ExceptionFilterAttribute

    {

        public override void OnException(ExceptionContext context)

        {

            var ex = context.Exception;

            // 构建错误信息对象

            var dic = new Dictionary<string, object>

            {

                ["err_code"] = 80250,

                ["err_msg"] = ex.Message,

                ["err_sol"] = "建议携带你的数据到医院做检查。"

            };

            // 设置结果

            context.Result = new JsonResult(dic);

            context.ExceptionHandled = true;

        }


        public override Task OnExceptionAsync(ExceptionContext context)

        {

            OnException(context);

            return Task.CompletedTask;

        }

    }

上面代码中,我以 JSON 格式返回错误数据。

 

这个 Attribute 可以用于类与方法,然后咱们用 Web API 来测试。

[Route("api/[controller]")]

    public class DemoController : Controller

    {

        [HttpGet]

        [MyExceptionFilter]

        public IActionResult Compute(int m, int n)

        {

            if (m < 0 || n < 0)

            {

                throw new Exception("数值不能小于 0。");

            }

            return Json(new { num1 = m, num2 = n, result = m + n });

        }

    }

此处把 attrbute 用到方法上。

 

运行应用程序,然后请出 Postman 大叔来帮我们测试 Web API。为参数 m 和 n 赋值,然后以 GET 方式发送请求。

获得正确的结果,现在咱们提交小于 0 的参数。就会返回刚刚自定义的错误。

 

 好了,今天的内容就说到这里,下次有空继续扯。

原文地址:http://www.cnblogs.com/tcjiaan/p/8461408.html 


.NET社区新闻,深度好文,欢迎访问公众号文章汇总 http://www.csharpkit.com

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/322047.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

树莓派3B上部署运行.net core 2程序

针对Linxu arm处理器如何部署.net core 2的资料很少&#xff0c;网上找到几篇但都写得不够详细&#xff0c;按照他们教程来撞墙了&#xff0c;折磨了几天终于部署成功了&#xff0c;先上一张运行成功的图1.windows系统中&#xff0c;在项目的目录下使用CMD命令运行进行发布dotn…

拥抱.NET Core系列:MemoryCache 初识

MSCache能做什么&#xff1f;绝对过期支持滑动过期支持&#xff08;指定一个时间&#xff0c;TimeSpan&#xff0c;指定时间内有被Get缓存时间则顺延&#xff0c;否则过期&#xff09;过期回调自定义过期MSCache目前最新的正式版是 2.0.0&#xff0c;预览版是2.1.0&#xff0c;…

Spark Structure Streaming(一)之简介

一、Structure Streaming 结构化流是基于Spark SQL引擎构建的可伸缩且容错的流处理引擎。可以像对静态数据进行批处理计算一样&#xff0c;来表示流计算。 当流数据继续到达时&#xff0c;Spark SQL引擎将负责递增地&#xff0c;连续地运行它并更新最终结果。可以在Scala&…

Ocelot中使用Butterfly实践

Ocelot(https://github.com/TomPallister/Ocelot)是一个用.net core实现的API网关&#xff0c;Butterfly(https://github.com/ButterflyAPM/butterfly)是用.net core实现的全程序跟踪&#xff0c;现在&#xff0c;Ocelot中可以使用Butterfly了&#xff0c;关于Ocelot和Butterfl…

jzoj6290-倾斜的线【计算几何,贪心】

正题 题目大意 有nnn个点&#xff0c;将两个点连成线&#xff0c;求斜率最接近PQ\frac{P}{Q}QP​的线。 解题思路 我们有一个结论&#xff1a;若我们对于每一个点做一条斜率为PQ\frac{P}{Q}QP​的线&#xff0c;然后按截距排序&#xff0c;然后答案必定是相邻的点。 证明: 我…

Java 平台调试架构JPDA

转载自 Java-JPDA 概述 JPDA&#xff1a;Java 平台调试架构&#xff08;Java Platform Debugger Architecture&#xff09; 它是 Java 虚拟机为调试和监控虚拟机专门提供的一套接口。 一、JPDA https://docs.oracle.com/javase/8/docs/technotes/guides/jpda/ JPDA 由三个…

Ocelot + Consul实践

关于Consul(https://www.consul.io)是一个分布式&#xff0c;高可用,支持多数据中心的服务发现和配置共享的服务软件,由 HashiCorp 公司用 Go 语言开发, 基于 Mozilla Public License 2.0 的协议进行开源。 在Consul的文档上&#xff0c;Consul 支持Service Discovery, Health …

Arthas - 开源 Java 诊断工具

转载自 Arthas使用 Authas — 开源的java诊断工具 下载安装 authas是一个jar包&#xff0c;可以直接下载后运行 wget https://alibaba.github.io/arthas/arthas-boot.jarjava -jar arthas-boot.jar就可以启动起来。启动后&#xff0c;authas会自动检测存在的java进程&…

jzoj6307-安排【归并排序】

正题 题目大意 一个目前序列&#xff0c;一个目标序列&#xff0c;每次可以选择一个区间交换区间最大值和最小值。 询问在345678345678345678步内将目前序列转换回目标序列的方案(输出该方案)。 解题思路 我们考虑归并排序&#xff0c;对于两个升序的序列&#xff0c;我们考…

.NET Core 2.1 Preview 1发布:更快的构建性能

今天&#xff0c;我们宣布发布 .NET Core 2.1 Preview 1。这是 .NET Core 2.1 的第一个公开发布。我们有很大的改进希望分享出来&#xff0c;并且渴望得到您的反馈意见&#xff0c;无论是在评论中还是在github中dotnet/core #1297ASP.NET Core 2.1 Preview 1 和 Entity Framewo…

Spark SQL(七)之基于用户的相似度公式

一、基于用户的Jaccard相似度公式 其中&#xff0c;u、v表示任意两个用户&#xff0c;N(u)表示用户u喜欢的物品集合,N(v)表示用户v喜欢物品的集合。 代码 public class UserCFApp {public static void main(String[]args){SparkConf sparkConf new SparkConf();sparkConf.se…

欢乐纪中A组赛【2019.8.17】

前言 前几天题目没改完(好难QvQQvQQvQ)&#xff0c;然后这几天ZZYZZYZZY和WHFWHFWHF去广州二中了 然后我是菜鸡&#xff0c;今天暴力写挂了QAQQAQQAQ&#xff0c;T2T2T2少判断了个东西少了808080 成绩 懒得写只放自己的了(反正垫底) Rank51,20ptsRank51,20ptsRank51,20pts 正…

BXUG第11期活动

分享主题&#xff1a;Xamarin Azure 微软云加端移动应用技术架构分享者&#xff1a;周岳 微软MVP分享主题&#xff1a;从设计图到最终界面- Xamarin跨平台界面最佳实践分享者: 程文锋 视高盛景分享主题&#xff1a;基于VSTS的App DevOps分享者&#xff1a; 安庭庭 张浩 视高…

Spark SQL(八)之基于物品的相似度公式

一、基于物品的Jaccard相似度公式 其中&#xff0c;i、j表示任意两个物品&#xff0c;N(i)表示喜欢物品i的用户数&#xff0c;N(j)表示喜欢物品j的用户数。 代码&#xff1a; public class ItemCFApp {public static void main(String[]args){SparkConf sparkConf new Spark…

ASP.NET CORE MVC 实现减号分隔(Kebab case)样式的 URL

ASP.NET CORE MVC 中&#xff0c;默认的 Route 模板是&#xff1a; /{controller}/{action} 。我们可以通过开启 URL 小写转换将 URL 变为小写&#xff0c;但此方式在 Controller 或者 Action 为一个词组时&#xff0c;生成的 URL 并不友好。假设我们有 UserController 和 Add…

Spark SQL(九)之基于用户的推荐公式

一、基于用户的推荐公式 其中&#xff0c;S(u,K)表示与用户u最相似的K个用户&#xff0c;N(i)代表喜欢物品i的用户集合&#xff0c;rm表示用户v对物品i的评分。 二、代码 public class UserCFRecommendApp {public static void main(String[]args){SparkConf sparkConf new S…

jzoj6309-完全背包【贪心,背包】

正题 题目大意 完全背包&#xff0c;不过容量特别大。 解题思路 然后我们可以先做一个1∼200001\sim 200001∼20000的背包&#xff0c;然后枚举一个大小&#xff0c;然后将这个大小填入背包知道无法填入为止&#xff0c;然后剩下的空位在再一次背包就好。 codecodecode #incl…

拥抱.NET Core系列:MemoryCache 缓存过期

MSCache项目MSCache目前最新的正式版是 2.0.0&#xff0c;预览版是2.1.0&#xff0c;会与.NETCore 2.1一起发布。本篇用了2.0.0版本开源在GitHub上&#xff0c;仓库地址是&#xff1a;https://github.com/aspnet/CachingNuGet地址为&#xff1a;https://www.nuget.org/packages…

Spark SQL(十)之基于物品的推荐公式

一、基于物品的推荐公式 其中&#xff0c;S(j,K)表示与物品j最相似的K个物品&#xff0c;N(u)表示用户u喜欢的物品集合&#xff0c;Rui表示用户u对物品i的评分。 二、代码 public class ItemCFRecommendApp {public static void main(String[]args){SparkConf sparkConf new …

jzoj6308-中间值【分治】

正题 题目大意 两个序列不降a,ba,ba,b&#xff0c;每次可以修改或询问[l1,r1,l2,r2][l_1,r_1,l_2,r_2][l1​,r1​,l2​,r2​]要求输出将序列aaa的l1∼r1l_1\sim r_1l1​∼r1​和bbb的l2∼r2l_2\sim r_2l2​∼r2​部分合起来然后求中位数。 解题思路 我们考虑分治&#xff0c;…