.NET011-ASP.NET MVC
1 http:// ASP.NET MVC
1.1 Get Started With http:// ASP.NET MVC
在未来的新项目开发多采用WebApi项目结构去开发,但由于 很多历史原因 ,有些老项目采用了MVC的方式去开发,这就涉及到 老项目的维护和升级迁移 ,势必需要开发人员能够读懂前人写的代码,故此写此文章记录 http:// ASP.NET MVC开发过程,以此来 帮助有此需求的开发人员能够快速掌握 。
- 创建 http:// ASP.NET Web 应用程序(.NET Framework)
- 选择MVC,取消勾选HTTPS配置
1.2 目录结构
- Connected Services: 里面会加载WCF和WebServices等一些扩展服务
- Properties-AssemblyInfo.cs: 里面会包含一些项目信息、dll应用信息等(发布项目总览)
- App_Data : 数据库脚本/数据库Backup
- App_Start: 包含了程序初始化需要的类
- Content: 保存css文件
- Scripts: 保存JS文件
- fonts: 保存字体文件
- Models : 存放实体对象
- Views : 表现层,和用户直接交互的界面
- Controllers : 控制器,控制业务逻辑,选择数据传输
- Global.asax : 全局配置文件,MVC程序启动是从这里开始
- packages.config : 包管理文件
- Web.config : MCV程序的配置文件
2 项目发布 IIS
2.1 本地部署
- 依赖Windows,依赖IIS(IIS作为服务器)
- 启用或关闭Windows功能
- IIS(Interface Information Service)勾选所有模块
2.2 Windows云服务部署
- 服务器管理器-IIS(可新建后选择IIS)
2.3 发布
- 打开IIS管理器
- 添加网站-路径选择指向 项目根目录 -配置端口(建议5000-10000之间)-配置主机名
- 查看应用程序池中是否有该应用
2.4 权限
- 打开网站所在的物理路径-属性-安全-添加编辑IUSR(完全控制权限)-添加编辑IIS_IUSRS(完全控制权限)
3 数据传值方式
3.1 传值代码编写
- 右键添加控制器 FirstController
- 在FirstController.cs代码中Index()方法,右键添加视图,修改<h2>标签里面的内容为 This is first index.
- 在控制器中写一些数据
public class FirstController : Controller
// GET: First
public ActionResult Index()
// 业务逻辑的计算
// 结果展示给VIEW
base.ViewBag.User1 = "yt01-008";
base.ViewBag.User2 = "yt01-009";
base.ViewData["company"] = "yt";
base.TempData["temp"] = "test";
base.HttpContext.Session["User"] = "httpcontext albertzhaoz";
object model = "model albertzhaoz";
return View(model);
}
- 在View中获取这些数据来源
需要注意的是HttpContext. Current .Session["User"]和Model要通过View(model)方法传递过去,且要定义model变量
@{
ViewBag.Title = "Index";
@model string
<h2>This is first index.</h2>
<!--这里面是VIEW层-->
<h3>ViewBag.User1:@ViewBag.User1</h3>
<h3>ViewBag.User2:@ViewBag.User2</h3>
<h3>ViewData:@ViewData["company"]</h3>
<h3>TempData:@TempData["temp"]</h3>
<h3>HttpContext:@HttpContext.Current.Session["User"]</h3>
<h3>model:@Model</ h3 >
3.2 传值类别
- ViewData
- ViewBag
- Model
- TempData
- HttpContext.Session
3.3 传值异常情况
- 值覆盖情况( 如果ViewBag和ViewData键一样的话,值会变更为最新的 )
base.ViewBag.User1 = "yt123";
base.ViewData["User1"] = yt123123";
<h3>ViewBag.User1:@ViewBag.User1</h3> 这里面的值会被覆盖,显示结果为yt123123
- Redirect重定向传值丢失问题,也可以用View("想跳转的方法名")
- model传值不要把数据类型定义成一个String类型,有重载方法会进行方法跳转。
public class FirstController : Controller
// GET: First
public ActionResult Index()
// 业务逻辑的计算
// 结果展示给VIEW
base.ViewBag.User1 = "yt01-008";
base.ViewBag.User2 = "yt01-009";
base.ViewData["company"] = "yt";
base.TempData["temp"] = "test";
base.HttpContext.Session["User"] = "httpcontext albertzhaoz";
object model = "model albertzhaoz";
base.ViewData["User1"] = "yt01-008008";
return View(model);
// 直接跳转到Albert界面,并将model传递过去
// return View("Albert",model);
public ActionResult Albert()
return View();
}
4 http:// ASP.NET MVC 集成日志 log4net
log4net 组件: 多种方式记录日志(控制台、文本、数据库等)
4.1 准备 log4net.config 配置文件
- Nuget 引入程序集 log4net
- 准备配置文件 log4net.config, 且配置文件的属性设置为始终复制。
<?xml version="1.0" encoding="utf-8"?>
<log4net>
<!--Define some output appenders-->
<appender name="rollingAppender" type="log4net.Appender.RollingFileAppender">
<file value="log\log.txt"/>
<!--追加日志内容-->
<appendToFile value="true"/>
<!--防止多线程时不能写Log,官方说线程非安全-->
<lockingModle type="log4net.Appender.FileAppender.FileAppender+MinimalLock"/>
<!--可以为Once|Size|Data|Composite-->
<!--Composite为Size和Data的组合-->
<rollingStyle value="Composite"/>
<!--当备份文件时,为文件名加后缀-->
<dataPattern value ="yyyyMMdd.txt"/>
<!--日志最大个数-->
<!--rollingStyle节点为Size时,只能有Value个日志-->
<!--rollingStyle节点为Composite时,每天有value个日志-->
<maxSizeRollBackups value="20"/>
<!--可用的单位:KB|MB|GB-->
<maximumFileSize value ="10MB"/>
<!--置为true时,当前最新日志文件名永远为file节点中的名字-->
<staticLogFileName value="true"/>
<!--输出级别在DEBUG和ERROR之间的日志-->
<!--levels:OFF>FATAL>ERROR>WARN>INFO>DEBUG>ALL-->
<filter type="log4net.Filter.LevelRangeFilter">
<param name="LevelMin" value="DEBUG"/>
<param name="LevelMax" value="ERROR"/>
</filter>
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %-5level %logger - %message%newline"/>
</layout>
</appender>
<!--levels:OFF>FATAL>ERROR>WARN>INFO>DEBUG>ALL-->
<priority value="ALL"/>
<level value="ALL"/>
<appender-ref ref="rollingAppender"/>
</root>
</log4net>
4.2 编写 LoggerHelper 帮助类
using log4net;
using log4net.Config;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Web;
namespace GetStartedWithASPNETMVC.Utility
public class LoggerHelper
private ILog loger = null;
/// <summary>
/// 静态构造函数(整个进程只执行一次-读取配置文件)
/// </summary>
static LoggerHelper()
XmlConfigurator.Configure(new FileInfo(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Configs\\log4net.config")));
ILog Log = LogManager.GetLogger(typeof(LoggerHelper));
Log.Info("系统初始化Logger模块");
public LoggerHelper(Type type)
loger = LogManager.GetLogger(type);
public void Error(string msg = "出现异常", Exception ex = null)
Console.WriteLine(msg);
loger.Error(msg, ex);
public void Warn(string msg)
Console.WriteLine(msg);
loger.Warn(msg);
public void Info(string msg)
Console.WriteLine(msg);
loger.Info(msg);
public void Debug(string msg)
Console.WriteLine(msg);
loger.Debug(msg);
}
4.3 使用 LoggerHelper
private readonly LoggerHelper _logger = new LoggerHelper(typeof(LogAfterBehavior));
public ActionResult TestLog4net()
_logger.Debug($"{typeof(FirstController).Name}-TestLog4net");
_logger.Warn($"{typeof(FirstController).Name}-TestLog4net");
_logger.Info($"{typeof(FirstController).Name}-TestLog4net");
_logger.Error($"{typeof(FirstController).Name}-TestLog4net");
return View();
}
4.4 记录日志到数据库中
注意点,在生产环境中,设置 <bufferSize value="100" /> ,不要去频繁写数据库。
// 在 Subaru 数据库中执行如下代码
CREATE TABLE [dbo].[ErrorLog](
[nId] [bigint] IDENTITY(1,1) NOT NULL,
[dtDate] [datetime] NOT NULL,
[sThread] [nvarchar](100) NOT NULL,
[sLevel] [nvarchar](200) NOT NULL,
[sLogger] [nvarchar](500) NOT NULL,
[sMessage] [nvarchar](3000) NOT NULL,
[sException] [nvarchar](4000) NULL)
// 在 log4net.config 中配置数据库连接字符串
<!--配置数据库-->
<appender name="ADONetAppender" type="log4net.Appender.ADONetAppender">
<bufferSize value="1" />
<connectionType value="System.Data.SqlClient.SqlConnection, System.Data, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<connectionString value="DATABASE=Subaru;SERVER=.;UID=sa;PWD=xxx.;Connect Timeout=15;" />
<commandText value="INSERT INTO ErrorLog ([dtDate],[sThread],[sLevel],[sLogger],[sMessage],[sException]) VALUES (@log_date, @thread, @log_level, @logger, @message, @exception)" />
<parameter>
<parameterName value="@log_date" />
<dbType value="DateTime" />
<layout type="log4net.Layout.RawTimeStampLayout" />
</parameter>
<parameter>
<parameterName value="@thread" />
<dbType value="String" />
<size value="100" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%t" />
</layout>
</parameter>
<parameter>
<parameterName value="@log_level" />
<dbType value="String" />
<size value="200" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%p" />
</layout>
</parameter>
<parameter>
<parameterName value="@logger" />
<dbType value="String" />
<size value="500" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%logger" />
</layout>
</parameter>
<parameter>
<parameterName value="@message" />
<dbType value="String" />
<size value="3000" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%m" />
</layout>
</parameter>
<parameter>
<parameterName value="@exception" />
<dbType value="String" />
<size value="4000" />
<layout type="log4net.Layout.ExceptionLayout" />
</parameter>
</appender>
<!--levels:OFF>FATAL>ERROR>WARN>INFO>DEBUG>ALL-->
<priority value="ALL"/>
<level value="ALL"/>
<appender-ref ref="rollingAppender"/>
<appender-ref ref="ADONetAppender" />
</root>
5 Global 区域
5.1 基本概念
Global.asax: 全局文件,是一个文本文件,提供全局可用代码,这些代码包括应用程序的事件处理以及会话事件、方法和静态变量。有时该文件也被称为应用程序文件。
Application_Start: 程序的入口,程序启动后从这里开始执行一次。(网站初始化)
namespace GetStartedWithASPNETMVC
public class MvcApplication : System.Web.HttpApplication
protected void Application_Start()
AreaRegistration.RegisterAllAreas();//注册区域
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);//注册全局Filter
RouteConfig.RegisterRoutes(RouteTable.Routes);//注册路由
BundleConfig.RegisterBundles(BundleTable.Bundles);//注册Bundles--引用JS/CS需要的组件
区域: 开发过程中,功能模块会越来越多(权限管理、用户管理等)。如果能够按照每一个模块的划分,进行 模块化开发 。
5.2 创建区域
- 右键-添加-新搭建的基架项目-MVC 5 区域( 每一个模块都可以对应一个MVC基架 ),每一个板块职责就更加清晰,组件化模块化开发。
6 路由
6.1 匹配路由
客户端发起请求--IIS服务器--程序集--路由匹配 RouteConfig.RegisterRoutes(RouteTable.Routes) 这里维护了一个路由表,是一个Collection<RouteBase>集合 --转发到匹配的控制器Action中去处理。
路由匹配先按照url路径来匹配,如果路径参数里面没有则会进入到default里面去匹配。 例如First/index,会默认匹配到default路由表({controller}/{action}。 如果First/Albert_2022_02_33,首先会匹配default路由,在First控制器中,查找Albert(带着参数2022,02,33)方法,匹配到AlbertDate方法中。
/// <summary>
/// 路由规则的申明
/// 路由匹配规则:去掉Url协议部分,IP,端口号
/// </summary>
/// <param name="routes"></param>
public static void RegisterRoutes(RouteCollection routes)
// 忽略路由
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
// 路由规则通过Key Value存储
// name-Key url-Value(通过正则表达式匹配)
// 如果申明多个路由,name名称不能重复
// 显示参数,声明传递参数直接明了
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
routes.MapRoute(
name: "Albert",
url: "{controller}/{action}_{year}_{month}_{day}/{id}",
defaults: new { controller = "Home", action = "Albert", id = UrlParameter.Optional },
// 这边对上面路由规则进行了限定,year只占四位,month只占2位,day只占2位
constraints:new{year=@"\d{4}",month=@"\d{2},day=@"\d{2}",}
}
6.2 忽略路由
// 忽略路由,遇到xxx.axd/xxx就不走路由匹配
// 这个忽略有历史原因,在Webform开发时代,IIS是通过后缀转发,在MVC时代微软先打了一个补丁,凡是MVC请求则创建一个axd,后面IIS升级可以直接路由转发,但巨硬擅长并愿意兼容,则没有删除那个补丁,现在就遗留了这个忽略。
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
7 MVC 插件式开发
7.1 插件式开发
可以将控制器部分独立开,独立开之后可以直接放到单独的类库项目中,这样便可以做到 独立模块开发 ,和 热插拔(不需要停止服务器,可以把新的插件给发布出去,需要做一个触发增量初始化的动作,将路由规则和控制器扫描进去) 。
- 创建一个类库项目GetStartedWithASPNETMVC_Plugin
- 创建类ReadFileController,继承Controller (添加引用,引用地址在GetStartedWithASPNETMVC项目下packages\Microsoft.AspNet.Mvc.5.2.7\lib\net45\System.Web.Mvc.dll)
- 在ReadFileController中写Action方法,这里需要注意的 View按照名称 来查找 ("~/Views/ReadFile/ ReadTxtFile.cshtml") ,查找更目录下的Views/ReadFile/ ReadTxtFile.cshtml
public class ReadFileController: Controller
public ActionResult ReadTxtFile()
string path = @"F:\Code_Repo\03_Test\PowerAutomate\GithubRepo.txt";
var content = System.IO.File.ReadAllText(path);
ViewBag.content = content;
//跳转到根目录下的Views/ReadFile/ReadTxtFile.cshtml文件中
return View("~/Views/ReadFile/ ReadTxtFile.cshtml");
}
- 用GetStartedWithASPNETMVC项目引用GetStartedWithASPNETMVC_Plugin项目
- 在GetStartedWithASPNETMVC中增加一个View,ReadFile/ReadTxtFile.cshtml
@{
ViewBag.Title = "ReadTxtFile";
<h2>我是来自插件ReadFile</h2>
<h2>ViewBag的内容:@ViewBag.content</h2>
- 启动项目,输入ReadFile/ReadTxtFile则可以进入到ReadTxtFile.cshtml中。
7.2 热插拔实现
基于ASP.NET MVC的热插拔模块式开发框架(OrchardNoCMS)--模块开发
8 Razor 语法
cshtml既可以写html,也可以写后台代码。用@{我是代码部分} 中间不可以有空格 。
注意嵌套View的使用,Html.RenderPartial(Html.Partial)。
使用Action:用于将其他视图嵌套过来,经过Action传递。( 如果后台方法加了 ChildActionOnly 特性,只能被子请问访问,不能独立访问。 )
@{
ViewBag.Title = "Index";
<h2>This is second Index</h2>
<a href="http://www.baidu.com">百度一下</a>
<p>我是新的一行</p>
@{ int i = 3; int y = 4;}
@if(i>2){
<a href="http://www.google.com">我是Google</a>
@for(int k=0;k<10;k++)
<--!这里需要注意的是href标签进行跳转要指明相关协议加上http或https,不然无法显示-->
<a href="http://www.baidu.com">这是连接@k</a>
<!--使用partial页:用于将其他视图嵌套过来-->
<!--第一个参数是视图名称,第二个参数通过model传过去-->
<h2>我是页面嵌套</h2>
@{ Html.RenderPartial("ChildAction","Timonthy");}
<!--使用Action:用于将其他视图嵌套过来,经过Action传递-->
<!--Action:返回的是字符串,放入当前位置,需要经过action处理。RenderAction在指定位置添加一个view,返回void,需放入大括号中,需经过action处理-->
@{Html.Action("ChildAction", "Second",new {name = "Timonthy"});}
<!--Action内部跳转嵌套-->
<h2>我是内部跳转Action,First param is aciton name,second param is controller name,third pamara is input param</h2>
@{ Html.RenderAction("ChildAction", "Second",new {name = "Timonthy"});}
<!--这种传值是页面一次性请求完成的,跟ajax不一样-->
@section scripts{
<script>
$(function(){
albert(12345)
</script>
}
9 Razor 扩展
各种Html标签都可以通过后台语言来实现。
- 创建类库项目-GetStartedWithAspnetHelper
- 编写扩展方法
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Web.Mvc;
namespace System.Web.Mvc
public static class AlbertHtmlHelper
public static MvcHtmlString Br(this HtmlHelper helper)
var builder = new TagBuilder("br");
return MvcHtmlString.Create(builder.ToString(TagRenderMode.SelfClosing));
}
- 在View中调用Br()方法
@{
ViewBag.Title = "RazorExtend";
<h2>RazorExtend</h2>
@Html.Br() 等价于<br/>
<h2>测试</h2>
@Html.ActionLink(linkText,actionName) <a href=actionName>linkText</a>
@Html.ActionLink("带控制器","ActionName","ControllerName") <a href="../ControllerName/RazorShow">带控制器</a>
@Html.ActiornLink("带路由信息","ActionName",new {id=1,name=3,age=4,height=5}) <a href="Html/ActionName/1?name=3&age=4&height=5>带路由信息</a>
10 页面模板
新建项目中,默认页面展示其实是分为三层:Header、Body、Foot。请求不同页面只是Body在变化。
使用了_ViewStart.cshtml,使用了Layout跳转到了_Layout.cshtml。Layout引入了模板页。
@RenderBody() 则是加载其他页面进入,是一个区域占位符。
每个页面都需要使用的组件或元素则可以在母版页中定义。
@{
Layout = "~/Views/Shared/_Layout.cshtml";
<!DOCTYPE html>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>@ViewBag.Title - 我的 ASP.NET 应用程序</title>
@Styles.Render("~/Content/css")
@Scripts.Render("~/bundles/modernizr")
</head>
<div class="navbar navbar-inverse navbar-fixed-top">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse" title="more options">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
@Html.ActionLink("应用程序名称", "Index", "Home", new { area = "" }, new { @class = "navbar-brand" })
<div class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<li>@Html.ActionLink("Home", "Index", "Home")</li>
<li>@Html.ActionLink("关于", "About", "Home")</li>
<li>@Html.ActionLink("联系方式", "Contact", "Home")</li>
<div class="container body-content">
@RenderBody()
<footer>
<p>© @DateTime.Now.Year - 我的 ASP.NET 应用程序</p>
</footer>
@Scripts.Render("~/bundles/jquery")
@Scripts.Render("~/bundles/bootstrap")
@RenderSection("scripts", required: false)
</body>
</html>
11 JS/CSS 压缩
- 打开母版页文件 _Layout.cshtml
- 打开JS脚本目录 Scripts/xxx,鼠标直接拖拽到_Layout.cshtml中,则可以直接使用。
- App_Start/BundleConfig.cs中绑定了JS和CS,和RouterConfig.cs绑定路由一样,在项目启动的时候会扫描当前项目的JS和CS文件,并注入其中。
- new ScriptBundle(虚拟名称).Include(JS实际路径) 在页面中调用时,只需要调用一个则实现了调用多个目的。 捆绑调用。
- new StyleBundle也是捆绑调用
- 可以将css和Js压缩体积更小,降低加载css和js消耗。
- 调用的时候,直接加载虚拟名称就可
@Styles.Render("~/Content/css")
@Scripts.Render("~/bundles/modernizr")
- 如果向提高一些性能,将JS加载放在最后。参考_Layout.cshtml文件最后。
using System.Web;
using System.Web.Optimization;
namespace GetStartedWithASPNETMVC
public class BundleConfig
// 有关捆绑的详细信息,请访问 https://go.microsoft.com/fwlink/?LinkId=301862
public static void RegisterBundles(BundleCollection bundles)
bundles.Add(new ScriptBundle("~/bundles/jquery").Include(
"~/Scripts/jquery-{version}.js"));
bundles.Add(new ScriptBundle("~/bundles/jqueryval").Include(
"~/Scripts/jquery.validate*"));
// 使用要用于开发和学习的 Modernizr 的开发版本。然后,当你做好
// 生产准备就绪,请使用 https://modernizr.com 上的生成工具仅选择所需的测试。
bundles.Add(new ScriptBundle("~/bundles/modernizr").Include(
"~/Scripts/modernizr-*"));
bundles.Add(new ScriptBundle("~/bundles/bootstrap").Include(
"~/Scripts/bootstrap.js"));
bundles.Add(new StyleBundle("~/Content/css").Include(
"~/Content/bootstrap.css",
"~/Content/site.css"));
}
- @RenderSection("scripts", required: false) 可以替换section脚本
@section scripts{
<script>
$(function(){
albert(12345)
</script>
}
12 MVC 连接数据库做数据交互-EF6
- 创建类库项目GetStartedWithASPNET_EF
- 新建 http:// ADO.NET 实体数据模型 SubaruDbContext --来自数据库的Code First--填写对应的信息--生成数据库对应的实体类和DbContext
- 在GetStartedWithASPNETMVC项目中添加EFController.cs(需要引用EF Nuget),测试数据库增删改查。
- 拷贝GetStartedWithASPNET_EF项目中的app.config文件中数据库连接字符串到web.config中configuration中
namespace GetStartedWithASPNETMVC.Controllers
public class EFController : Controller
// GET: EF
public ActionResult Index()
using (SubaruDbContext subaruDbContext = new SubaruDbContext())
var online = subaruDbContext.Online.FirstOrDefault();
ViewBag.Online_Last_Station = online.Last_Station;
return View();
}
13 分层架构
13.1 UI层(MVC)
namespace GetStartedWithASPNETMVC.Controllers
public class EFController : Controller
// GET: EF
public ActionResult Index()
//using (SubaruDbContext subaruDbContext = new SubaruDbContext())
// var online = subaruDbContext.Online.Where(a=>a.ID == 4).FirstOrDefault();
// ViewBag.Online_Last_Station = online.Last_Station;
IOnlineService onlineService = new OnlineService(new SubaruDbContext());
var firstOnline = onlineService.Find<Online>(6);
ViewBag.Online_Last_Station = firstOnline.Last_Station;
return View();
}
13.2 业务逻辑层:一个接口,一个实现
- 创建GetStartedWithASPNETMVC_Interface项目
namespace GetStartedWithASPNETMVC_Interface
public interface IBaseService : IDisposable//是为了释放Context
#region Query
/// <summary>
/// 根据id查询实体
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
T Find<T>(int id) where T : class;
/// <summary>
/// 提供对单表的查询
/// </summary>
/// <returns>IQueryable类型集合</returns>
[Obsolete("尽量避免使用,using 带表达式目录树的 代替")]
IQueryable<T> Set<T>() where T : class;
/// <summary>
/// 查询
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="funcWhere"></param>
/// <returns></returns>
IQueryable<T> Query<T>(Expression<Func<T, bool>> funcWhere) where T : class;
#endregion
#region Add
/// <summary>
/// 新增数据,即时Commit
/// </summary>
/// <param name="t"></param>
/// <returns>返回带主键的实体</returns>
T Insert<T>(T t) where T : class;
/// <summary>
/// 新增数据,即时Commit
/// 多条sql 一个连接,事务插入
/// </summary>
/// <param name="tList"></param>
IEnumerable<T> Insert<T>(IEnumerable<T> tList) where T : class;
#endregion
#region Update
/// <summary>
/// 更新数据,即时Commit
/// </summary>
/// <param name="t"></param>
void Update<T>(T t) where T : class;
/// <summary>
/// 更新数据,即时Commit
/// </summary>
/// <param name="tList"></param>
void Update<T>(IEnumerable<T> tList) where T : class;
#endregion
#region Delete
/// <summary>
/// 根据主键删除数据,即时Commit
/// </summary>
/// <param name="t"></param>
void Delete<T>(int Id) where T : class;
/// <su+mary>
/// 删除数据,即时Commit
/// </summary>
/// <param name="t"></param>
void Delete<T>(T t) where T : class;
/// <summary>
/// 删除数据,即时Commit
/// </summary>
/// <param name="tList"></param>
void Delete<T>(IEnumerable<T> tList) where T : class;
#endregion
#region Other
/// <summary>
/// 立即保存全部修改
/// 把增/删的savechange给放到这里,是为了保证事务的
/// </summary>
void Commit();
/// <summary>
/// 执行sql 返回集合
/// </summary>
/// <param name="sql"></param>
/// <param name="parameters"></param>
/// <returns></returns>
IQueryable<T> ExcuteQuery<T>(string sql, SqlParameter[] parameters) where T : class;
/// <summary>
/// 执行sql,无返回
/// </summary>
/// <param name="sql"></param>
/// <param name="parameters"></param>
void Excute<T>(string sql, SqlParameter[] parameters) where T : class;
#endregion
namespace GetStartedWithASPNETMVC_Interface
public interface ISubaruService:IBaseService
}
- 创建GetStartedWithASPNETMVC_Serivce项目
namespace GetStartedWithASPNETMVC_Services
public class BaseService:IBaseService
#region Identity
protected DbContext Context { get; private set; }
/// <summary>
/// 构造函数注入
/// </summary>
/// <param name="context"></param>
public BaseService(DbContext context)
this.Context = context;
#endregion Identity
#region Query
public T Find<T>(int id) where T : class
return this.Context.Set<T>().Find(id);
/// <summary>
/// 不应该暴露给上端使用者,尽量少用
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
[Obsolete("尽量避免使用,using 带表达式目录树的代替")]
public IQueryable<T> Set<T>() where T : class
return this.Context.Set<T>();
/// <summary>
/// 这才是合理的做法,上端给条件,这里查询
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="funcWhere"></param>
/// <returns></returns>
public IQueryable<T> Query<T>(Expression<Func<T, bool>> funcWhere) where T : class
if (funcWhere == null)
return this.Context.Set<T>();
return this.Context.Set<T>().Where<T>(funcWhere);
#endregion
#region Insert
/// <summary>
/// 即使保存 不需要再Commit
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="t"></param>
/// <returns></returns>
public T Insert<T>(T t) where T : class
this.Context.Set<T>().Add(t);
this.Commit();//写在这里 就不需要单独commit 不写就需要
return t;
public IEnumerable<T> Insert<T>(IEnumerable<T> tList) where T : class
this.Context.Set<T>().AddRange(tList);
this.Commit();//一个链接 多个sql
return tList;
#endregion
#region Update
/// <summary>
/// 是没有实现查询,直接更新的,需要Attach和State
/// 如果是已经在context,只能再封装一个(在具体的service)
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="t"></param>
public void Update<T>(T t) where T : class
if (t == null) throw new Exception("t is null");
this.Context.Set<T>().Attach(t);//将数据附加到上下文,支持实体修改和新实体,重置为UnChanged
this.Context.Entry<T>(t).State = EntityState.Modified;
this.Commit();//保存 然后重置为UnChanged
public void Update<T>(IEnumerable<T> tList) where T : class
foreach (var t in tList)
this.Context.Set<T>().Attach(t);
this.Context.Entry<T>(t).State = EntityState.Modified;
this.Commit();
#endregion
#region Delete
/// <summary>
/// 先附加 再删除
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="t"></param>
public void Delete<T>(T t) where T : class
if (t == null) throw new Exception("t is null");
this.Context.Set<T>().Attach(t);
this.Context.Set<T>().Remove(t);
this.Commit();
/// <summary>
/// 还可以增加非即时commit版本的,
/// 做成protected
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="Id"></param>
public void Delete<T>(int Id) where T : class
T t = this.Find<T>(Id);//也可以附加
if (t == null) throw new Exception("t is null");
this.Context.Set<T>().Remove(t);
this.Commit();
public void Delete<T>(IEnumerable<T> tList) where T : class
foreach (var t in tList)
this.Context.Set<T>().Attach(t);
this.Context.Set<T>().RemoveRange(tList);
this.Commit();
#endregion
#region Other
public void Commit()
this.Context.SaveChanges();
public IQueryable<T> ExcuteQuery<T>(string sql, SqlParameter[] parameters) where T : class
return this.Context.Database.SqlQuery<T>(sql, parameters).AsQueryable();
public void Excute<T>(string sql, SqlParameter[] parameters) where T : class
DbContextTransaction trans = null;
trans = this.Context.Database.BeginTransaction();
this.Context.Database.ExecuteSqlCommand(sql, parameters);
trans.Commit();
catch (Exception ex)
if (trans != null)
trans.Rollback();
throw ex;
public virtual void Dispose()
if (this.Context != null)
this.Context.Dispose();
#endregion
namespace GetStartedWithASPNETMVC_Services
public class SubaruService: BaseService,ISubaruService
}
13.3 数据访问层EF
代码见12 MVC连接数据库
14 IOC
在分层架构以后,为了做到层与层之间的解耦,需要依赖注入,就会使用到IOC容器了,在MVC框架中Unity是比较著名的IOC容器框架。
14.1 准备代码
- 准备三个接口,三个实现类
public interface ITestServiceA
void Print();
public interface ITestServiceB
void Print();
public interface ITestServiceC
void Print();
internal class TestServiceA : ITestServiceA
public void Print()
Console.WriteLine(this.GetType().FullName);
xxx
14.2 安装Nuget包:Unity
- 使用Unity(用微软官方的DI也可以,使用参见06 依赖注入DI文章)
// 实例化
IUnityContainer container = new UnityContainer();
// 注册接口和对象绑定关系
container.RegisterType<ITestServiceA,TestServiceA>();
// 使用接口得到对象
var testServiceA = container.Resolve<ITestServiceA>();
14.3 扩展方法优化
- 使用扩展方法优化,TestServiceExtensions
注意名称空间为Unity,扩展IUnityContainer的方法,这样就不需要在主代码里面进行接口的注册了。
namespace Unity
public static class TestServiceExtension
public static void AddTestServiceB(this IUnityContainer container)
container.RegisterType<ITestServiceA, TestServiceA>();
container.RegisterType<ITestServiceB, TestServiceB>();
container.RegisterType<ITestServiceC, TestServiceC>();
}
14.4 扩展方法使用
static void Main(string[] args)
var container = new UnityContainer();
// 注入TestServiceA、TestServiceB、TestServiceC
container.AddTestServiceB();
var testServiceA = container.Resolve<ITestServiceA>();
var testServiceB = container.Resolve<ITestServiceB>();
var testServiceC = container.Resolve<ITestServiceC>();
testServiceA.Print();
testServiceB.Print();
testServiceC.Print();
Console.ReadLine();
}
15 IOC容器整合MVC
15.1 引用Nuget
- GetStartedWithASPMVC项目-Nuget引入Unity、Unity.Configuration
15.2 准备一些代码
- 数据层 Model First : GetStartedWithASPNETMVC_EF.SubaruDbContext
using System;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity;
using System.Linq;
namespace GetStartedWithASPNETMVC_EF
public partial class SubaruDbContext : DbContext
public SubaruDbContext()
: base("name=SubaruDbContext")
public virtual DbSet<Offline> Offline { get; set; }
public virtual DbSet<Online> Online { get; set; }
public virtual DbSet<Rework> Rework { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
modelBuilder.Entity<Offline>()
.Property(e => e.ProductType)
.IsUnicode(false);
modelBuilder.Entity<Online>()
.Property(e => e.ProductType)
.IsUnicode(false);
modelBuilder.Entity<Rework>()
.Property(e => e.ProductType)
.IsUnicode(false);
- 配置接口和实现类
接口: GetStartedWithASPNETMVC_Interface.IBaseService、GetStartedWithASPNETMVC_Interface.IOnlineService
using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks;
namespace GetStartedWithASPNETMVC_Interface
public interface IBaseService : IDisposable//是为了释放Context
#region Query
/// <summary>
/// 根据id查询实体
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
T Find<T>(int id) where T : class;
/// <summary>
/// 提供对单表的查询
/// </summary>
/// <returns>IQueryable类型集合</returns>
[Obsolete("尽量避免使用,using 带表达式目录树的 代替")]
IQueryable<T> Set<T>() where T : class;
/// <summary>
/// 查询
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="funcWhere"></param>
/// <returns></returns>
IQueryable<T> Query<T>(Expression<Func<T, bool>> funcWhere) where T : class;
#endregion
#region Add
/// <summary>
/// 新增数据,即时Commit
/// </summary>
/// <param name="t"></param>
/// <returns>返回带主键的实体</returns>
T Insert<T>(T t) where T : class;
/// <summary>
/// 新增数据,即时Commit
/// 多条sql 一个连接,事务插入
/// </summary>
/// <param name="tList"></param>
IEnumerable<T> Insert<T>(IEnumerable<T> tList) where T : class;
#endregion
#region Update
/// <summary>
/// 更新数据,即时Commit
/// </summary>
/// <param name="t"></param>
void Update<T>(T t) where T : class;
/// <summary>
/// 更新数据,即时Commit
/// </summary>
/// <param name="tList"></param>
void Update<T>(IEnumerable<T> tList) where T : class;
#endregion
#region Delete
/// <summary>
/// 根据主键删除数据,即时Commit
/// </summary>
/// <param name="t"></param>
void Delete<T>(int Id) where T : class;
/// <su+mary>
/// 删除数据,即时Commit
/// </summary>
/// <param name="t"></param>
void Delete<T>(T t) where T : class;
/// <summary>
/// 删除数据,即时Commit
/// </summary>
/// <param name="tList"></param>
void Delete<T>(IEnumerable<T> tList) where T : class;
#endregion
#region Other
/// <summary>
/// 立即保存全部修改
/// 把增/删的savechange给放到这里,是为了保证事务的
/// </summary>
void Commit();
/// <summary>
/// 执行sql 返回集合
/// </summary>
/// <param name="sql"></param>
/// <param name="parameters"></param>
/// <returns></returns>
IQueryable<T> ExcuteQuery<T>(string sql, SqlParameter[] parameters) where T : class;
/// <summary>
/// 执行sql,无返回
/// </summary>
/// <param name="sql"></param>
/// <param name="parameters"></param>
void Excute<T>(string sql, SqlParameter[] parameters) where T : class;
#endregion
namespace GetStartedWithASPNETMVC_Interface
public interface IOnlineService:IBaseService
}
实现类: GetStartedWithASPNETMVC_Services.BaseService、 GetStartedWithASPNETMVC_Services.OnlineService
using GetStartedWithASPNETMVC_Interface;
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Data.SqlClient;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks;
namespace GetStartedWithASPNETMVC_Services
public class BaseService:IBaseService
#region Identity
// 依赖注入注入进去
protected DbContext Context { get; private set; }
/// <summary>
/// 构造函数注入
/// </summary>
/// <param name="context"></param>
public BaseService(DbContext context)
this.Context = context;
#endregion Identity
#region Query
public T Find<T>(int id) where T : class
return this.Context.Set<T>().Find(id);
/// <summary>
/// 不应该暴露给上端使用者,尽量少用
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
[Obsolete("尽量避免使用,using 带表达式目录树的代替")]
public IQueryable<T> Set<T>() where T : class
return this.Context.Set<T>();
/// <summary>
/// 这才是合理的做法,上端给条件,这里查询
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="funcWhere"></param>
/// <returns></returns>
public IQueryable<T> Query<T>(Expression<Func<T, bool>> funcWhere) where T : class
if (funcWhere == null)
return this.Context.Set<T>();
return this.Context.Set<T>().Where<T>(funcWhere);
#endregion
#region Insert
/// <summary>
/// 即使保存 不需要再Commit
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="t"></param>
/// <returns></returns>
public T Insert<T>(T t) where T : class
this.Context.Set<T>().Add(t);
this.Commit();//写在这里 就不需要单独commit 不写就需要
return t;
public IEnumerable<T> Insert<T>(IEnumerable<T> tList) where T : class
this.Context.Set<T>().AddRange(tList);
this.Commit();//一个链接 多个sql
return tList;
#endregion
#region Update
/// <summary>
/// 是没有实现查询,直接更新的,需要Attach和State
/// 如果是已经在context,只能再封装一个(在具体的service)
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="t"></param>
public void Update<T>(T t) where T : class
if (t == null) throw new Exception("t is null");
this.Context.Set<T>().Attach(t);//将数据附加到上下文,支持实体修改和新实体,重置为UnChanged
this.Context.Entry<T>(t).State = EntityState.Modified;
this.Commit();//保存 然后重置为UnChanged
public void Update<T>(IEnumerable<T> tList) where T : class
foreach (var t in tList)
this.Context.Set<T>().Attach(t);
this.Context.Entry<T>(t).State = EntityState.Modified;
this.Commit();
#endregion
#region Delete
/// <summary>
/// 先附加 再删除
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="t"></param>
public void Delete<T>(T t) where T : class
if (t == null) throw new Exception("t is null");
this.Context.Set<T>().Attach(t);
this.Context.Set<T>().Remove(t);
this.Commit();
/// <summary>
/// 还可以增加非即时commit版本的,
/// 做成protected
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="Id"></param>
public void Delete<T>(int Id) where T : class
T t = this.Find<T>(Id);//也可以附加
if (t == null) throw new Exception("t is null");
this.Context.Set<T>().Remove(t);
this.Commit();
public void Delete<T>(IEnumerable<T> tList) where T : class
foreach (var t in tList)
this.Context.Set<T>().Attach(t);
this.Context.Set<T>().RemoveRange(tList);
this.Commit();
#endregion
#region Other
public void Commit()
this.Context.SaveChanges();
public IQueryable<T> ExcuteQuery<T>(string sql, SqlParameter[] parameters) where T : class
return this.Context.Database.SqlQuery<T>(sql, parameters).AsQueryable();
public void Excute<T>(string sql, SqlParameter[] parameters) where T : class
DbContextTransaction trans = null;
trans = this.Context.Database.BeginTransaction();
this.Context.Database.ExecuteSqlCommand(sql, parameters);
trans.Commit();
catch (Exception ex)
if (trans != null)
trans.Rollback();
throw ex;
public virtual void Dispose()
if (this.Context != null)
this.Context.Dispose();
#endregion
namespace GetStartedWithASPNETMVC_Services
public class OnlineService : BaseService, IOnlineService
public OnlineService(DbContext context) : base(context)
}
15.3 读取unity.config并Resolve出服务类
- 在控制器中写如下代码,前面很多配置都是读取,读取配置文件中albertcontainer容器配置。通过container.Resolve<T>()解析出所需的类。
public ActionResult UnityIndex()
// Unity在MVC中的应用
ExeConfigurationFileMap fileMap = new ExeConfigurationFileMap();
fileMap.ExeConfigFilename = Path.Combine(AppDomain.CurrentDomain.BaseDirectory + "Configs\\unity.config");
Configuration configuration = ConfigurationManager.OpenMappedExeConfiguration(fileMap, ConfigurationUserLevel.None);
UnityConfigurationSection section = (UnityConfigurationSection)configuration.GetSection(UnityConfigurationSection.SectionName);
IUnityContainer container = new UnityContainer();
section.Configure(container, "albertcontainer");
IOnlineService service = container.Resolve<IOnlineService>();
var online = service.Find<Online>(7);
base.ViewBag.Last_Result = online.Last_Result;
return View();
}
15.4 重构读取配置,单例模式静态构造优化性能
- 重构这块,使用单例模式,静态构造函数实现单例化。 在Unity目录下创建DIFoctroyHelper
using Microsoft.Practices.Unity.Configuration;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.IO;
using System.Linq;
using System.Web;
using Unity;
namespace GetStartedWithASPNETMVC.Utility
public class DIFactoryHelper
private static IUnityContainer container = new UnityContainer();
static DIFactoryHelper()
// Unity在MVC中的应用
ExeConfigurationFileMap fileMap = new ExeConfigurationFileMap();
fileMap.ExeConfigFilename = Path.Combine(AppDomain.CurrentDomain.BaseDirectory + "Configs\\unity.config");
Configuration configuration = ConfigurationManager.OpenMappedExeConfiguration(fileMap, ConfigurationUserLevel.None);
UnityConfigurationSection section = (UnityConfigurationSection)configuration.GetSection(UnityConfigurationSection.SectionName);
container = new UnityContainer();
section.Configure(container, "albertcontainer");
public static IUnityContainer GetContainer()
return container;
// 使用DIFactoryHelper类
namespace GetStartedWithASPNETMVC.Controllers
public class EFController : Controller
private readonly DIFactoryHelper dIFactoryHelper = new DIFactoryHelper();
public ActionResult UnityIndex()
// Unity在MVC中的应用
IOnlineService service = DIFactoryHelper.GetContainer().Resolve<IOnlineService>();
var online = service.Find<Online>(7);
base.ViewBag.Last_Result = online.Last_Result;
return View();
}
15.5 配置文件
- 在Configs文件夹(上面log4net.config创建过)添加unity.config
注册格式为 名称空间.类/接口名,类/接口名-->名称空间.具体类名,具体类名
<configuration>
<configSections>
<section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection, Unity.Configuration"/>
</configSections>
<unity>
<containers>
<container name="albertcontainer">
<!--接口(名称空间.类/接口,接口名),实现类-->
<register type="System.Data.Entity.DbContext, EntityFramework" mapTo="GetStartedWithASPNETMVC_EF.SubaruDbContext, GetStartedWithASPNETMVC_EF"/>
<register type="GetStartedWithASPNETMVC_Interface.IOnlineService,GetStartedWithASPNETMVC_Interface"
mapTo="GetStartedWithASPNETMVC_Services.OnlineService, GetStartedWithASPNETMVC_Services"/>
</container>
</containers>
</unity>
</configuration>
16Unity IOC支持AOP扩展
AOP面向切面编程:在不修改之前业务逻辑的情况下,动态增加功能。可以在执行某个行为之前做点什么事?Unity 支持 AOP。
16.1 Nuget 引入包
Unity.Interception 和 Unity.Interception.Configuration
16.2 增加配置文件节点
- 增加配置文件节点 第2行和第5行
<unity>
<sectionExtension type="Microsoft.Practices.Unity.InterceptionExtension.Configuration.InterceptionConfigurationExtension, Unity.Interception.Configuration"/>
<containers>
<container name="albertcontainer">
<extension type="Interception"/>
<!--接口(名称空间.类/接口,接口名),实现类-->
<register type="System.Data.Entity.DbContext, EntityFramework" mapTo="GetStartedWithASPNETMVC_EF.SubaruDbContext, GetStartedWithASPNETMVC_EF"/>
<register type="GetStartedWithASPNETMVC_Interface.IOnlineService,GetStartedWithASPNETMVC_Interface"
mapTo="GetStartedWithASPNETMVC_Services.OnlineService, GetStartedWithASPNETMVC_Services"/>
</register>
</container>
</containers>
</unity>
16.3 增加AOP相关操作类
- 增加AOP动作(创建AOP文件夹,增加AOP相关类)增加一个LogAfterBehavior,继承自IInterceptionBehavior
using GetStartedWithASPNETMVC.Utility;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Web;
using Unity.Interception.InterceptionBehaviors;
using Unity.Interception.PolicyInjection.Pipeline;
namespace GetStartedWithASPNETMVC.AOP
public class LogAfterBehavior : IInterceptionBehavior
//private bool willExecute;
//public bool WillExecute
// get { return willExecute; }
// set { willExecute = true; }
public bool WillExecute => true;
//在执行数据库查询之后,读取一些文件内容,记录到数据库中。
private readonly Logger logger = new Logger(typeof(LogAfterBehavior));
public IEnumerable<Type> GetRequiredInterfaces()
return Type.EmptyTypes;
public IMethodReturn Invoke(IMethodInvocation input, GetNextInterceptionBehaviorDelegate getNext)
// 原先方法之后执行
Console.WriteLine($"{input.MethodBase.Name} 执行之执行前....");
// 执行原先方法
IMethodReturn methodReturn = getNext()(input, getNext);
// 原先方法之前执行
// 先从App_Data中读取文本,然后写入到sql数据库中
var sqlContent = File.ReadAllText(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "App_Data\\CreateErrorLog.md"));
logger.Info($"在执行{input.MethodBase.Name}方法之前执行了读取文本操作,并记录到数据库中");
logger.Debug(sqlContent);
return methodReturn;
}
16.4 根据操作类增加配置
- 配置文件增加信息, 第14行,第15行。在依赖注入调用这个服务的时候,可以在之前执行一些代码,也可以在之后执行一些代码。
<configuration>
<configSections>
<section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection, Unity.Configuration"/>
</configSections>
<unity>
<sectionExtension type="Microsoft.Practices.Unity.InterceptionExtension.Configuration.InterceptionConfigurationExtension, Unity.Interception.Configuration"/>
<containers>
<container name="albertcontainer">
<extension type="Interception"/>
<!--接口(名称空间.类/接口,接口名),实现类-->
<register type="System.Data.Entity.DbContext, EntityFramework" mapTo="GetStartedWithASPNETMVC_EF.SubaruDbContext, GetStartedWithASPNETMVC_EF"/>
<register type="GetStartedWithASPNETMVC_Interface.IOnlineService,GetStartedWithASPNETMVC_Interface"
mapTo="GetStartedWithASPNETMVC_Services.OnlineService, GetStartedWithASPNETMVC_Services">
<interceptor type="InterfaceInterceptor"/>
<interceptionBehavior type="GetStartedWithASPNETMVC.AOP.LogAfterBehavior, GetStartedWithASPNETMVC"/>
</register>
</container>
</containers>
</unity>
</configuration>
17 控制器支持构造函数注入
17.1 控制器实例化源码剖析
如何做到控制器支持构造函数注入? IOC容器用来创建对象的实例(依赖注入)
如果能通过使用IOC容器来创建控制器对象实例,那么容器中的各个服务将可以直接可以通过构造函数注入。
阅读System.Web.MVC.dll源码剖析 MVCHandler:发现MVC控制器其实是通过一个控制器工厂创建出来的(通过获取默认的DefaultControllerFactory)
factory = ControllerBuilder.GetControllerFactory();
controller = factory.CreateController(RequestContext,requiredString);
如果我们换一个工厂就可以实现自己来创建了控制器实例了。
17.2 实现自定义的ControllerFactory
- 在Utility文件夹下创建一个ControllerFactoryHelper,使其继承自DefaultControllerFactory
- 在选择构造函数的时候支持在构造控制器实例的时候,支持依赖注入。
using System;
using System.Web.Mvc;
using System.Web.Routing;
using Unity;
namespace GetStartedWithASPNETMVC.Utility
public class ControllerFactoryHelper:DefaultControllerFactory
/// <summary>
/// 创建控制器
/// </summary>
/// <param name="requestContext"></param>
/// <param name="controllerName"></param>
/// <returns></returns>
public override IController CreateController(RequestContext requestContext, string controllerName)
return base.CreateController(requestContext, controllerName);
/// <summary>
/// 获取控制器实例
/// </summary>
/// <param name="requestContext"></param>
/// <param name="controllerType"></param>
/// <returns></returns>
protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
object oInstance = DIFactoryHelper.GetContainer().Resolve(controllerType);
return (IController)oInstance;
}
17.3 生效自定义ControllerFactoryHelper
- 在Global.asax.cs中增加如下代码
// 设置指定的控制器工厂
ControllerBuilder.Current.SetControllerFactory(new ControllerFactoryHelper());
namespace GetStartedWithASPNETMVC
public class MvcApplication : System.Web.HttpApplication
protected void Application_Start()
AreaRegistration.RegisterAllAreas();// 注册区域
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);// 注册全局Filter
RouteConfig.RegisterRoutes(RouteTable.Routes);// 注册路由
BundleConfig.RegisterBundles(BundleTable.Bundles);// 注册Bundles--引用JS/CS需要的组件
ControllerBuilder.Current.SetControllerFactory(new ControllerFactoryHelper()); // 使用自定义的控制器工厂
}
17.4 控制器中使用构造函数注入
using GetStartedWithASPNETMVC.Utility;
using GetStartedWithASPNETMVC_EF;
using GetStartedWithASPNETMVC_Interface;
using GetStartedWithASPNETMVC_Services;
using Microsoft.Practices.Unity.Configuration;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.IO;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using Unity;
namespace GetStartedWithASPNETMVC.Controllers
public class BuilderControllerFactory : Controller
// 这个控制器用来通过构造函数注入服务
private readonly IOnlineService _service = null;
public BuilderControllerFactory(IOnlineService service)
_service = service;
public ActionResult BuilderIndex()
var online = _service.Find<Online>(7);
base.ViewBag.Last_Result = online.Last_Result;
return View();
#region 扩展:属性注入和方法注入
//属性注入
[Dependency]
public IOnlineService _IOnlineServiceProp { get; set; }
private IOnlineService _IOnlineServiceMethod { get; set; }
/// <summary>
/// 方法注入
/// </summary>
/// <returns></returns>
[InjectionMethod]
public void SetICompanyServiceMethod(IOnlineService iOnlineService)
_IOnlineServiceMethod = iOnlineService;
#endregion
}
18 Filter
不修改原代码逻辑,增加一些逻辑运行,AOP编程。
18.1 ActionFilter
两种扩展方式:ActionFilterAttribute是一个抽象类、IActionFilter。
18.1.1 ActionFilterAttribute扩展
- 在Utility下增加FilterHelper类,继承自 ActionFilterAttribute ,对抽象类进行实现。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace GetStartedWithASPNETMVC.Utility
public class FilterHelper:ActionFilterAttribute
// 在方法执行前
public override void OnActionExecuting(ActionExecutingContext filterContext)
base.OnActionExecuting(filterContext);
// 在方法执行后
public override void OnActionExecuted(ActionExecutedContext filterContext)
base.OnActionExecuted(filterContext);
// 在结果执行前(视图返回)
public override void OnResultExecuting(ResultExecutingContext filterContext)
base.OnResultExecuting(filterContext);
// 生成视图
// 在结果执行后(视图返回)
public override void OnResultExecuted(ResultExecutedContext filterContext)
base.OnResultExecuted(filterContext);
}
- 创建FilterController控制器
- 直接将FilterHelper类,添加到FilterController方法ActionResult Index方法上
namespace GetStartedWithASPNETMVC.Controllers
public class FilterController : Controller
// GET: Filter
[FilterHelper]
public ActionResult Index()
return View();
}
18.1.2 IActionFilter
- 创建类FilterTwoHelper,继承自 FilterAttribute、IActionFilter
public class FilterTwoHelper:FilterAttribute,IActionFilter
public void OnActionExecuting(ActionExecutingContext filterContext)
Console.WriteLine("OnActionExecuting");
public void OnActionExecuted(ActionExecutedContext filterContext)
Console.WriteLine("OnActionExecuted");
}
- 使用和FilterHelper类一样
18.1.3 ActionFilter应用
- 记录日志
- 压缩:浏览器发起请求到服务器,服务器响应结果给浏览器,响应的数据存在大小的区别,如果能够在响应数据上,把数据压缩即可提高性能。
在Utility文件夹下增加一个专门做压缩的Filter:CustomCompressionActionFilterAttribute:ActionFilterAttribute
using System;
using System.Collections.Generic;
using System.IO.Compression;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace GetStartedWithASPNETMVC.Utility
public class CustomCompressionActionFilterAttribute:ActionFilterAttribute
/// <summary>
/// 压缩
/// </summary>
/// <param name="filterContext"></param>
public override void OnActionExecuting(ActionExecutingContext filterContext)
// 判断客户端和服务端是否有相同的压缩规则
var request = filterContext.HttpContext.Request;
var response = filterContext.HttpContext.Response;
var acceptEncoding = request.Headers["Accept-Encoding"];//检测支持格式
if (!string.IsNullOrWhiteSpace(acceptEncoding) && acceptEncoding.ToUpper().Contains("GZIP")) //如果包含说明浏览器已经声明了支持GZIP格式压缩
response.AddHeader("Content-Encoding", "gzip");//响应头指定类型
response.Filter = new GZipStream(response.Filter, CompressionMode.Compress);//压缩类型指定
base.OnActionExecuting(filterContext);
public override void OnActionExecuted(ActionExecutedContext filterContext)
base.OnActionExecuted(filterContext);
}
- 创建一个CompressionController,用浏览器F12查看网页请求大小。
- 在CompressionController Index方法上加上特性CustomCompressionActionFilterAttribute,再次查看大小。
大小从842压缩到了662。
18.2 Filter多种注册方法
- 可以标记在方法上面
- 也可以标记在控制器上,让所有的方法生效
- 全局注册 App_Start中FilterConfig.cs 中,对项目中的所有方法生效
filter.Add(new CustomCompressionActionFilterAttribute()); //全局注册
18.3 ResultFilter
在生成结果之前或生成结构之后插入一些额外的逻辑,先进去OnResultExecuting-生成视图-OnResultExecuted
- 通过 ActionFilterAttribute 继承扩展(这种方式和ActionFilter一样效果)
- 实现 IResultFilter、FilterAttribute 接口来扩展
18.4 ExceptionFilter
18.4.1 ExceptionFilter基础用法
异常处理Filter
- HandleErrorAttribute (在App_Start中FilterConfig.cs,默认实现)
- IHandle、FilterAttribute
自定义CustomHandleErrorAttribute
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace GetStartedWithASPNETMVC.Utility.Filter
public class CustomHandleErrorAttribute: HandleErrorAttribute
/// <summary>
/// 如果有异常发生,则跳转到这边
/// </summary>
/// <param name="filterContext"></param>
public override void OnException(ExceptionContext filterContext)
if (!filterContext.ExceptionHandled)
if (filterContext.HttpContext.Request.IsAjaxRequest())
filterContext.Result = new JsonResult()
Data = new
DebugMessage = filterContext.Exception.Message,
RetValue = "",
PromptMsg = "发生错误,请联系管理员"
filterContext.Result = new ViewResult()//短路器
ViewName = "~/Views/Shared/Error.cshtml",
ViewData = new ViewDataDictionary<string>(filterContext.Exception.Message)
filterContext.ExceptionHandled = true;//已经被我处理了
}
- 修改Error.cshtml文件以获取错误消息,错误消息的格式通过ViewDataDictionary<string>传递过去
<!DOCTYPE html>
@model string
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<meta name="viewport" content="width=device-width" />
<title>错误</title>
</head>
<hgroup>
<h1>错误。</h1>
<h2>处理你的请求时出错。</h2>
<h2>出现如下错误:@Model</h2>
</hgroup>
</body>
</html>
18.4.2 扩展-捕捉非200异常
在Global.asax中添加如下代码
/// <summary>
/// 只要是响应不是200,就能在这边捕捉到,捕捉异常的漏网之鱼
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
protected void Application_Error(object sender, EventArgs e)
Exception excetion = Server.GetLastError();
Response.Write("System is Error....");
Server.ClearError();
}
18.4.3 全局异常捕捉设置
因为异常比较敏感,所以基本都设置为全局Filter,在App_Start中FilterConfig.cs中添加全局Filter。
using GetStartedWithASPNETMVC.Utility.Filter;
using System.Web;
using System.Web.Mvc;
namespace GetStartedWithASPNETMVC
public class FilterConfig
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
filters.Add(new HandleErrorAttribute());
filters.Add(new CustomExceptionFilterAttribute());
}
18.5 AuthorizeFilter
为了验证权限而存在,系统开发中必不可少:
1.登录授权 :把用户信息写入到Session中,在返回的时候把SessionID返回到客户端,写入到Cookie中
2.访问验证是否有权限:浏览器会自动带上Session,服务器如果找到SessionID就表示登录过了,验证通过则跳转到登录页。
18.5.1 创建用户数据库表 CurrentUser
通过Sqlsugar将CurrentUser表写入到数据库中,并初始化一些数据。
修改MVC项目中Unity配置文件,将数据库实体表和DbContext配置过去(创建一个类库项目,选择数据库的Code First进行集成)。
using SqlSugar;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace GetStartedWithASPNETMVC_Sqlsugar.Models
[SugarTable("T_CurrentUser")]
public class CurrentUser
[SugarColumn(IsIdentity = true, IsPrimaryKey = true)]
public int Id { get; set; }
public string Name { get; set; }
public string Account { get; set; }
public string Password { get; set; }
public string Email { get; set; }
[SugarColumn(IsNullable = true)]//可以为NULL
public DateTime LoginTime { get; set; }
}
18.5.2 通用类库-生成验证码
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Web;
namespace GetStartedWithASPNETMVC.Utility
public class VerifyCodeHelper
public static Bitmap CreateVerifyCode(out string code)
//建立Bitmap对象,绘图
Bitmap bitmap = new Bitmap(200, 60);
Graphics graph = Graphics.FromImage(bitmap);
graph.FillRectangle(new SolidBrush(Color.White), 0, 0, 200, 60);
Font font = new Font(FontFamily.GenericSerif, 48, FontStyle.Bold, GraphicsUnit.Pixel);
Random r = new Random();
string letters = "ABCDEFGHIJKLMNPQRSTUVWXYZ0123456789";
StringBuilder sb = new StringBuilder();
//添加随机的五个字母
for (int x = 0; x < 5; x++)
string letter = letters.Substring(r.Next(0, letters.Length - 1), 1);
sb.Append(letter);
graph.DrawString(letter, font, new SolidBrush(Color.Black), x * 38, r.Next(0, 15));
code = sb.ToString();
//混淆背景
Pen linePen = new Pen(new SolidBrush(Color.Black), 2);
for (int x = 0; x < 6; x++)
graph.DrawLine(linePen, new Point(r.Next(0, 199), r.Next(0, 59)), new Point(r.Next(0, 199), r.Next(0, 59)));
return bitmap;
}
18.5.3 编写控制器
using GetStartedWithASPMVC_SqlsugarDbcontext;
using GetStartedWithASPNETMVC.Utility;
using GetStartedWithASPNETMVC.Utility.Filter;
using GetStartedWithASPNETMVC_Interface;
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace GetStartedWithASPNETMVC.Controllers
public class LoginController : Controller
// 这个控制器用来通过构造函数注入服务
private readonly ICustomService _service = null;
public LoginController(ICustomService service)
this._service = service;
[HttpGet]
[CsutomAllowAnonymousAttribute]
// GET: Login
public ActionResult Log()
return View();
[HttpPost]
[CsutomAllowAnonymousAttribute]
public ActionResult Log(string name, string password, string verify)
//1.验证验证码
//2.查询数据库,验证用户信息
//3.把用户信息写入到Session中去;
//4.跳转到主页中
//string formName = base.HttpContext.Request.Form["Name"];
if (!string.Equals(verify, HttpContext.Session["CheckCode"].ToString(), StringComparison.InvariantCultureIgnoreCase))
ModelState.AddModelError("failed", "用户名或者密码错误");
return View();
//通过数据库查询用户名和密码
var queryResult = _service.Query<T_CurrentUser>(c => c.Name == name).First();
if (queryResult != null)
if (password.Equals(queryResult.Password))
HttpContext.Session["T_CurrentUser"] = queryResult;
return View("~/Views/Login/LogSuccess.cshtml");
//return base.Redirect("/Home/Index");
ModelState.AddModelError("failed", "用户名或者密码错误");
return View();
ModelState.AddModelError("failed", "用户名或者密码错误");
return View();
/// <summary>
/// 验证码 直接写入Response
/// </summary>
[CsutomAllowAnonymousAttribute]
public void VerifyCode()
string code = "";
// 绘制出图片,通用类库
Bitmap bitmap = VerifyCodeHelper.CreateVerifyCode(out code);
base.HttpContext.Session["CheckCode"] = code;
bitmap.Save(base.Response.OutputStream, ImageFormat.Gif);
base.Response.ContentType = "image/gif";
[HttpPost]
public ActionResult Logout()
//1.登录成功----写了Session数据
//2.注销 登出---清除session
#region Cookie
HttpCookie myCookie = HttpContext.Request.Cookies["T_CurrentUser"];
if (myCookie != null)
myCookie.Expires = DateTime.Now.AddMinutes(-1);//设置过过期
HttpContext.Response.Cookies.Add(myCookie);
#endregion Cookie
#region Session
var sessionUser = HttpContext.Session["T_CurrentUser"];
if (sessionUser != null && sessionUser is T_CurrentUser)
T_CurrentUser currentUser = (T_CurrentUser)HttpContext.Session["T_CurrentUser"];
HttpContext.Session["T_CurrentUser"] = null;//表示将制定的键的值清空,并释放掉,
HttpContext.Session.Remove("T_CurrentUser");
HttpContext.Session.Clear();//表示将会话中所有的session的键值都清空,但是session还是依然存在,
HttpContext.Session.RemoveAll();//
HttpContext.Session.Abandon();//就是把当前Session对象删除了,下一次就是新的Session了
#endregion Session
return RedirectToAction("Log", "Login"); ;
}
18.5.4 编写视图
@using GetStartedWithASPMVC_SqlsugarDbcontext
@model T_CurrentUser
ViewBag.Title = "Log";
<h2>@ViewBag.Title。</h2>
<div class="row">
<div class="col-md-8">
<section id="loginForm">
@using (Html.BeginForm("Log", "Login", new { sid = "123", Account = "Eleven" },
FormMethod.Post, new { @class = "form-horizontal", role = "form" }))
@Html.AntiForgeryToken()
<h4>使用本地帐户登录。</h4>
@Html.ValidationSummary(true)
<div class="form-group">
@Html.LabelFor(m => m.Name, new { @class = "col-md-2 control-label" })
<div class="col-md-10">
@Html.TextBoxFor(m => m.Name, new { @class = "form-control" })
<div class="form-group">
@Html.LabelFor(m => m.Password, new { @class = "col-md-2 control-label" })
<div class="col-md-10">
@Html.PasswordFor(m => m.Password, new { @class = "form-control" })
<div class="form-group">
@Html.Label("VerifyCode", "VerifyCode", new { @class = "col-md-2 control-label" })
<div class="col-md-10">
@Html.TextBox("verify", "", new { @class = "form-control" })
<div class="form-group">
<div class="col-md-10">
<img alt="验证码图片" class="img" onclick="refresh(this)" src="/Login/VerifyCode" title="点击刷新">
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="登录" class="btn btn-default" />
@Html.ValidationMessage("failed")
</section>
<script>
function refresh(obj) {
$(obj).attr("src", "/Login/VerifyCode?random=" + Math.random());
</script>
18.5.5 验证权限 AuthorizeAttribute
自定义CustomAuthorizeAttribute类,继承AuthorizeAttribute,实现虚方法OnAuthorization(当要验证权限的时候)。
在虚方法中,首先HttpContext(请求的所有东西都在这里面),其次去判断httpContext.Session["T_CurrentUser"]是否为空或者类型是否正确
如果不正确,Ajax请求就给一个filterContext.Result = new JsonResult(){xxxxx} 普通请求则跳转到登录界面。
如果正确,可以给一个欢迎界面或记录一些日志。
using GetStartedWithASPMVC_SqlsugarDbcontext;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace GetStartedWithASPNETMVC.Utility.Filter
public class CustomAuthorizeAttribute:AuthorizeAttribute
private string _LoginUrl = null;
public CustomAuthorizeAttribute(string loginUrl = "~/Login/Log")
this._LoginUrl = loginUrl;
/// <summary>
/// 当要验证权限的时候进来
/// </summary>
/// <param name="filterContext"></param>
public override void OnAuthorization(AuthorizationContext filterContext)
var httpContext = filterContext.HttpContext;//能拿到httpcontext 就可以为所欲为
if (filterContext.ActionDescriptor.IsDefined(typeof(CsutomAllowAnonymousAttribute), true))
return;
else if (filterContext.ActionDescriptor.ControllerDescriptor.IsDefined(typeof(CsutomAllowAnonymousAttribute), true))
return;
if (httpContext.Session["T_CurrentUser"] == null
|| !(httpContext.Session["T_CurrentUser"] is T_CurrentUser))//为空了,
//这里有用户,有地址 其实可以检查权限
if (httpContext.Request.IsAjaxRequest())
//httpContext.Request.Headers["xxx"].Equals("XMLHttpRequst")
filterContext.Result = new JsonResult()
Data = new
DebugMessage = "登陆过期",
RetValue = ""
httpContext.Session["T_CurrentUser"] = httpContext.Request.Url.AbsoluteUri;
filterContext.Result = new RedirectResult(this._LoginUrl);
//短路器:指定了Result,那么请求就截止了,不会执行action
T_CurrentUser user = (T_CurrentUser)httpContext.Session["T_CurrentUser"];
//this.logger.Info($"{user.Name}登陆了系统");
return;//继续
}
18.5.6 匿名访问(不进行授权验证)
如果需要所有的方法都需要认证,如何实现?
如果进行全局注册的方式会出现套娃的情况,即登录的方法也会走进验证Filter里面
- 实现方式一: 这需要我们在登录的方法中添加一个特性 [AllowAnonymous] ,跳过AuthorizeAttribute验证
- 实现方式二: 自定义一个方法特性,然后在进入验证Filter的时候,检查是否控制器方法是否有此特性 (既要检查方法是否有此特性,也要检查控制器类是否有全类级别的特性) ,如果有则直接返回不进行验证。
namespace GetStartedWithASPNETMVC.Utility.Filter
[AttributeUsage(AttributeTargets.Method)]
public class CsutomAllowAnonymousAttribute: Attribute
// 既要检查方法是否有标记的特性,也要检查控制器上方是否有全局特性
if (filterContext.ActionDescriptor.IsDefined(typeof(CsutomAllowAnonymousAttribute), true))
return;
else if (filterContext.ActionDescriptor.ControllerDescriptor.IsDefined(typeof(CsutomAllowAnonymousAttribute), true))
return;
}
18.5.7 登出
- 在母版页(~/Views/Shared/_Layout.cshtml)中添加用户姓名和注销选项,添加一个子模块_Login.cshtml
<div class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<li>@Html.ActionLink("Home", "Index", "Home")</li>
<li>@Html.ActionLink("关于", "About", "Home")</li>
<li>@Html.ActionLink("联系方式", "Contact", "Home")</li>
@Html.Partial("_Login")
</div>
- 写_Login.cshtml逻辑
@using GetStartedWithASPMVC_SqlsugarDbcontext
@model T_CurrentUser
@if (base.Context.Session["T_CurrentUser"] != null && base.Context.Session["T_CurrentUser"] is T_CurrentUser)
using (Html.BeginForm("Logout", "Login", FormMethod.Post, new { id = "logoutForm", @class = "navbar-right" }))
@Html.AntiForgeryToken()
<ul class="nav navbar-nav navbar-right">
@Html.ActionLink("你好 " + ((T_CurrentUser)Context.Session["T_CurrentUser"]).Name + "!", "Index", "Home", routeValues: null, htmlAttributes: new { title = ((T_CurrentUser)Context.Session["T_CurrentUser"]).Name })
<li><a href="javascript:document.getElementById('logoutForm').submit()">注销</a></li>
<ul class="nav navbar-nav navbar-right">
<li>@Html.ActionLink("登录", "Log", "Login", routeValues: null, htmlAttributes: new { id = "loginLink" })</li>
}
- 在控制器中写登出逻辑
[HttpPost]
[CsutomAllowAnonymousAttribute] // 不进行验证
public ActionResult Logout()
//1.登录成功----写了Session数据
//2.注销 登出---清除session
#region Cookie
HttpCookie myCookie = HttpContext.Request.Cookies["T_CurrentUser"];
if (myCookie != null)
myCookie.Expires = DateTime.Now.AddMinutes(-1);//设置过期时间
HttpContext.Response.Cookies.Add(myCookie);
#endregion Cookie
#region Session
var sessionUser = HttpContext.Session["T_CurrentUser"];
if (sessionUser != null && sessionUser is T_CurrentUser)
T_CurrentUser currentUser = (T_CurrentUser)HttpContext.Session["T_CurrentUser"];
HttpContext.Session["T_CurrentUser"] = null;//表示将制定的键的值清空,并释放掉,
HttpContext.Session.Remove("T_CurrentUser");
HttpContext.Session.Clear();//表示将会话中所有的session的键值都清空,但是session还是依然存在,
HttpContext.Session.RemoveAll();//
HttpContext.Session.Abandon();//就是把当前Session对象删除了,下一次就是新的Session了
#endregion Session
return RedirectToAction("Log", "Login"); ;
}
19 管道
19.1 HTTP全流程解析
- 用户浏览器输入浏览器地址/域名
- DNS解析(域名供应商):将输入的网址解析成IP+端口
- 请求到达服务器Server:IP在互联网定位服务器,端口确定进程,端口还可以带有协议信息,用于穿过防火墙
- HTTP.SYS服务接收HTTP请求,交给IIS
- IIS将请求发到对应的程序处理
- 请求包装成HttpWorkerRequest对象
- 传到AppDomain的HttpRuntime入口
- 开始管道模型
- IIS接收返回的数据流,给HTTP.SYS
- HTTP.SYS将数据给客户端(浏览器)
19.2 aspnet_ISAPI(ISAPI)转交请求给 http:// ASP.NET 管道
HTTPContext
HTTPRuntime
在 http:// ASP.NET 中任何一个请求,都需要 IHttpHandler去处理
源码分析: System.Web里面搜索HttpRuntime,搜索 ProcessRequest 方法( 此方法会把HttpWorkerRequest转换请求后的对象进行处理 )
// System.Web.HttpRuntime
using System.Security.Permissions;
[AspNetHostingPermission(SecurityAction.Demand, Level = AspNetHostingPermissionLevel.Medium)]
public static void ProcessRequest(HttpWorkerRequest wr)
if (wr == null)
throw new ArgumentNullException("wr");
if (UseIntegratedPipeline)
throw new PlatformNotSupportedException(SR.GetString("Method_Not_Supported_By_Iis_Integrated_Mode", "HttpRuntime.ProcessRequest"));
ProcessRequestNoDemand(wr);
// System.Web.HttpRuntime
internal static void ProcessRequestNoDemand(HttpWorkerRequest wr)
RequestQueue requestQueue = _theRuntime._requestQueue;
wr.UpdateInitialCounters();
if (requestQueue != null)
wr = requestQueue.GetRequestToExecute(wr);
if (wr != null)
CalculateWaitTimeAndUpdatePerfCounter(wr);
wr.ResetStartTime();
ProcessRequestNow(wr);
// System.Web.HttpRuntime
internal static void ProcessRequestNow(HttpWorkerRequest wr)
_theRuntime.ProcessRequestInternal(wr);
// System.Web.HttpRuntime
using System.Text;
using System.Threading;
using System.Web.Hosting;
private void ProcessRequestInternal(HttpWorkerRequest wr)
Interlocked.Increment(ref _activeRequestCount);
if (_disposingHttpRuntime)
wr.SendStatus(503, "Server Too Busy");
wr.SendKnownResponseHeader(12, "text/html; charset=utf-8");
byte[] bytes = Encoding.ASCII.GetBytes("<html><body>Server Too Busy</body></html>");
wr.SendResponseFromMemory(bytes, bytes.Length);
wr.FlushResponse(finalFlush: true);
wr.EndOfRequest();
return;
finally
Interlocked.Decrement(ref _activeRequestCount);
HttpContext httpContext;
httpContext = new HttpContext(wr, initResponseWriter: false);
catch
wr.SendStatus(400, "Bad Request");
wr.SendKnownResponseHeader(12, "text/html; charset=utf-8");
byte[] bytes2 = Encoding.ASCII.GetBytes("<html><body>Bad Request</body></html>");
wr.SendResponseFromMemory(bytes2, bytes2.Length);
wr.FlushResponse(finalFlush: true);
wr.EndOfRequest();
return;
finally
Interlocked.Decrement(ref _activeRequestCount);
wr.SetEndOfSendNotification(_asyncEndOfSendCallback, httpContext);
HostingEnvironment.IncrementBusyCount();
EnsureFirstRequestInit(httpContext);
catch
if (!httpContext.Request.IsDebuggingRequest)
throw;
httpContext.Response.InitResponseWriter();
IHttpHandler applicationInstance = HttpApplicationFactory.GetApplicationInstance(httpContext);
if (applicationInstance == null)
throw new HttpException(SR.GetString("Unable_create_app_object"));
if (EtwTrace.IsTraceEnabled(5, 1))
EtwTrace.Trace(EtwTraceType.ETW_TYPE_START_HANDLER, httpContext.WorkerRequest, applicationInstance.GetType().FullName, "Start");
if (applicationInstance is IHttpAsyncHandler)
IHttpAsyncHandler httpAsyncHandler2 = (httpContext.AsyncAppHandler = (IHttpAsyncHandler)applicationInstance);
httpAsyncHandler2.BeginProcessRequest(httpContext, _handlerCompletionCallback, httpContext);
applicationInstance.ProcessRequest(httpContext);
FinishRequest(httpContext.WorkerRequest, httpContext, null);
catch (Exception e)
httpContext.Response.InitResponseWriter();
FinishRequest(wr, httpContext, e);
19.3 管道模型核心设计
通过一系列转换将HttpWorkerRequest对象转换成HttpApplication(IHttpHandler),后续一系列操作(比如权限认证、缓存判断、异常处理等)是通过观察者模式来实现的。
观察者模式:固定动作+不定环节(事件),不定的模型可以 通过注册事件来添加进执行链 中。
可以通过类集成自 IHttpHandler 写一些固定流程和事件来执行自己想要的操作。
添加一个控制器CustomHttpApplicationController,添加AlbertHttpAppliction类来实现固定流程和事件的注册机制。
using GetStartedWithASPNETMVC.Utility.Filter;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace GetStartedWithASPNETMVC.Controllers
public class CustomHttpApplicationController : Controller
// GET: CustomHttpApplication
[CsutomAllowAnonymousAttribute]
[AllowAnonymous]
public ActionResult Index()
AlbertHttpAppliction albertHttpAppliction = new AlbertHttpAppliction();
albertHttpAppliction.PrecessPreOne += () => { Console.WriteLine("PreOne"); };
albertHttpAppliction.PrecessAfterOne += () => { Console.WriteLine("AfterOne"); };
albertHttpAppliction.PrecessPreTwo += () => { Console.WriteLine("PreTwo"); };
albertHttpAppliction.PrecessAfterTwo += () => { Console.WriteLine("AfterTwo"); };
albertHttpAppliction.ProcessRequest(null);
return View();
public class AlbertHttpAppliction : IHttpHandler
public bool IsReusable => true;
public event Action PrecessPreOne = null;
public event Action PrecessAfterOne = null;
public event Action PrecessPreTwo = null;
public event Action PrecessAfterTwo = null;
public void ProcessRequest(HttpContext context)
this.PrecessPreOne?.Invoke();
Console.WriteLine("第一步:开始执行自定义IHttpHandler");
this.PrecessAfterOne?.Invoke();
this.PrecessPreTwo?.Invoke();
Console.WriteLine("第二步:开始执行自定义IHttpHandler");
this.PrecessAfterTwo?.Invoke();
}
19.4 请求级扩展-管道模型事件注册操作者IHttpModule
- 自定义CustomHttpModule,实现IHttpModule
- 在实现接口方法 void Init(HttpApplication context)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace GetStartedWithASPNETMVC.Utility.Pipeline
public class CustomHttpModule : IHttpModule
public void Dispose()
throw new NotImplementedException();
public void Init(HttpApplication context)
context.BeginRequest += (o, e) =>
HttpContext.Current.Response.Write("This is customhttpmodule begin request response");
context.EndRequest += (o, e) =>
HttpContext.Current.Response.Write("This is customhttpmodule end request response");
}
- 生效CustomHttpModule(增加配置文件,本地电脑有一个默认的配置文件 C:\Windows\ http:// Microsoft.NET \Framework64\v4.0.30319\Config\web.config)
在web.config文件中 框架默认配置(慎重修改,修改前备份),可以在项目中的web.config进行增加配置。
<httpModules>
<add name="OutputCache" type="System.Web.Caching.OutputCacheModule"/>
<add name="Session" type="System.Web.SessionState.SessionStateModule"/>
<add name="WindowsAuthentication" type="System.Web.Security.WindowsAuthenticationModule"/>
<add name="FormsAuthentication" type="System.Web.Security.FormsAuthenticationModule"/>
<add name="PassportAuthentication" type="System.Web.Security.PassportAuthenticationModule"/>
<add name="RoleManager" type="System.Web.Security.RoleManagerModule"/>
<add name="UrlAuthorization" type="System.Web.Security.UrlAuthorizationModule"/>
<add name="FileAuthorization" type="System.Web.Security.FileAuthorizationModule"/>
<add name="AnonymousIdentification" type="System.Web.Security.AnonymousIdentificationModule"/>
<add name="Profile" type="System.Web.Profile.ProfileModule"/>
<add name="ErrorHandlerModule" type="System.Web.Mobile.ErrorHandlerModule, System.Web.Mobile, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"/>
<add name="ServiceModel" type="System.ServiceModel.Activation.HttpModule, System.ServiceModel.Activation, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
<add name="UrlRoutingModule-4.0" type="System.Web.Routing.UrlRoutingModule"/>