NET MVC全局异常处理(一)
.NET MVC全局异常处理
一直知道有.NET有相关的配置,但没有实际做过,以为改下设定就可以,结果实际使用的时候还是遇到不少问题,所以要记录一下。
IIS配置
刚开始不想改程序代码,所以直接就想到了IIS里面的错误页配置配置,一开始反复测试,设置改了很多,但是没有效果,后来发现是静态页的配置,还没有进入MVC的程序部分,所以对于.NET MVC这种动态页是不生效的,应该使用.NET错误页选项
静态错误页配置
静态页配置流程如下:
如上图所示,IIS中配置对错误的响应有三种方式,默认情况是第三个,本地访问显示详细错误信息,外部地址访问显示自定义页面,这样方便开发者调试,如果没有设置专门的错误页会使用IIS自带的样式,也就是第二张图中的配置,根据路径我们可以找到这样一个文件夹,里面都是错误提示的静态页,对应不同的状态代码
我们可以把IIS设置为均使用自定义错误页看下效果,或者直接通过文件访问
上面那张是详细的静态404错误,可以看到会暴露我们系统路径,下面则是默认的自定义错误页
静态错误的默认页有相应的设置,看似可以修改,有“文件”、“执行URL”、“重定向”三种,但是实际设置一下就会发现报错:锁定错误
通过这个错误我们去搜索解决方法可以看到一些人说将web.config中的httperror节下的defaultPath解锁即可,但似乎这是IIS7以前的设置,在IIS10中并没有相应的选项,看到一些说明提到可能是官方使用了更加安全的管理机制,因为发现这边的配置是静态页相关,不符合我的需要,没有深入研究,如果一定要使用这种可以看看这篇博客,试试能否通过系统命令解决锁定的问题
.NET错误页配置
.NET错误页的设置与静态页差不多,除了入口不一样,配置的选项也不太相同,但是整体意思一样
可以看到这里要求是绝对URL,所以实际使用起来应该是不太方便,所以没有找到太多相关资料。另外,需要web.conig中的customError设为On,部分异常如500会自动跳转到MVC的默认错误页Home/Error
使用IIS的错误页处理虽然不用改代码,但是维护起来局限性很多,最终还是应该通过程序进行全局异常捕获
程序设置
通过程序控制的方法我想到两种,一个是使用全局配置文件Global.asax中的Application_Error方法,另一个是使用MVC的过滤器,默认的四种过滤器中就包含异常过滤
全局异常配置
这种方法对于WebForm和MVC都是通用的,在ASP.NET中,只要网站程序抛出未捕获的异常都会触发Application_Error事件。
使用此方法一定要把GlobalFilter全局过滤器中的HandleErrorAttribute注册取消掉,也可以将配置文件中的customErrors节点关闭,否则HTTP 500的错误将不会被Application_Error事件捕获。
捕获到异常之后我们可以很容易地跳转到静态页面
protected void Application_Error(object sender, EventArgs e)
{
Exception exception = Server.GetLastError();
var httpStatusCode = (exception as HttpException)?.GetHttpCode() ?? 700; //如果为空则走自定义
var httpContext = ((MvcApplication)sender).Context;
httpContext.ClearError();
switch (httpStatusCode)
{
case 404:
httpContext.Response.Redirect("~/Error/404.htm");
break;
default:
httpContext.Response.Redirect("~/Error/500.htm");
break;
}
}
在一般情况下我们也可以指向一个控制器
protected void Application_Error(object sender, EventArgs e)
{
Exception exception = Server.GetLastError();
var httpStatusCode = (exception as HttpException)?.GetHttpCode() ?? 700; //如果为空则走自定义
var httpContext = ((MvcApplication)sender).Context;
httpContext.ClearError();
var routeDic = new RouteValueDictionary
{
{"controller", "Home"},
{ "action","Error"}
};
httpContext.Response.RedirectToRoute("Default", routeDic);
}
但是在实际的业务中遇到了一些http请求的问题,在处理一部分代码抛出的异常时会出现“服务器无法在已发送HTTP标头之后······”这一系列异常,如“设置状态”、“追加标头”等,这个时候跳转要使用另一种写法
protected void Application_Error(object sender, EventArgs e)
{
Server.ClearError();
Response.TrySkipIisCustomErrors = true;
var routeData = new RouteData();
IController controller = new HomeController();
routeData.Values.Add("controller", "Home");
routeData.Values.Add("action", "Error");
controller.Execute(new RequestContext(new HttpContextWrapper(Context), routeData));
Response.End();
}
这里要注意的一点是如果要使用Area中的控制器不能写成routeData.Values.Add,而是使用DataTokens
routeData.DataTokens.Add("area", "TestArea");
【转载】网站遭遇DDoS攻击怎么办
在网站运维过程中,有些人的网站遭遇过DDoS攻击,DDos攻击又叫做分布式拒绝服务攻击。DDos攻击将多个计算机联合起来作为攻击平台,对一个或多个目标发动DDoS攻击,从而成倍地提高拒绝服务攻击的威力。该攻击方式利用目标系统网络服务功能缺陷或者直接消耗其系统资源,使得该目标系统无法提供正常的服务。该攻击导致的结果就是你的网站无法响应正常的请求,正常的流量用户无法打开的你的网站,无法访问到你的网站内容。如果你的服务器是阿里云、腾讯云这类云服务器厂商的服务器,可选购DDoS高防IP产品来阻止这类攻击行为。
先介绍下DDoS攻击防御方法:
(1)过滤不必要的服务和端口:可以使用Inexpress、Express、Forwarding等工具来过滤不必要的服务和端口,即在路由器上过滤假IP。
(2)异常流量的清洗过滤:通过DDOS硬件防火墙对异常流量的清洗过滤,通过数据包的规则过滤、数据流指纹检测过滤、及数据包内容定制过滤等顶尖技术能准确判断外来访问流量是否正常,进一步将异常流量禁止过滤。
(3)分布式集群防御:这是目前网络安全界防御大规模DDOS攻击的最有效办法。分布式集群防御的特点是在每个节点服务器配置多个IP地址(负载均衡),并且每个节点能承受不低于10G的DDOS攻击,如一个节点受攻击无法提供服务,系统将会根据优先级设置自动切换另一个节点。
(4)高防智能DNS解析:高智能DNS解析系统与DDOS防御系统的完美结合,为企业提供对抗新兴安全威胁的超级检测功能。它颠覆了传统一个域名对应一个镜像的做法,智能根据用户的上网路线将DNS解析请求解析到用户所属网络的服务器。
除了上述提到的几个处理办法外,如果你的服务器是云服务器厂商的服务器,则云服务器厂商一般都会提供DDoS高防IP这类的安全产品,可通过购买这类安全类的产品来快速设置阻止服务器遭遇到的DDos攻击,以下只以阿里云的DDoS高防IP产品为例。
阿里云的DDos高防IP产品是针对互联网服务器(包括非阿里云主机)在遭受大流量DDoS攻击后导致服务不可用的情况下,推出的付费服务,用户可通过配置高防IP,将攻击流量引流到高防IP,确保源站的稳定可靠。
直接上官方的帮助文档链接,不再详细阐述,相关运维人员可参考官方帮助文档:
(1)阿里云DDoS高防IP产品首页。
如果使用的服务器是腾讯云服务器,可参考腾讯云的官方文档:腾讯云DDoS 防护(大禹)。
使用 HttpRequester 更方便的发起 HTTP 请求
使用 HttpRequester 更方便的发起 HTTP 请求
Intro#
一直感觉 .net 里面(这里主要说的是 .net framework 下)发送 HTTP 请求的方式用着不是特别好用,而且在 .net framework 里发送 HTTP 请求的方式有好几种,如:WebClient
/WebRequest
/HttpClient
,于是自己封装了一个 HttpRequester
WebClient
主要是用来下载,不能对 HTTP 做较多的自定义,HttpClient
是微软后来加入的,也是比较推荐使用的处理 HTTP 请求的,但是在 .net framework 下如果不注意的话可能会造成很大的灾难,从 .net core 2.1 开始,微软引入了 HttpClientFactory
去解决了一些问题,如果你是在 .net core 程序下跑的话,推荐使用 HttpClient
,如果在 .net framework 下跑的话可以使用 WebRequest
,这里说明一下,.net core 下,WebRequest
内部也是基于 HttpClient
的,详细可以参考 https://github.com/dotnet/corefx/blob/master/src/System.Net.Requests/src/System/Net/HttpWebRequest.cs#L1096。
HttpRequester
是基于 WebRequest
封装的,使用比较简洁的 Fluent API 的方式调用,如果是在 .net framework 下开发,可以尝试使用一下,具体使用可以参考下面的示例以及 Github 上的示例代码 示例代码2
添加 Nuget 包引用#
添加对 WeihanLi.Common
的引用,需要 1.0.14 及以上版本
使用 HttpRequester
#
var result = new HttpRequester("https://weihanli.xyz") // 使用 GET 方式请求 https://weihanli.xyz
.Execute(); // 返回 responseText
System.Console.WriteLine(result);
// 使用 POST 方法请求 https://accounting.weihanli.xyz/Account/LogOn
var loginResult = new HttpRequester("https://accounting.weihanli.xyz/Account/LogOn", HttpMethod.Post)
.WithHeaders(new Dictionary<string, string>()
{
{ "X-Requested-With", "XMLHttpRequest" },
}) // 设置请求头
// .AjaxRequest(true)
// 设置 Referer,在做爬虫时会比较有用,还可以通过 WithProxy("proxyUrl") 设置代理
.WithReferer("https://accounting.weihanli.xyz/Account/Login?ReturnUrl=%2F")
// 手动设置 UserAgent,默认会随机设置一个 UA
.WithUserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36")
.WithFormParameters(new Dictionary<string, string>()
{
{"Username","liweihan" },
{"Password", "112233" },
{"RememberMe","false" }
}) // 设置 post 的 form 参数
// 获取返回的 responseText,并 json 反序列化为一个强类型的Model
.Execute<WeihanLi.Common.Models.JsonResultModel<bool>>();
System.Console.WriteLine(loginResult.ToJson());
// 上传文件示例
var uploadFileResponse = new HttpRequester("https://graph.baidu.com/upload", HttpMethod.Post)
.WithFile($@"{System.Environment.GetEnvironmentVariable("USERPROFILE")}\Pictures\4e6ab53e383863ed4d15252039f70423.jpg", "image", new Dictionary<string, string>()
{
{ "tn","pc" },
{ "from","pc" },
{ "image_source","PC_UPLOAD_SEARCH_FILE" },
{ "range","{\"page_from\": \"searchIndex\"}" },
}) // 设置上传文件,并设置其它 form 参数信息
.WithReferer("https://baidu.com/") // 设置 referer
.WithUserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36")
.ExecuteForResponse(); // 获取一个 HttpWebResponse 对象,可以使用 StatusCode/ ResponseHeader 等信息
System.Console.WriteLine($"Response status:{uploadFileResponse.StatusCode}, result:{uploadFileResponse.ReadToEnd()}");
More#
除了 Header/Referer/UserAgent 之外,还可以设置 Proxy,设置 Cookie,Ajax 请求 等信息,而且还可以直接 PostJson 示例如下:
new HttpRequester("requestUrl", HttpMethod.Post)
.WithProxy("proxyUrl") // 使用代理 //.WithProxy("url", "userName", "password") // 配置带密码的代理
.WithCookie(cookie) //带 Cookie 访问 //.WithCookie("url", cookie) // 只用指定路径的 cookie
.WithJsonParameter(entity) // post 一个 json 对象,content-type 会自动设置为 `application/json`
.AjaxRequest(true) // 设置该请求是 Ajax 请求
.Execute();
Memo#
C#文件流。
什么是文件流:
字节流,把大文件分散成byte[ ]
使用文件流:
class Program { static void Main(string[] args) { //文件流的使用方式。 string msg = "张三"; //字符串转成byte数组。 byte[] bytes = System.Text.Encoding.UTF8.GetBytes(msg); //byte转成字符串 string newmsg = System.Text.Encoding.UTF8.GetString(bytes); Console.WriteLine(newmsg); } }
通过FileStream来读写文件。
class Program { static void Main(string[] args) { ////1、 创建文件流 //FileStream fsWrite = new FileStream("fist.txt", FileMode.Create); ////2、使用文件流,执行读写操作。 //string msg = "I am a colder."; //byte[] bytes = System.Text.Encoding.UTF8.GetBytes(msg); ////写 ////参数1:将指定字节数组写入文件 ////参数2:参数1的偏移量,一般为0 ////参数3:要写入的实际字节个数 //fsWrite.Write(bytes, 0, bytes.Length); ////3、清空缓冲区 执行上面的操作后 并没有真正的写入而是还在缓冲区(默认满了才写) //// 关闭文件流 //// 释放资源 可以只掉这个 它内部会调用上两个 //fsWrite.Flush(); //fsWrite.Close(); //fsWrite.Dispose(); //Console.WriteLine("finished!"); ////使用文件流时,可以将代码放到 ////using () ////{ ////} ////中 ,可以自动释放资源。 //读取文件 //1、创建文件流 using (FileStream fsRead = new FileStream("fist.txt", FileMode.Open)) {//不需指定编码,因为这是读取字节流,还没转成字符串 //根据文件的总字节数,创建byte数组,一次性读入 byte[] bytes1 = new byte[fsRead.Length]; //2、读取文件 fsRead.Read(bytes1, 0, bytes1.Length); string readmsg = System.Text.Encoding.UTF8.GetString(bytes1); Console.WriteLine(readmsg); } } }
文件流实现大文件拷贝:
class Program { static void Main(string[] args) { //文件流实现文件拷贝 string source = @"c:\copy.txt"; string target=@"e:\copy.txt"; Copy(source, target); } private static void Copy(string source, string target) { // 1、创建读取源文件的文件流 using(FileStream fsRead=new FileStream(source, FileMode.Open, FileAccess.Read)) { //2,创建写入文件的文件流。 using(FileStream fsWrite=new FileStream(target, FileMode.Create, FileAccess.Write)) { //拷贝文件的时候,创建一个中间缓冲区。 byte[] bytes = new byte[1024]; //返回值表示本次实际读取到的字节个数。 int r= fsRead.Read(bytes, 0, bytes.Length); while (r>0) { //将读取的内容写到新文件中。 第三个参数应该是实际读取到的字节数 fsWrite.Write(bytes, 0, r); Console.WriteLine("."); r = fsRead.Read(bytes, 0, bytes.Length); } } } } }
File的一些读文件方法 和FileStream 都是一次性读入
Url的Base64编码以及解码
Base64可以将二进制转码成可见字符方便进行http传输,但是base64转码时会生成“+”,“/”,“=”这些被URL进行转码的特殊字符,导致两方面数据不一致。我们可以在发送前将“+”,“/”,“=”替换成URL不会转码的字符,接收到数据后,再将这些字符替换回去,再进行解码。在ASP.NET应用程序中,可以使用HttpUtility工具类结合Convert类来实现对URl进行Base64编码以及解码操作。
(1)将URL进行Base64编码
public static string Base64Encrypt(string sourthUrl) { string eurl = HttpUtility.UrlEncode(sourthUrl); eurl = Convert.ToBase64String(encoding.GetBytes(eurl)); return eurl; }
(2)将URL进行Base64解码
public static string Base64Decrypt(string eStr) { if (!IsBase64(eStr)) { return eStr; } byte[] buffer = Convert.FromBase64String(eStr); string sourthUrl = encoding.GetString(buffer); sourthUrl = HttpUtility.UrlDecode(sourthUrl); return sourthUrl; }
C#计算字符串长度,汉字算两个字符
在C#中的字符串类String中,有个Length属性表示字符串的长度,但该字段返回的是字符的个数,如果字符串中含有中文字符的话,一个汉字占用两个字符的长度,此时获取的长度就不够精确,当然也看具体业务需要。以下方法可用于计算字符串长度,字符串中的一个汉字计为两个字符。
/// <summary> /// 得到字符串长度,一个汉字长度为2 /// </summary> /// <param name="inputString">参数字符串</param> /// <returns></returns> public static int StrLength(string inputString) { System.Text.ASCIIEncoding ascii = new System.Text.ASCIIEncoding(); int tempLen = 0; byte[] s = ascii.GetBytes(inputString); for (int i = 0; i < s.Length; i++) { if ((int)s[i] == 63) tempLen += 2; else tempLen += 1; } return tempLen; }
2019周笔记(2.18-2.23)
这周没有什么太专题的东西,就流水账记一下。最近开始入手转型Asp.net MVC,虽然已经落后时代几条街,但是还是赶赶追追。有一些太细的点记在我的笔记里,就不记到这里了,每次发博客园都是第二遍编写,希望能增加记忆。
2019.02.19
以前都是在C#程序中直接生成MD5,这一次由于逻辑需要,要在SQL 直接生成MD5,学习了一个语句:
select substring(sys.fn_sqlvarbasetostr(HashBytes('MD5','123456')),3,32)
然后进行了发散得到以下知识:
1、--HashBytes ('加密方式', '待加密的值')--(有个疑问,为什么他的返回类型是二进制,最终select出来的确实0x开头的16进制???)
--加密方式= MD2 | MD4 | MD5 | SHA | SHA1
--返回值类型:varbinary(maximum 8000 bytes)
select HashBytes('MD5','123456')
--HashBytes生成的结果为:0xE10ADC3949BA59ABBE56E057F20F883E
其中,'待加密的值'要非常小心,赋的值相同,如果类型不同,得到的md5相差甚远。例如:varchar(10),nvarchar(10),因为他们的实际存储大小不一样。
2、sys.fn_sqlvarbasetostr用于把字节流类型varbinary,转化成字符流varchar
3、substring('原始字符串','开始位置','截取长度')字符串截取。有一点需要注意的是,sql跟C#程序不同的是,开始位置是从1开始的,不是0,别算错了
2019.02.20
通过MVC的一个[ValidateAntiForgeryToken]特性学习到了“跨网站请求伪造”(CSRF(Cross-site request forgery))这个概念,意思就是第三方站点利用漏洞站点对浏览器cookie的信任,发送虚假请求,从而做出一些未经用户许可的操作,前提是用户未退出信任站点的会话,并且信任站点在本地产生了cookie。之前确实没怎么注意这个细节,只是现在开发的WebAPI项目中根本没有用到cookie,连session也没用,所以应该是安全的。
2019.02.21
集合类型的几种选择思路:
1.如果你返回的集合是只用于遍历,不可修改的,则返回IEnumerable<T>
2.如果返回的集合需要修改,如添加和删除元素,用ICollection<T>
3.如果返回的集合需要支持排序,索引等,用IList<T>
4.如果返回的集合要支持索引,但不能添加,删除元素,用ReadOnlyCollection<T>
2019.02.22
1、泛型:Func<T, bool>,Expression<Func<T, bool>>用法
Func<T, bool>:.net系统自定义了两种委托,有返回值的Func,无返回值的Action。而Func的最后一个参数总是委托的返回类型。
Expression<Func<T, bool>>:是一种表达式,EF中where要求的类型
例如:
//正确的代码 Expression<Func<QuestionFeed, bool>> predicate=null; if (type == 1) { predicate = f => f.FeedID == id && f.IsActive == true; } else { predicate = f => f.FeedID == id; } _questionFeedRepository.Entities.Where(predicate);
Mysql语句中当前时间不能直接使用C#中的Date.Now传输
MySql中处理字符串时间,会默认把第一个数字当成年份处理。
在C#服务器中,使用Date.Now.ToString()生成的字符串时间,如果不指定字符串格式,C#会按照系统语言输出不同的字符串格式,如:
a. 美国: 06/01/2019 01:59:00 PM
b.中国: 2019/06/01 13:59:00
原因分析:进过翻阅很多资料,汇总一下,发现,Date.Now默认转字符串时跟一个类有关:System.Globalization.CultureInfo(提供有关特定区域性(对于非托管代码开发,则称为“区域设置”)的信息。 这些信息包括区域性的名称、书写系统、使用的日历、字符串的排序顺序以及对日期和数字的格式化设置。)
参考:https://docs.microsoft.com/zh-cn/dotnet/api/system.globalization.cultureinfo?view=netframework-4.7.2
区域语言
而微软通过这个类,对不同系统的用户做了人性化的处理,如:对时间的显示格式存在差别。设置后对服务器代码部署到不同地区的开发者来说,便于对日期等格式的统一化管理!
解决方案:
1-临时修改线程中的区域语言为中文模式:
System.Threading.Thread.CurrentThread.CurrentCulture=new System.Globalization.CultureInfo("zh-CN");
2-使用全局配置模式:globalization节点下添加属性:culture="zh-CN" uiCulture="zh-CN"
<system.web> <compilation debug="true" targetFramework="4.5" /> <globalization requestEncoding="utf-8" responseEncoding="utf-8" fileEncoding="utf-8" culture="zh-CN" uiCulture="zh-CN" /> </system.web>
3-Date.Now.ToString("yyyy-MM-dd HH:mm:ss") 指定日期类型,避免使用:string.format(@"{0}",Date.Now)或者Date.Now.ToString()这样的默认字符串格式。
Date.Now.ToString("yyyy-MM-dd HH:mm:ss")
4-使用Mysql自带的获取当前时间方法: now()
select now() --获取当前时间
总结:需要注意的事,前面2中解决方案解决了问题根源;后2中方案只是绕过了这个问题,但是对于后来的新同学可能还会犯同样的错误,所以推荐前面两种方案,这样在写当前时间的时候,你随意怎么写。
另外像:月/日/年 小时:分 这样的时间格式,SqlServer是支持的,这里给SqlServer的强大点个赞!!!
备注几个本人查阅很多资料的地址,或许对大家有其他参考价值:
https://www.c-sharpcorner.com/article/datetime-in-c-sharp/
错误实例一:如下面的代码,在英文版的windows系统下,会导致查询数据不准:应改为:Date.Now.ToString("yyyy-MM-dd HH:mm")
/// <summary> /// (用户ID) => 用户优惠码可用数量 /// </summary> /// <param name="userId">用户ID</param> /// <returns></returns> public static int GetCouponCount(int userId) { int count = 0; if (userId > 0) { List<string> whereList = new List<string> { string.Format("`{0}` = '{1}'", Coupon_user_mapping._USERID_, userId), string.Format("`{0}` = '{1}'", Coupon_user_mapping._STATUS_, ECoupon.Status.可用.GetValue()), string.Format("`{0}` > '{1}'", Coupon_user_mapping._USEENDTIME_, DateTime.Now) }; string where = string.Join(" AND ", whereList); whereList.Clear(); whereList = null; Coupon_user_mappingBLL.Select(where, out count); } return count; }
下面是本公司服务器时间测试截图:左侧为中文windows系统,右侧为英文版windows系统。 在设置CultureInfo区域语言后,英文的系统展示时间也可以为左侧的标准格式了。
Mysql中Count函数的正确使用
备注: 直接使用Count(*)或Count(1)这些大家基本都会,主要是Count函数还可以加满足表达式的统计:express
关于Count函数表达式的用法,目前个人只知道2种:
a:使用:Count(表达式 Or null)
b:使用:Count(Case when 表达式 then 1 END) 或者 Count(CASE WHEN 表达式 THEN 1 ELSE null END)
如:
select userid, COUNT(ParentID=0 OR NULL), COUNT(CASE WHEN ParentID=0 THEN 1 END), COUNT(CASE WHEN ParentID=0 THEN 1 ELSE NULL END) from usermessage where userid in (511946,656015,1541157,3465768,5049530,5112483,5210074,5372797) GROUP BY userid