1 ASP.NET MVC
1.1 Get Started With ASP.NET MVC
在未来的新项目开发多采用WebApi项目结构去开发,但由于很多历史原因,有些老项目采用了MVC的方式去开发,这就涉及到老项目的维护和升级迁移,势必需要开发人员能够读懂前人写的代码,故此写此文章记录ASP.NET MVC开发过程,以此来帮助有此需求的开发人员能够快速掌握。
- 创建ASP.NET Web 应用程序(.NET Framework)
-
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: 包管理文件
-
2 项目发布 IIS
2.1 本地部署
依赖Windows,依赖IIS(IIS作为服务器)
- 启用或关闭Windows功能
IIS(Interface Information Service)勾选所有模块
2.2 Windows云服务部署
-
2.3 发布
打开IIS管理器
- 添加网站-路径选择指向项目根目录-配置端口(建议5000-10000之间)-配置主机名
-
2.4 权限
打开网站所在的物理路径-属性-安全-添加编辑IUSR(完全控制权限)-添加编辑IIS_IUSRS(完全控制权限)
3 数据传值方式
3.1 传值代码编写
右键添加控制器 FirstController
- 在FirstController.cs代码中Index()方法,右键添加视图,修改
标签里面的内容为 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
-
3.3 传值异常情况
值覆盖情况(如果ViewBag和ViewData键一样的话,值会变更为最新的) ```csharp base.ViewBag.User1 = “yt123”; base.ViewData[“User1”] = yt123123”;
ViewBag.User1:@ViewBag.User1
这里面的值会被覆盖,显示结果为yt123123
- Redirect重定向传值丢失问题,也可以用View("想跳转的方法名")
- model传值不要把数据类型定义成一个String类型,有重载方法会进行方法跳转。
```csharp
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--> <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; ///
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基架),每一个板块职责就更加清晰,组件化模块化开发。
6 路由
6.1 匹配路由
客户端发起请求—IIS服务器—程序集—路由匹配 RouteConfig.RegisterRoutes(RouteTable.Routes) 这里维护了一个路由表,是一个Collection
路由匹配先按照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>© @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
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扩展