相关文章推荐
无邪的丝瓜  ·  Asynchronous ...·  4 周前    · 
文雅的炒粉  ·  小白学视觉 | ...·  7 月前    · 
首发于 .NET系列
.NET011-ASP.NET MVC

.NET011-ASP.NET MVC

1 ASP.NET MVC

1.1 Get Started With ASP.NET MVC

在未来的新项目开发多采用WebApi项目结构去开发,但由于 很多历史原因 ,有些老项目采用了MVC的方式去开发,这就涉及到 老项目的维护和升级迁移 ,势必需要开发人员能够读懂前人写的代码,故此写此文章记录 ASP.NET MVC开发过程,以此来 帮助有此需求的开发人员能够快速掌握

  • 创建 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 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>&copy; @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
  • 新建 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)转交请求给 ASP.NET 管道

HTTPContext

HTTPRuntime

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\ 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"/>