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()方法,右键添加视图,修改

    标签里面的内容为 This is first index.

  • 在控制器中写一些数据

    1. public class FirstController : Controller
    2. {
    3. // GET: First
    4. public ActionResult Index()
    5. {
    6. // 业务逻辑的计算
    7. // 结果展示给VIEW
    8. base.ViewBag.User1 = "yt01-008";
    9. base.ViewBag.User2 = "yt01-009";
    10. base.ViewData["company"] = "yt";
    11. base.TempData["temp"] = "test";
    12. base.HttpContext.Session["User"] = "httpcontext albertzhaoz";
    13. object model = "model albertzhaoz";
    14. return View(model);
    15. }
    16. }
  • 在View中获取这些数据来源

需要注意的是HttpContext.Current.Session[“User”]和Model要通过View(model)方法传递过去,且要定义model变量

  1. @{
  2. ViewBag.Title = "Index";
  3. }
  4. @model string
  5. <h2>This is first index.</h2>
  6. <!--这里面是VIEW层-->
  7. <h3>ViewBag.User1:@ViewBag.User1</h3>
  8. <h3>ViewBag.User2:@ViewBag.User2</h3>
  9. <h3>ViewData:@ViewData["company"]</h3>
  10. <h3>TempData:@TempData["temp"]</h3>
  11. <h3>HttpContext:@HttpContext.Current.Session["User"]</h3>
  12. <h3>model:@Model</ h3 >

3.2 传值类别

  • ViewData
  • ViewBag
  • Model
  • TempData
  • HttpContext.Session

    3.3 传值异常情况

  • 值覆盖情况(如果ViewBag和ViewData键一样的话,值会变更为最新的) ```csharp base.ViewBag.User1 = “yt123”; base.ViewData[“User1”] = yt123123”;

ViewBag.User1:@ViewBag.User1

这里面的值会被覆盖,显示结果为yt123123

  1. - Redirect重定向传值丢失问题,也可以用View("想跳转的方法名"
  2. - model传值不要把数据类型定义成一个String类型,有重载方法会进行方法跳转。
  3. ```csharp
  4. public class FirstController : Controller
  5. {
  6. // GET: First
  7. public ActionResult Index()
  8. {
  9. // 业务逻辑的计算
  10. // 结果展示给VIEW
  11. base.ViewBag.User1 = "yt01-008";
  12. base.ViewBag.User2 = "yt01-009";
  13. base.ViewData["company"] = "yt";
  14. base.TempData["temp"] = "test";
  15. base.HttpContext.Session["User"] = "httpcontext albertzhaoz";
  16. object model = "model albertzhaoz";
  17. base.ViewData["User1"] = "yt01-008008";
  18. return View(model);
  19. // 直接跳转到Albert界面,并将model传递过去
  20. // return View("Albert",model);
  21. }
  22. public ActionResult Albert()
  23. {
  24. return View();
  25. }
  26. }

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-->
      <root>
          <priority value="ALL"/>
          <level value="ALL"/>
          <appender-ref ref="rollingAppender"/>
      </root>
    </log4net>
    

    4.2 编写 LoggerHelper 帮助类

    ```xml 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; ///

/// 静态构造函数(整个进程只执行一次-读取配置文件) /// 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);
    }
}

}

<a name="Gfge8"></a>
## 4.3 使用 LoggerHelper
```xml
private readonly Logger _logger = new Logger(typeof(FirstController));

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 记录日志到数据库中

注意点,在生产环境中,设置,不要去频繁写数据库。

// 在 DQ381 数据库中执行如下代码
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=DQ381;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-->
    <root>
        <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基架),每一个板块职责就更加清晰,组件化模块化开发。

17 ASP.NET MVC - 图1

6 路由

6.1 匹配路由

