ASP.NET 企业应用技术详解
Web开发的一些基本原则
一、最小权限原则
只允许用户做某些操作,而不是不允许用户做某些操作。
用户看到的是服务器端执行输出的结果,用户不可能看到aspx、aspx.cs源文件,目标另存为也是保存的服务器端执行后的结果。js、html是被输出到浏览器上的,用户有办法看到。
二、C#代码运行在服务器端,js代码运行在客户端。
可以在空间的OnClientClick中编写客户端代码(js):return confirm(‘真的要删除吗?’)
服务器端编写context.Response.Write("<srcipt ty[pe='text/javascript> alert(' 删除成功')</script>'");
会看到页面加载时弹出消息框,查看源文件可以看到上面的代码在html源文件的最上端,即浏览器先解析上面的js程序,在继续向下解析。
不能用这种方法来询问客户是否继续操作,因为这种方法阻塞的是客户端的解析和运行,不会对服务器端产生影响。
服务器端添加Windows.Forms的引用,然后调用MessageBox。show();方法企图显示消息框式错误的,因为这个程序会运行在服务器端,而不是客户端,用户出发时消息框在服务器端弹出。
三、能在浏览器做的事就不要再服务器端做。如做一个加法运算,隐藏一个控件等。
四、客户端是不可信的
客户端验证不能代替服务器端验证。客户端验证时为了方便用户使用,服务器端验证时为了确保数据合法。有些人可以越过浏览器直接和服务器通信,或禁用js等,如果省略服务器端的验证会不安全。
不要把机密的数据算法等放到客户端。如输入密码后才可下载功能。如果仅是在html中把下载地址隐藏了,那用户一定可以看到。这样是不安全的,可以用Hyperlinker控件。让他的visible属性为false,只有用户输入正确密码后把visible为TRUE,为FALSE是在客户端html中式看不到这个控件的,只有提交返回后才把这个控件渲染回来。
应该是在机密页面打开之前做权限校验,而不是在一个页面中做校验,如果正确就倒向机密页面,不正确就不导向。
缓存(Cache)
如果每次进入页面的时候都查询数据库生成页面内容的话,如果访问量非常大,则网站性能会非常差。而如果只有第一次访问的时候才查询数据库生成页面内容,以后都直接输出内容,则能提高系统性能。这样无论有多少人访问都只访问一次数据库,数据库压力不变。
缓存(Cache)是一种用空间换取时间的技术,存在于计算机中很多地方,用来将一些慢速设备中的常用数据保存在快速设备中,取数据的时候直接从快速设备中取。比如CPU二级缓存、Windows文件读取缓存。
缓存存在失效的问题:为了保证从缓存中读取数据和慢速数据中数据一致,则需要在慢速数据中对应的数据发生变化的时候,清除缓存中相应的数据(缓存依赖)。
缓存是改进网站性能的第一个手段,就像索引是改进数据库性能的第一个手段一样。ASP.net缓存主要分为:
页面缓存(中庸)、数据源缓存(最不灵活的)、数据缓存(灵活)这三种主要类型。
页面缓存
给页面添加<%@ OutputCache Duration=“15” VaryByParam=“none”%>标签就可以启用页面缓存,这样整个页面的内容都会被缓存,页面中的ASP.Net代码、数据源在缓存期间都不会被运行,而是直接输出缓存的页面内容。 Duration表示缓存时间,以秒为单位,超过这个时间则缓存失效,再次生成以后会再缓存15秒,以此类推。在Page_Load处设置断点、修改数据库数据测试。这个缓存是在服务器缓存的,不是在客户端,因为用HttpWatch还是能看到向服务器提交的请求的,只不过服务器看到有缓存就没有再执行页面类。一般只有看帖、看新闻、看视频的页面才缓存,CUD的页面没必要缓存。
缓存是针对所有这个页面的访问者。这样1个访问者和1万个访问者、一次访问和100万次访问对数据库的压力是一样的。
对于看新闻页面来讲,如果如上设置的话,则会缓存在第一个看到的新闻,因为?id=2、?id=3只是页面的不同参数而已,为了能让不同的新闻各自缓存,因此可以设置VaryByParam=“id”,表示对于相同页面的不同的id参数进行单独缓存。如果有多个确定缓存的参数,则将参数名用分号隔开即可,比如VaryByParam=“id;number”。测试。缓存可能会有过期数据的问题,因此根据需求选用。
如果想让任何不同的查询字符串都创建不同的缓存,则设置VaryByParam="*",一般情况下设置“*”就足够。
在WebUserControl中也可以像页面缓存一样设置控件的缓存。
注:id;name 组合缓存条件:只有两个参数的值组成的字符不一样的时候,才产生缓存
数据源缓存
设定ObjectDataSource的CacheDuration(缓存时间:秒),EnableCaching=true。这样每隔CacheDuration指定的时间段才调用SelectMethod指定的方法来执行数据库查询,其他时候都是直接返回缓存的数据。取数据的过程缓存,在缓存期间,绑定控件向ObjectDataSource要数据, ObjectDataSource直接将缓存的数据返回给控件,不再去向TypeName指向的类要数据。
缓存固定的时间适用于首页、文章列表等访问频繁的页面,对于看贴页面则不适合,假设有100万个帖子,如果每个帖子都是固定缓存1小时的话,假设一小时之内有10万个帖子被看了,那么就要缓存十万个帖子,非常占用内存,因为“百年一看”的“坟帖”偶然被访问一次也缓存一个小时,占用内存。这时候可以采用“滑动窗口(sliding)”策略,比如帖子缓存10分钟,如果10分钟之内又访问了,则缓存的失效时间修改为从被访问这一刻起的10分钟之后,以此类推。这样经常访问的帖子就可以“长期缓存”,而不经常访问的帖子也不会因为偶然访问而长期占用缓存。设置方法,数据源:CacheExpirationPolicy="Sliding"。面试可聊。todo:貌似滑动有问题。不是问题,Sliding只是策略,服务器会参考。
试验代码:
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.UI; using System.Web.UI.WebControls; using System.Data.SqlClient; using System.Data; namespace 缓存相关 { public partial class 数据源缓存 : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { //加载时启用数据源缓存 ObjectDataSource obj = new ObjectDataSource(); obj.EnableCaching = true; obj.CacheDuration = 20; obj.CacheExpirationPolicy = DataSourceCacheExpiry.Sliding; //访问后就清空重新开始计数 obj.CacheExpirationPolicy = DataSourceCacheExpiry.Absolute; //累计访问计数 } private string name; public void GetInfo() { SqlConnection conn = new SqlConnection("连接字符串"); conn.Open(); SqlCommand cmd = new SqlCommand("@参数",conn); SqlParameter[] parms = { new SqlParameter("@参数","赋值变量"), new SqlParameter("@参数","赋值变量") }; cmd.Parameters.AddRange(parms); SqlDataAdapter da = new SqlDataAdapter(cmd); DataTable dt = new DataTable(); da.Fill(dt); if (dt.Rows.Count > 0) { DataBin(); } } //绑定实体对像代码 省略 public void DataBin() { } } }
依赖于文件内容
CacheDependency cDep = new CacheDependency(filePath);
依赖于数据库内容(轮询机制/通知机制)
一:轮询机制
1.在数据库新建版本表。2.在数据库新建触发器(比如在新闻表上新建)。
3.使用C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727中的aspnet_regsql.exe:
注册:aspnet_regsql -S . -E -ed -d 数据库名 -et -t 版本表名
删除:aspnet_regsql -S . -E -d 数据库名 -dt -t 版本表名
取消数据库缓存依赖: aspnet_regsql -S . -E -dd 数据库名
数据库名 列出已注册表:aspnet_regsql -S . -E -d 数据库名 –lt
4.数据库依赖对象
SqlCacheDependency cDep =
new SqlCacheDependency("GSSMS", "CacheDep");
5.配置web.config
1.依赖于文件
System.Web.Caching.CacheDependency cDep = new System.Web.Caching.CacheDependency(filePath); Cache.Add("fmsg", msg, cDep, System.Web.Caching.Cache.NoAbsoluteExpiration, System.Web.Caching.Cache.NoSlidingExpiration, System.Web.Caching.CacheItemPriority.Normal, RemovedCallback);
aspnet_regsql -S . -E(集成登陆)/-U sa -P 123 -ed(启动/-dd关闭) -d(数据库名) GSSMS -et(指定缓存依赖的表名/-dt禁用表名) -t(表名) Aticle
2.依赖于数据库的web.config配置
<system.web> <caching> <sqlCacheDependency enabled="true"> <databases> <add name="GSSMS" connectionStringName="conStr2" pollTime="15000"/> </databases> </sqlCacheDependency> </caching> </system.web>
自定义缓存,依赖缓存
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.UI; using System.Web.UI.WebControls; namespace 缓存相关 { public partial class 自定义缓存 : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { } //自定义缓存 protected void Button1_Click(object sender, EventArgs e) { Cache.Remove("title"); Cache.Insert("", "", null, DateTime.Now.AddHours(1), TimeSpan.Zero); if (Cache["title"] == null) { string title = "查询数据库的操作"; //查询数据库的操作结果 if (title != null) { Button1.Text = title; Cache["title"] = title; } } else { Button1.Text = Cache["title"].ToString(); } } //依赖缓存 依赖于文件内容 protected void Button2_Click(object sender, EventArgs e) { //Cache.Insert(); //如果缓存中没有数据 if (Cache["fmsg"] == null) { string path = Server.MapPath("~/a.txt"); string msg = System.IO.File.ReadAllText(path); //创建文件依赖对象 System.Web.Caching.CacheDependency cDep = new System.Web.Caching.CacheDependency(path); Cache.Add("fmsg", msg, cDep, System.Web.Caching.Cache.NoAbsoluteExpiration, System.Web.Caching.Cache.NoSlidingExpiration, System.Web.Caching.CacheItemPriority.Normal, RemoveCallback); Response.Write("刚才因为文件被修改所以缓存清空了,又创建了一个缓存,下次访问的时候就能看到了"); } //缓存中有数据 else { Response.Write("Cache['fmsg']=" + Cache["fmsg"].ToString()); } } //回调函数 当数据清空的时候 void RemoveCallback(string key, object value, System.Web.Caching.CacheItemRemovedReason reason) { string logPath = Server.MapPath("~/cache.log"); System.IO.File.AppendAllText(logPath,"CacheDependency中,cache["+key+"]="+value.ToString()+"因为"+reason.ToString()+ "被移除了"); } //Cache.Insert方法的使用 protected void Button3_Click(object sender, EventArgs e) { // 为数据库中的表新建对应的表,表的标示码,版本号。 // 为原来的表创建触发器,当发生改变的时候使版本号变化 //配置web.config文件 等 string msg = "数据库取出来的数据"; //查询数据库的操作结果 System.Web.Caching.SqlCacheDependency sqlDep = new System.Web.Caching.SqlCacheDependency("依赖数据库名","依赖表名"); Cache.Insert("fmsg", msg, sqlDep, System.Web.Caching.Cache.NoAbsoluteExpiration, System.Web.Caching.Cache.NoSlidingExpiration, System.Web.Caching.CacheItemPriority.Normal, RemoveCallback); } } }
缓存其他
页面缓存、数据源缓存等内部都是使用HttpRuntime.Cache来实现缓存的,在一些页面缓存、数据源缓存完成不了的特殊的缓存要求中,可以直接调用HttpRuntime.Cache进行缓存。 HttpRuntime.Cache是“单例的、全局的的管理器”
HttpRuntime.Cache进行缓存。
页面缓存用HttpResponse.RemoveOutputCacheItem(pagevirthpath)的方法进行清除
(*)清除整个网站的缓存,对于小网站来说很实用。在缓存还未失效的时候可能需要立即清空缓存,让数据库的修改立即反映到界面中,在Inserted、Updated中调用这个方法清除所有的缓存。ASP.Net没有提供现成的方法,可以使用Hack级别的代码,见备注。这段代码代码清除了所有的自定义缓存、页面缓存和数据源的缓存。
(*)ASP.Net缓存默认是保存在内存中的,还可以配置保存到数据库中。大型网站还会配合使用Memcached服务器等, Memcached能够真正运行的要运行在Linux下,windows下也有版本,.net通过Memcached的.Net客户端报来访问Memcached服务器。
/// <summary> /// 递归清理VirtualDirectory下所有页面的缓存 /// </summary> /// <param name="vd"></param> private static void ClearPageCache(VirtualDirectory vd) { foreach (object obj in vd.Children) { if (obj is VirtualDirectory) { ClearPageCache(obj as VirtualDirectory);//递归移除子目录下的缓存 } else if (obj is VirtualFile) { VirtualFile vf = obj as VirtualFile; HttpResponse.RemoveOutputCacheItem(vf.VirtualPath);//移除页面缓存 } else { throw new Exception("未知Children类型"); } } } /// <summary> /// 清理ASP.Net缓存 /// </summary> public static void ClearOutputCache() { //移除自定义缓存 foreach (var item in HttpRuntime.Cache.Cast<DictionaryEntry>().ToArray()) { HttpRuntime.Cache.Remove((string)item.Key); } ClearPageCache(HostingEnvironment.VirtualPathProvider.GetDirectory("/"));//清理页面缓存 //移除ASP.Net中ObjectDataSource等的缓存,因为这些控件将缓存放到了_cacheInternal。这种反射方法清理缓存可能会随着不同版本而失效。 Type ct = HttpRuntime.Cache.GetType(); FieldInfo cif = ct.GetField("_cacheInternal", BindingFlags.NonPublic | BindingFlags.Instance); Type cmt = HttpRuntime.Cache.GetType().Assembly.GetType("System.Web.Caching.CacheMultiple"); Type cachekeyType = HttpRuntime.Cache.GetType().Assembly.GetType("System.Web.Caching.CacheKey"); FieldInfo cachesfield = cmt.GetField("_caches", BindingFlags.NonPublic | BindingFlags.Instance); object cacheInternal = cif.GetValue(HttpRuntime.Cache); object caches = cachesfield.GetValue(cacheInternal); Type arrayType = typeof(Array); MethodInfo arrayGetter = arrayType.GetMethod("GetValue", new Type[] { typeof(int) }); object cacheSingle = arrayGetter.Invoke(caches, new object[] { 1 }); FieldInfo entriesField = cacheSingle.GetType().GetField("_entries", BindingFlags.Instance | BindingFlags.NonPublic); Hashtable entries = (Hashtable)entriesField.GetValue(cacheSingle); List<object> keys = new List<object>(); foreach (object o in entries.Keys) { keys.Add(o); } MethodInfo remove = cacheInternal.GetType().GetMethod("Remove", BindingFlags.NonPublic | BindingFlags.Instance, null, new Type[] { cachekeyType, typeof(CacheItemRemovedReason) }, null); foreach (object key in keys) { remove.Invoke(cacheInternal, new object[] { key, CacheItemRemovedReason.Removed }); } }
AJAX简介
没有AJAX会怎么样?普通的ASP.Net每次执行服务端方法的时候都要刷新当前页面,比如实现显示服务器的时间。每次都要刷新页面的坏处:页面刷新打断用户操作、速度慢、增加服务器的流量压力。如果没有AJAX,在youku看视频的过程中如果点击了“顶、踩”、评论、评论翻页,页面就会刷新,视频就会被打断。开发一个看效果:用FLVPlayer播放视频,然后开发一个顶踩的功能,看没有ajax会打断视频,然后将按钮用UpdatePanel包起来就不会打断视频了。用HttpWatch看没有AJAX的时候服务器返回的是整个页面,有了AJAX服务器只返回几个按钮的内容。
AJAX(Asynchronous JavaScript and XML,异步JavaScript和XML)是一种进行页面局部异步刷新的技术。用AJAX向服务器发送请求和获得服务器返回的数据并且更新到界面中,不是整个页面刷新,而是在HTML页面中使用JavaScript创建XMLHTTPRequest对象来向服务器发出请求以及获得返回的数据,就像JavaScript版的WebClient一样,在页面中由XMLHTTPRequest来发出Http请求和获得服务器的返回数据,这样页面就不会刷新了。 XMLHTTPRequest是AJAX的核心对象
XMLHTTPRequest
开发一个AJAX功能需要开发服务端和客户端两块程序。以一个显示服务端时间为例。首先开发一个GetDate1.ashx,输出当前时间。在HTML页面中放一个按钮,在按钮的onclick中创建XMLHTTP向GetDate1.ashx发送请求,获得返回的数据并且显示到界面上。代码见备注。面试常考:不使用UpdatePanel、JQuery等AJAX库编写一个AJAX程序。
也可以在xmlhttp.open中向服务器传递参数:xmlhttp.open("POST", "GetDate1.ashx?id=1", false),如果传递给服务器的请求里有中文,则需要使用Javascript函数encodeURI来进行URL编码。
案例:用AJAX实现顶和踩:不用数据库,用全局变量。
var xmlhttp = new ActiveXObject("Microsoft.XMLHTTP"); //创建XMLHTTP对象,相当于
xmlhttp.open("POST", "GetDate1.ashx"); //“准备”向服务器的GetDate1.ashx发出Post请求。这里还没有发出请求
//XMLHTTP默认(也推荐)不是同步请求的,也就是open方法并不像WebClient的DownloadString那样把服务器返回的数据拿到才返回,是异步的,因此需要监听onreadystatechange事件
xmlhttp.onreadystatechange = function() {
if (xmlhttp.readyState == 4) {//readyState == 4 表示服务器返回数据了
if (xmlhttp.status == 200) {//如果状态码为200则是成功
//接收服务器的返回数据,没有用send的返回值,而是在onreadystatechange 事件里来接收的
document.getElementById("s1").innerHTML = xmlhttp.responseText; //responseText属性为服务器返回的文本
}
else {
alert("AJAX服务器返回错误!");
}
}
}
//不要以为if (xmlhttp.readyState == 4) {在send之前执行!!!!
//if (xmlhttp.readyState == 4)只有在服务器返回值以后才会执行,而send之后过一会儿服务器才会返回数据
xmlhttp.send(); //这时才开始发送请求
//发出请求后不等服务器返回数据,就继续向下执行,所以不会阻塞,界面就不卡了,这就是AJAX中“A”的含义“异步”
页面详细代码:
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Ajax.aspx.cs" Inherits="Ajax" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head runat="server"> <title></title> <script src="Scripts/common.js" type="text/javascript"></script> <script type="text/javascript"> function createXmlHttp() {//创建xhr对象 var xhobj = false; try { xhobj = new ActiveXObject("Msxml2.XMLHTTP"); // ie msxml3.0+ } catch (e) { try { xhobj = new ActiveXObject("Microsoft.XMLHTTP"); //ie msxml2.6 } catch (e2) { xhobj = false; } } if (!xhobj && typeof XMLHttpRequest != 'undefined') {// Firefox, Opera 8.0+, Safari xhobj = new XMLHttpRequest(); } return xhobj; } //声明变量 var xhr = false; //在浏览器加载完所有的页面资源后创建 异步对象 window.onload = function () { xhr = createXmlHttp(); } //点击按钮时调用此方法,使用AJAX到服务器拿数据 function doAjax() { //设置参数 xhr.open("GET", "Ajax.aspx?isAjax=1", true); //设置回调函数 xhr.onreadystatechange = watching; //出发 xhr.send(null); } //回调函数 function watching() { alert(xhr.readyState); //检查 异步对象 的准备状态是否=4,如果等于4说明服务器已经将数据发回给异步对象了 if (xhr.readyState >= 4) { if (xhr.status == 200) {//正常返回,判断服务器返回的状态码是否=200 var txt = xhr.responseText;//获得服务器返回的字符数据 document.getElementById("divMsg").innerHTML = txt;//显示到页面div中 } else { document.getElementById("divMsg").innerHTML = "服务器出错啦!" + xhr.status; } } } function test() { alert(ChangeDateFormat("\/Date(1302451200000)\/")); } </script> </head> <body> <form id="form1" runat="server"> <input type="button" value="AJAX请求" onclick="doAjax()" /> <input type="button" value="test" onclick="test()"/> <div id="divMsg"> </div> </form> </body> </html>
JQuery AJAX
new ActiveXObject("Microsoft.XMLHTTP")是IE中创建XMLHttpRequest对象的方法。非IE浏览器中创建方法是new XmlHttpRequest()。为了兼容不同的浏览器需要编写很多代码,见备注。
回调函数中data参数为服务器返回的数据, textStatus为服务器返回状态码, textStatus为"success"表示成功。
JQuery中提供了简化ajax使用的方法。$.ajax()函数是JQuery中提供的ajax访问函数,$.post()是对$.ajax()的post方式提交ajax查询的封装, $.get()是对$.ajax()的get方式提交ajax查询的封装。推荐用post方式,因为post方式没有缓存的问题,演示,get方式中缓存处理的处理方法。代码见备注。todo:好像服务端异常404、500等回调方法并不会被调用。
如果有请求参数则在第二个参数用字典方式来设置。 $.post("GetDate1.ashx", {"id":"2"},function(data, textStatus) {。JQuery帮我们进行了URL编码,因此参数中有中文也不用担心。
请求、处理、响应!服务器返回给客户端的就是一堆文本。如果服务器Write一堆文本,客户端拿到的就是一堆文本,if(data=="true")。怎么样调试AJAX程序的错误。要看浏览器有没有向服务器发出请求。分别查看“请求、处理、响应”三个阶段。
AJAX程序排错技巧:先用HttpWatch看浏览器有没有向服务器发出请求,如果没有的话检查本地代码,如果有的话,看服务器的返回,如果服务器返回有异常,则通过调试的方式让程序在发生异常的点断住。如果服务器没有返回异常,接着看服务器返回的内容是不是期望的。调试AJAX程序最好的工具就是HttpWatch!!!
1、兼容浏览器的创建XMLHttp对象的方法
function CreateXmlHttp()
{
var xmlhttp;
//非IE浏览器创建XmlHttpRequest对象
if (window.XmlHttpRequest)
{
xmlhttp = new XmlHttpRequest();
}
//IE浏览器创建XmlHttpRequest对象
if (window.ActiveXObject)
{
try
{
xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
}
catch (e)
{
try
{
xmlhttp = new ActiveXObject("msxml2.XMLHTTP");
}
catch (ex) { alert("AJAX创建失败!");}
}
}
return xmlhttp;
}
2、JQuery的post封装
$.post("GetDate1.ashx", function(data, textStatus) {
$("#Button2").val(data);
});
请求参数:$.post("GetDate1.ashx", {"id":"2"},function(data, textStatus) {
Json
AJAX传递复杂数据如果自己进行格式定义的话会经历组装、解析的过程,因此AJAX中有一个事实上的数据传输标准JSon。Json(是一个标准,就像XML一样,Json规定了对象以什么样的格式保存为一个字符串)将复杂对象序列化为一个字符串,在浏览器端再将字符串反序列化为JavaScript可以读取的对象。看一下Json的格式。Json被几乎所有语言支持。
C#中将.Net对象序列化为Json字符串的方法:JavaScriptSerializer().Serialize(p), JavaScriptSerializer在System.Web.Extensions.dll中,是.Net3.x 中新增的类,如果在.Net2.0中则需要用第三方的组件。
JQuery AJAX得到的data是Json格式数据,用$.parseJSON(data)方法将JSon格式数据解析为JavaScript对象
可以在post函数最后一个函数传递"json"则data就是反序列化以后的对象,免去了parseJSON
微软AJAX解决方案(*)
ASP.Net中内置的简化AJAX开发的控件UpdatePanel
- 放入ScriptManager,将要实现AJAX效果的控件放到UpdatePanel中即可。
- UpdatePanel原理探秘,用HttpWatch看。
- 只把需要无刷新更新的部分放到UpdatePanel中。
- UpdatePanel用来实现一些对性能要求不高的需求非常方便。
- 从原理分析为什么在客户端把TextBox变红,AJAX请求以后又变白了。
Timer实现定时AJAX效果,原理分析。
UpdateProgress显示“正在加载数据”。
AJAX Toolkit简介。
全局文件
添加Web→全局应用程序类,注意文件名不要改。
- 全局文件是对Web应用声明周期的一个事件响应的地方
- 将Web应用启动时初始化的一些代码写到Application_Start中,比如后面讲的Log4Net的初始化等。应用关闭的时候Application_End调用。
- 当一个Session启动的时候Session_Start被调用,Session结束(用户主动退出或者超时结束)Session_End被调用。
- 当一个用户请求来的时候Application_BeginRequest方法被调用
- 当应用中出现未捕获异常,Application_Error被调用(常考,ASP.Net中的错误处理机制),用HttpContext.Current.Server.GetLastError()获得异常信息,然后用Log4Net记录到日志中。
使用案例:实现图片的防盗链,讨论;屏蔽指定的IP地址。
图片防盗链发过去一个错误图片同样浪费资源。
protected void Application_BeginRequest(object sender, EventArgs e) { var Request = HttpContext.Current.Request; if (Request.Url.PathAndQuery.StartsWith("/domDemo2/images/mm/"))//防止美女时钟的图片盗链 { //判断是否盗链 if (Request.UrlReferrer == null || !IsSameHost(Request.UrlReferrer, Request.Url)) { HttpContext.Current.Response.Write("请勿直接访问图片,请在美女时钟页面中访问!"); HttpContext.Current.Response.End(); } } } /// <summary> /// 判断uri1和uri2是否是在同一台主机上 /// </summary> /// <param name="uri1"></param> /// <param name="uri2"></param> /// <returns></returns> private static bool IsSameHost(Uri uri1, Uri uri2) { return Uri.Compare(uri1, uri2, UriComponents.Host, UriFormat.SafeUnescaped, StringComparison.CurrentCultureIgnoreCase) == 0; }
错误页
当页面发生错误的时候,ASP.Net会将错误信息展示出来(Sqlconnection的错误就能暴露连接字符串),这样一来不好看,二来会泄露网站的内部实现信息,给网站带来安全隐患,因此需要定制错误页,发生错误时显示开发人员定制的页面。404页面放点广告也是好的嘛。
配置web.config,配置customErrors区域:
<customErrors mode="On" defaultRedirect="MyErrorPage.aspx"> <error statusCode="403" redirect="NoAccess.htm" /> <error statusCode="404" redirect="FileNotFound.htm" /> </customErrors>
mode三个可选值:On:总是显示定制错误页面;Off:不显示定制错误界面,直接显示调用堆栈等异常信息;remoteonly:对于本机的访问显示调用堆栈等异常信息,对于外部用户的显示定制错误页面。一般设置为RemoteOnly,这样发生错误的话,管理员可以在服务器的浏览器中看详细错误信息,普通用户看不到。学习演示的时候mode设置为On,否则看不到定制页。可以在定义错误页中判断Request.UserHostAddress来设置某些ip看到异常信息,可以读取Session如果是管理员则可以看异常信息。
error子元素设定对于不同的状态码使用不同的错误页,很多网站都把404做一个特殊的错误页。没有单独设置的状态码错误则显示defaultRedirect中指定的页面。
错误页即可以使用htm页面,也可以使用aspx页面。在aspx页面中可以用HttpContext.Current.Server.GetLastError()拿到异常对象。一般不要把异常信息显示给用户,而是使用后面讲的Log4Net等将异常记录到系统日志。如果要在错误页面中拿到异常对象,比如customErrors中设置redirectMode="ResponseRewrite",因为默认是客户端重定向,在错误页面中就拿不到异常对象了。
URL重写(UrlRewrite)
为什么要URL重写?1、有利于SEO,带参数的URL权重较低;2、地址看起来更正规,推广uid。
伪静态:看起来像普通页面,而非动态生成的页面。
原理:在Global.asax的Application_BeginRequest 中读取Request.Url 得到请求的URL(View-3.aspx),然后用HttpContext.Current.RewritePath(ReWriteUrl)进行重写(也就是交由另外一个页面处理这个请求)(View.aspx?tid=3格式) http://www.cnblogs.com/hd/archive/2005/06/20/177633.html
也可以使用微软的URLRewrite,只要修改配置文件就可以进行URL重写。照着文档自学配置。
configSections下增加
<section name="RewriterConfig"type="URLRewriter.Config.RewriterConfigSerializerSectionHandler, URLRewriter" /> <configuration>中增加 <RewriterConfig> <Rules> <RewriterRule> <LookFor>~/ViewArticle-(\d+)\.aspx</LookFor> <SendTo>~/ViewArticle.aspx?Id=$1</SendTo> </RewriterRule> <RewriterRule> <LookFor>~/ArticleList-(\d+)\.aspx</LookFor> <SendTo>~/ArticleList.aspx?catId=$1</SendTo> </RewriterRule> </Rules> </RewriterConfig>
system.web下增加
<httpHandlers> <add verb="*" path="*.aspx"type="URLRewriter.RewriterFactoryHandler, URLRewriter" > </httpHandlers>