客户端发起请求—IIS服务器—程序集—路由匹配 RouteConfig.RegisterRoutes(RouteTable.Routes) 这里维护了一个路由表,是一个Collection集合 —转发到匹配的控制器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 ```csharp @{ ViewBag.Title = “ReadTxtFile”; }

我是来自插件ReadFile

ViewBag的内容:@ViewBag.content

- 启动项目,输入ReadFile/ReadTxtFile则可以进入到ReadTxtFile.cshtml中。 <a name="b1gxw"></a> ## 7.2 热插拔实现 [基于ASP.NET MVC的热插拔模块式开发框架(OrchardNoCMS)--模块开发](https://www.cnblogs.com/n-pei/p/3421853.html) <a name="CzW4F"></a> # 8 Razor 语法 cshtml既可以写html,也可以写后台代码。用@{我是代码部分} **中间不可以有空格**。<br />**注意嵌套View的使用,Html.RenderPartial(Html.Partial)。**<br />**使用Action:用于将其他视图嵌套过来,经过Action传递。(**如果后台方法加了**ChildActionOnly**特性,只能被子请问访问,不能独立访问。**)**csharp @{ ViewBag.Title = “Index”; }

This is second Index

百度一下

我是新的一行

@{ int i = 3; int y = 4;} @if(i>2){ 我是Google }

@for(int k=0;k<10;k++) { <--!这里需要注意的是href标签进行跳转要指明相关协议加上http或https,不然无法显示--> 这是连接@k }

我是页面嵌套

@{ Html.RenderPartial(“ChildAction”,”Timonthy”);} @{Html.Action(“ChildAction”, “Second”,new {name = “Timonthy”});}

我是内部跳转Action,First param is aciton name,second param is controller name,third pamara is input param

@{ Html.RenderAction(“ChildAction”, “Second”,new {name = “Timonthy”});} @section scripts{ } <a name="TQsFN"></a> # 9 Razor 扩展 各种Html标签都可以通过后台语言来实现。 - 创建类库项目-GetStartedWithAspnetHelper - 编写扩展方法csharp 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()方法csharp @{ ViewBag.Title = “RazorExtend”; }

RazorExtend



@Html.Br() 等价于

测试

@Html.ActionLink(linkText,actionName) linkText

@Html.ActionLink(“带控制器”,”ActionName”,”ControllerName”) 带控制器

@Html.ActiornLink(“带路由信息”,”ActionName”,new {id=1,name=3,age=4,height=5}) <a href=”Html/ActionName/1?name=3&age=4&height=5>带路由信息

<a name="ZAgeb"></a>
# 10 页面布局
新建项目中,默认页面展示其实是分为三层:Header、Body、Foot。请求不同页面只是Body在变化。<br />使用了_ViewStart.cshtml,使用了Layout跳转到了_Layout.cshtml。Layout引入了模板页。<br />**@RenderBody() 则是加载其他页面进入,是一个区域占位符。**<br />**每个页面都需要使用的组件或元素则可以在母版页中定义。**
```csharp
@{
    Layout = "~/Views/Shared/_Layout.cshtml";
}

<!DOCTYPE html>
<html>
<head>
<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>
<body>
    <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>
            <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>
                </ul>
            </div>
        </div>
    </div>
    <div class="container body-content">
        @RenderBody()
        <hr />
        <footer>
            <p>&copy; @DateTime.Now.Year - 我的 ASP.NET 应用程序</p>
        </footer>
    </div>

    @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文件最后。 ```csharp 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脚本
```csharp
@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项目 ```csharp 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项目
```csharp
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>();
            else
                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;
            try
            {
                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容器框架。

  • 准备三个接口,三个实现类 ```csharp 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


- 安装Nuget包:Unity
- 使用Unity(用微软官方的DI也可以,使用参见06 依赖注入DI文章)
```csharp
// 实例化
IUnityContainer container = new UnityContainer();
// 注册接口和对象绑定关系
container.RegisterType<ITestServiceA,TestServiceA>();
// 使用接口得到对象
var testServiceA = container.Resolve<ITestServiceA>();
  • 使用扩展方法优化,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>();
        }      
    }
}
  • 扩展使用

    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

    通过配置文件整合IOC容器到MVC

  • GetStartedWithASPMVC项目-Nuget引入Unity、Unity.Configuration

  • 在Configs文件夹(上面log4net.config创建过)添加unity.config

    16 MVC+IOC支持AOP扩展

17 控制器支持构造函数注入

18 ASP.NET MVC对AOP的支持

19 Filter

20 ASP.NET MVC管道

21 项目实战

22 项目地址