DotNet Core Note

Last updated on 3 months ago

Note Dot Net Core

DotNet & DotNetCore

image-20220212182101257

DotNet 包含 DotNet Core

DotNet 内有 DotNet FrameworkDotNet CoreXamarin

DotNet Framework 支持Windows和Web应用程序。今天,您可以使用Windows窗体,WPF和UWP在.NET Framework中构建Windows应用程序。ASP.NET MVC用于在.NET Framework中构建Web应用程序。

DotNet Core 是一种新的开源和跨平台框架,用于为包括Windows,Mac和Linux在内的所有操作系统构建应用程序。.NET Core仅支持UWP和ASP.NET Core。UWP用于构建Windows 10目标Windows和移动应用程序。ASP.NET Core用于构建基于浏览器的Web应用程序。

Xamarin 无庸置疑,当您想使用C#构建移动(iOS,Android和Windows Mobile)应用程序时,Xamarin是您唯一的选择。

DotNet Standard 是一套官方定义的API规范,DotNet Framework 和 DotNet Core 都实现了这套规范。

DotNet Framework DotNet Core
跨平台 windows windows、Linux、Mac
微服务 基于对Docker的支持,不建议 支持,有些中间件天然支持Core
容器化 可以,但笨重
多版本并行 net 4.0 支持并行了也只是和3.5 全版本并行
高性能可扩展 一般 天然支持
开源

再结合下图

族谱

.NET Core和ASP.NET Core区别

  1. .NET Core是运行时。它可以执行为其构建的应用程序。ASP.NET Core是构成一个用于构建Web应用程序的框架的库的集合。ASP.NET Core库可以在.NET Core和“完整.NET Framework”(Windows附带许多年)上使用。

  2. 使用.NET Core的 ASP.NET CORE-所有依赖项都是自包含的,可以使用大多数Nuget包,不能使用Windows特定的包,可以在Windows,Linux,Mac上执行

  3. 使用.NET Framework的 ASP.NET CORE-大多数依赖项都是自包含的,仅在Windows上执行,将有权访问Windows特定的Nuget软件包,需要在计算机上安装有针对性的.NET Framework版本

ASP.NET Core (.NET Core) and ASP.NET Core (.NET Framework)区别

ASP.NET Core (.NET Core)

使用.Net Core运行时的ASP.NET Core可以支持跨平台(Windows, Mac, and Linux (包括Docker)),服务器不需要安装.Net Core,它的依赖与应用程序捆绑在一起。而且它是高性能的开源的框架。它能够在您自己的进程中托管IIS,Nginx,Apache,Docker或自托管。ASP.NET Core完全作为NuGet包发布。这允许您优化您的应用程序,使其仅包含必要的NuGet包。实际上,面向.NET Core的ASP.NET Core 2.x应用程序只需要一个NuGet包。应用程序表面积较小的好处,可以有更严格的安全性,更少的服务和更高的性能。可以使用 Kestrel web server。可以使用Visual Studio Code写代码。它现在还不支持Aspx, WPF, WCF and WebServices。它内置依赖注入的支持。可以使用coreclr,它是带有.net core的asp.net核心的运行时。

ASP.NET Core (.NET Framework)

使用.NET Framework运行时的ASP.NET Core可以支持桌面应用,也可以说是完整版。但这些应用程序只能在Windows上运行,但有关ASP.NET Core的其他所有内容的行为方式都相同。另一方面,.Net框架在2005年之前就开始了,它不断添加新功能,使其成为一个复杂的框架和更重的框架。它不是跨平台的。它支持Aspx,WPF,WCF和WebServices。

参考资料

  1. ASP.NET Core (.NET Core) and ASP.NET Core (.NET Framework)区别-CJavaPy
  2. .NET Framework, .NET Core 和.NET Standard的区别和联系_Will Wang0715的博客-CSDN博客

发布

环境用的是Dot Net 5 SDK

问题1

每次运行生成到 \bin\Debug\net5.0 下的文件 对比 发布之后的文件,多的一个 web.config

生成文件

image-20210817220618980

image-20210817220659934

如果把web.config文件复制到生成文件夹中,照样可以通过IIS发布

问题2

IIS 安装完毕之后,该设置的都设置了,但是访问直接

image-20210817221100914

如果之前玩过IIS的话,就会有点懵圈,我之前玩就是这么玩的,为毛现在不行了,答案是,这样真不行。

首先,需要访问 .NET Downloads (Linux, macOS, and Windows) (microsoft.com)

image-20210817221438691

根据自己的环境下载对应的Host 和 SDK

image-20210817221551567

上面这安装完毕后,重启IIS服务

image-20210817221946942

image-20210817222017718

image-20210817222055216

在模块中多俩这玩意,没有这俩玩意的,再找资源下载,都在刚才那个网址里,然 就没有那个500的毛病了。

其实最主要的是依赖 AspNetCoreModuleV2,具体可以看问题1里说的那个web.config中。

image-20210817222624896

你悟了吗?

CMD启动

  1. 通过CMD 去启动 cd 到发布目录 执行

    1
    dotnet DotNetCoreDemo.dll

    image-20210817224246940

    1
    dotnet DotNetCoreDemo.dll --urls "http://*:8888"

    image-20210817224330676

image-20210817224503747

由于DotNetCore 有跨平台的优势,一般生产环境基本是不用IIS

执行的命令在web.config中可以看到,仔细观察

image-20210817224712705

在 DotNetCore 中,IIS 其实充当的角色是转发请求,与之前的发布网站的用法不一样了,有点怀念之前webform的青涩。

读取静态文件+配置中间件

  1. 引入命名空间

    1
    2
    using Microsoft.Extensions.FileProviders;
    using System.IO;
  2. 配置读取静态文件中间件

    1
    2
    3
    4
    5
    6
    7
    8
    //获取相对路径
    string path = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot");

    app.UseStaticFiles(new StaticFileOptions()
    {
    //从物理路径指向wwwroot
    FileProvider = new PhysicalFileProvider(path)
    });

CMD 参数

  1. cmd 执行

    1
    dotnet DotNetCoreDemo.dll --urls "http://*:8080" -- Parame="Harris" 

    格式 – [参数名称] = [参数]

  2. 控制器注入

    1
    using Microsoft.Extensions.Configuration;
    1
    private readonly IConfiguration _iconfiguration;
    1
    2
    3
    4
    5
    6
    7
    public HomeController(ILogger<HomeController> logger, IConfiguration configuration)
    {
    _logger = logger;
    _logger.LogWarning("HomeController 被构造..");

    _iconfiguration = configuration; //注入
    }
    1
    2
    3
    string Parame = _iconfiguration["Parame"];//参数名称

    base.ViewBag.User1 = "张三" + " -- 参数:" + Parame; //使用

    image-20210819222204161

控制器读取配置

方式1

image-20210819224059065

1
2
3
4
5
6
7
8
9
10
11
"TESTID": "1111",
"TESTNAME": "HARRIS",
"TESTAdress": {
"sheng": "SHANXI",
"shi": "XINZHOU"
},
"Family": [
"baba",
"mama",
"son"
]

同样需要注入:

image-20210819224219428

应用:

1
2
3
4
5
6
7
8

ViewBag.Setting1 = _iconfiguration["TESTID"];

ViewBag.Setting2 = _iconfiguration["TESTNAME"];

ViewBag.Setting3 = _iconfiguration["TESTAdress:sheng"];

ViewBag.Setting4 = _iconfiguration["Family:1"];
1
2
3
4
5
6
<ul>
<li>@ViewBag.Setting1</li>
<li>@ViewBag.Setting2</li>
<li>@ViewBag.Setting3</li>
<li>@ViewBag.Setting4</li>
</ul>

image-20210819224316517

方式2

先修改一下appsetting.json

1
2
3
4
5
6
7
8
9
10
11
12
13
"TEST": {
"TESTID": "1111",
"TESTNAME": "HARRIS",
"TESTAdress": {
"sheng": "SHANXI",
"shi": "XINZHOU"
},
"Family": [
"baba",
"mama",
"son"
]
}

实例化一个实体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class TESTMODEL
{
public string TESTID { get; set; }

public string TESTNAME { get; set; }

public TESTAdress TESTAdress { get; set; }

public List<string> Family { get; set; }
}

public class TESTAdress
{
public string sheng { get; set; }

public string shi { get; set; }
}

让实体与需要的json节点相互对应。

然后 Startup.cs

1
2
3
4
5
6
7
8
9
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();

services.AddSession();

//取配置文件中的TEST节点,映射到 TESTMODEL 实体
services.Configure<TESTMODEL>(Configuration.GetSection("TEST"));
}

接着控制器注入

image-20210819230720154

1
2
3
4
5
6
7
8
9
public HomeController(ILogger<HomeController> logger, IConfiguration configuration,IOptions<TESTMODEL> options)
{
_logger = logger;
_logger.LogWarning("HomeController 被构造..");

_iconfiguration = configuration;

_tESTMODEL = options.Value;
}

应用:

1
2
3
object parame = Newtonsoft.Json.JsonConvert.SerializeObject(_tESTMODEL);
//然后返回给视图
return View(parame);

image-20210819230859533

Razor 混编

index.cshtml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
@using DotNetCoreDemo.Interface;
@using Microsoft.AspNetCore.Authorization;
@{
ViewData["Title"] = "Index";
}

<h1>This is Second Index</h1>

@*测试 runtimecompilation*@

<h2>Test Razor runtimecompilation!</h2>

@*实现接口*@

@implements ICoustomInterface

@functions{
//实现接口
public string Show()
{
return "Hello World";
}
}

<h2>实现接口:@Show()</h2>

@*服务注入*@

@inject ICoustomInterface coustomservice
@{

<h2>前台服务注入:@coustomservice.Show()</h2>
}

<h2>后台注册服务:@base.ViewBag.ServiceText</h2>

@*标记特性*@
@*给当前类新增特性,cshtml 可以把它当成一个类*@
@attribute [Authorize];

@functions{
public string GetStr()
{
return "Hello world";
}
}

<h2>调用方法 : @GetStr()</h2>

@*单行*@

@{ <h3 style="color:darkcyan">单行输出:@GetStr()</h3>}

@*多行*@
@*<text>@i</text> 标识为字符串显示文本 不能像后台代码那样用加号连接*@

@{
for (int i = 0; i < 3; i++)
{
<h3 style="color:yellowgreen">多行输出:@GetStr()<text>@i</text></h3>
}
}

@*输出html标签*@

<h3>输出HTML标签:@("<strong>输出HTML标签:Hello world </strong>")</h3>

@*模板化方法*@

@{
void ReturnName(string name)
{
<h3 style="color:brown">模板化输出:@name</h3>
}

ReturnName("Harris");
}

Startup

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.FileProviders;
using System.IO;
using DotNetCoreDemo.Models;
using DotNetCoreDemo.Interface;
using DotNetCoreDemo.Service;

namespace DotNetCoreDemo
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}

public IConfiguration Configuration { get; }

// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();

services.AddSession();

//加入中间件,方便实时调试HTML
services.AddRazorPages().AddRazorRuntimeCompilation();

//注入服务
services.AddTransient<ICoustomInterface, CoustomService>();

//取配置文件中的TEST节点,映射到 TESTMODEL 实体
services.Configure<TESTMODEL>(Configuration.GetSection("TEST"));
}

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
}

//可以参考Program 中的配置,二选一
loggerFactory.AddLog4Net("CfgFile/Log4Net.Config");

//app.UseStaticFiles();

//获取相对路径
string path = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot");

app.UseStaticFiles(new StaticFileOptions()
{
//从物理路径指向wwwroot
FileProvider = new PhysicalFileProvider(path)
});

app.UseSession();

app.UseRouting();

app.UseAuthorization();

app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
}
}
}

Interface 层

1
2
3
4
5
6
7
namespace DotNetCoreDemo.Interface
{
public interface ICoustomInterface
{
public string Show();
}
}

Service 层

1
2
3
4
5
6
7
public class CoustomService : Interface.ICoustomInterface
{
public string Show()
{
return "Hello world";
}
}

Nuget 包

Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation

Razor 布局

开始

  • _ViewStart.cshtml 中设置母版页;

image-20210822120532857

  • 如果不想要模板页,想单页面展示也可以,可以在_ViewStart.cshtml中 注释 layout = “_Layout”; 或者 修改 layout = null;

    image-20210822122832247

母版页

image-20210822120844118

  1. 可以设置一些测试的菜单。

  2. @RenderBody() 相当于一个占位符,菜单中路由的界面都会在这里进行替换。

  3. @await RenderSectionAsync("Scripts",required:false) 应用场景:

    当子页面不手动引用 Scripts类库的时候,可以和上述这行代码配套使用。

    image-20210822121926617

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    //这个是不行的,除非手动引入js文件
    <script type="text/javascript">
    $(document).ready(function () {

    console.log("Hello Harris");

    alert("Hello Harris");
    });
    </script>

    //这个可以,他使用的是模板页中的js文件,相当于把下述js脚本拿到模板页中,然后执行
    @section Scripts{

    <script type="text/javascript">
    $(document).ready(function () {

    console.log("Hello Harris");

    alert("Hello Harris");
    });
    </script>
    }

    image-20210822122520500

  4. 结合上一点说的,如果不使用母版页,如果注释掉或者等于null的话,使用母版页js库的代码就废了。

Razor 扩展

扩展控件(1)

  1. @html.ActionLink 问题。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29

    @{
    ViewData["Title"] = "RazorControl Page";
    }


    ActionLink:@Html.ActionLink("当前控制器Index", "index")

    ActionLink:@Html.ActionLink("Home控制器Index", "index", "Home");

    @{
    TESTAdress testaddress_ = new TESTAdress();

    testaddress_.Sheng = "a";

    testaddress_.Shi = "b";

    TESTMODEL tESTMODEL = new TESTMODEL
    {
    TESTID = "1",
    TESTNAME = "Harris",
    _TESTAdress = testaddress_,
    Family = new List<string> { "1", "2", "3" },
    names = new Name { Xing = "a",Ming = "b"}
    };

    }

    ActionLink:@Html.ActionLink("带路由信息", "index", "Home", tESTMODEL);

    上述方法中有个错误的地方:就是最后一行,传的实体相对复杂,导致浏览器解析不了

    image-20210822195432996

    1
    <a href="/?TESTID=1&amp;TESTNAME=Harris&amp;_TESTAdress=DotNetCoreDemo.Models.TESTAdress&amp;Family=1&amp;Family=2&amp;Family=3&amp;names=DotNetCoreDemo.Models.Name">带路由信息</a>

    再结合下图:

    image-20210822195931807

    image-20210822195634650

    routeValues 这个才是为了给浏览器解析作为页面参数传递的,所以这里不支持复杂实体应该是这个原因。

  2. 新建一个静态方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    using Microsoft.AspNetCore.Html;
    using Microsoft.AspNetCore.Mvc.Rendering;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading.Tasks;

    namespace DotNetCoreDemo.Utility
    {
    public static class HtmlHelperLinkExtensions
    {
    public static IHtmlContent ActionImage(this IHtmlHelper helper,string src)
    {
    return new HtmlString($"<IMG src='{src}' alt='扩展控件IMAGE' style='width:100px;height:20px'/>");
    }
    }
    }

    视图

    1
    2
    @*扩展控件*@
    @Html.ActionImage("http://mmmp3.com/skin/default/images/logo.png")

    image-20210822202647528

扩展控件(2)

此扩展方法旨在扩展html体系中不存在得html 标签。

  1. 新建一个类 CustomTagHelper

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    using Microsoft.AspNetCore.Razor.TagHelpers;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading.Tasks;

    namespace DotNetCoreDemo.Utility
    {
    [HtmlTargetElement("Harris")]
    public class CustomTagHelper : TagHelper //,ITagHelper
    {
    public override void Process(TagHelperContext context, TagHelperOutput output)
    {
    output.TagName = "div";
    output.Attributes.Add("xing", "No.1");
    output.Attributes.Add("ming", "No.2");
    output.PreContent.SetContent("Hello ,My name is Harris");
    output.Attributes.Add("style", "color:red");

    }
    }
    }
    • 标记 HtmlTargeElement() 特性
    • 继承 Taghelper 或者实现 ITaghelper 接口
    • 重写 Process 方法
    • 定义标签
    • 指定属性、内容
  2. 声明 在 _ViewImports.cshtml

    1
    2
    3
    4
    @using DotNetCoreDemo
    @using DotNetCoreDemo.Models
    @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
    @addTagHelper *,DotNetCoreDemo //* 代表当前命名空间下都可使用
  3. 应用

    image-20210822210047680

    image-20210822210156272

    1. 传参

      设置好对象

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      using Microsoft.AspNetCore.Razor.TagHelpers;
      using System;
      using System.Collections.Generic;
      using System.Linq;
      using System.Threading.Tasks;

      namespace DotNetCoreDemo.Utility
      {
      [HtmlTargetElement("Harris")]
      public class CustomTagHelper : TagHelper //,ITagHelper
      {
      public string Id { get; set; }

      public string Name { get; set; }

      public string Age { get; set; }

      public string Shengshi { get; set; }

      public CustomTagHelper()
      {

      }

      public override void Process(TagHelperContext context, TagHelperOutput output)
      {
      //接收参数
      string _id = this.Id;

      string _name = this.Name;

      string _age = this.Age;

      string _shengshi = this.Shengshi;

      output.TagName = "div";
      output.Attributes.Add("xing", "No.1");
      output.Attributes.Add("ming", "No.2");

      //output.PreContent.SetContent("Hello ,My name is Harris");
      //使用参数
      output.PreContent.SetHtmlContent("<h2>Hello ,My name is Harris</h2><ul><li>ID:" + _id + "</li><li>NAME:" + _name + "</li><li>Age:" + _age + "</li><li>Shengshi:" + _shengshi + "</li></ul>");

      output.Attributes.Add("style", "color:red");

      }
      }
      }

      标签属性传值

      image-20210822212634256

      效果

      image-20210822212831324

局部视图

  1. 添加局部视图,其实就是在对应的view层添加一个cshtml文件

    image-20210823212950311

  2. 编辑 PartialView.cshtml ,相当于定义好局部视图,就长这个样子,Harris标签应用与上一节扩展控件内容。

    1
    2
    3
    4
    5
    6
    7
    @model string

    <Harris id="@Model" name="@Model" age="@Model"></Harris>

    <Harris id="@Model" name="@Model" age="@Model"></Harris>

    <Harris id="@Model" name="@Model" age="@Model"></Harris>
  3. 应用 @Html.Partial

    1
    2
    @*局部视图*@
    @Html.Partial("PartialView", "123")// 123 是参数,也就是说,在调用局部视图得时候,其实可以间接设置其内容
  4. 呈现

    image-20210823213304825

视图组件

  1. 新建视图组件扩展类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    using Microsoft.AspNetCore.Mvc;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading.Tasks;

    namespace DotNetCoreDemo.Utility
    {
    [ViewComponent(Name = "CustomList")]
    public class ListViewComponent : ViewComponent
    {
    public async Task<IViewComponentResult> InvokeAsync(string SeachVal)
    {
    var list = await GetStudents(SeachVal);

    ViewBag.User = "Harris";//可以用这个,等等...

    //return View(list);// 默认找得是~/Views/Shared/CustomList/Default.cshtml

    //这里可以指定视图组件的路径
    return View("~/Views/Shared/Components/Test/DefaultTest.cshtml", list);
    }

    public Task<List<Student>> GetStudents(string SeachVal)
    {
    return Task.Run(() =>
    {
    return new List<Student>()
    {
    new Student { ID=1,NAME = "Harris"},
    new Student { ID=2,NAME = "Make"},
    new Student { ID=3,NAME = "Ped"},
    new Student { ID=4,NAME = "Henry"}
    };
    });
    }

    }

    public class Student
    {
    public int ID { get; set; }

    public string NAME { get; set; }
    }
    }

    主要是继承 ViewComponent

  2. 新建视图组件视图

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    @using DotNetCoreDemo.Utility

    @model List<Student>

    <ul>
    @foreach (var item in Model)
    {
    <li>@item.NAME</li>
    }
    </ul>
  3. 应用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @*视图组件*@
    <h2>视图组件</h2>

    <table>
    <tr>
    <td>@await Component.InvokeAsync("CustomList", new { SeachVal = "123456" })</td>
    <td>@await Component.InvokeAsync("CustomList", new { SeachVal = "321321" })</td>
    <td>@await Component.InvokeAsync("CustomList", new { SeachVal = "321321" })</td>
    <td>@await Component.InvokeAsync("CustomList", new { SeachVal = "321321" })</td>
    <td>@await Component.InvokeAsync("CustomList", new { SeachVal = "321321" })</td>
    </tr>
    </table>
  4. 项目列表

    image-20210824222129039

    1. Components/CustomList/Default.cshtmlComponents/Test/DefaultTest.cshtml 中间的区别就是上面第一点中所说的,在返回View()的时候,要不要传具体路径的区别。

      image-20210824222356654

    2. 还得关心一下这个类里的异步结构。

  5. 结果

    image-20210824222521682

    1. 这地方主要是用的DefaultTest.cshtml,Harris 就是从 ViewBag.User 传过来滴。

IOC

IOC出现的背景

我们知道,软件开发领域有句著名的论断:不要重复发明轮子!因为软件开发讲求复用,所以,对于应用频繁的需求,总是有人设计各种通用框架和类库以减轻人们的开发负担。例如,数据持久化是非常频繁的需求,于是各种ORM框架应运而生;再如,对MVC的需求催生了Struts等一批用来实现MVC的框架。

随着面向对象分析与设计的发展和成熟,OOA&D被越来越广泛应用于各种项目中,然而,我们知道,用OO就不可能不用多态性,用多态性就不可能不用依赖注入,所以,依赖注入变成了非常频繁的需求,而如果全部手工完成,不但负担太重,而且还容易出错。再加上反射机制的发明,于是,自然有人开始设计开发各种用于依赖注入的专用框架。这些专门用于实现依赖注入功能的组件或框架,就是IoC Container。从这点看,IoC Container的出现有其历史必然性。目前,最著名的IoC也许就是Java平台上的Spring框架的IoC组件,而.NET平台上也有Spring.NET和Unity等。

IOC是什么

IOC(Inversion of Control),即“控制反转”,不是什么技术,而是一种设计思想。在Java开发中,IOC意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制。如何理解好Ioc呢?理解好Ioc的关键是要明确“谁控制谁,控制什么,为何是反转(有反转就应该有正转了),哪些方面反转了”,那我们来深入分析一下:

  • 谁控制谁,控制什么:传统Java SE程序设计,我们直接在对象内部通过new进行创建对象,是程序主动去创建依赖对象;而IOC是有专门一个容器来创建这些对象,即由IOC容器来控制对象的创建;谁控制谁?当然是IOC容器控制了对象;控制什么?那就是主要控制了外部资源获取
  • 为何是反转,哪些方面反转了:有反转就有正转,传统应用程序是由我们自己在对象中主动控制去直接获取依赖对象,也就是正转;而反转则是由容器来帮忙创建及注入依赖对象;为何是反转?因为由容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象,所以是反转;哪些方面反转了?依赖对象的获取被反转了。

img

img

IOC能做什么

IoC不是一种技术,只是一种思想,一个重要的面向对象编程的法则,它能指导我们如何设计出松耦合、更优良的程序。传统应用程序都是由我们在类内部主动创建依赖对象,从而导致类与类之间高耦合,难于测试;有了IoC容器后,把创建和查找依赖对象的控制权交给了容器,由容器进行注入组合对象,所以对象与对象之间是松散耦合,这样也方便测试,利于功能复用,更重要的是使得程序的整个体系结构变得非常灵活。

其实IoC对编程带来的最大改变不是从代码上,而是从思想上,发生了“主从换位”的变化。应用程序原本是老大,要获取什么资源都是主动出击,但是在IoC/DI思想中,应用程序就变成被动的了,被动的等待IoC容器来创建并注入它所需要的资源了。

IoC很好的体现了面向对象设计法则之一—— 好莱坞法则:“别找我们,我们找你”;即由IoC容器帮对象找相应的依赖对象并注入,而不是由对象主动去找。

IOC和DI

DI—Dependency Injection,即“依赖注入”:是组件之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中。依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。

理解DI的关键是:“谁依赖谁,为什么需要依赖,谁注入谁,注入了什么”,那我们来深入分析一下:

  • 谁依赖于谁:当然是应用程序依赖于IoC容器
  • 为什么需要依赖:应用程序需要IoC容器来提供对象需要的外部资源
  • 谁注入谁:很明显是IoC容器注入应用程序某个对象,应用程序依赖的对象
  • 注入了什么:就是注入某个对象所需要的外部资源(包括对象、资源、常量数据)

IOC和DI有什么关系呢?其实它们是同一个概念的不同角度描述,由于控制反转概念比较含糊(可能只是理解为容器控制对象这一个层面,很难让人想到谁来维护对象关系),所以2004年大师级人物Martin Fowler又给出了一个新的名字:“依赖注入”,相对IoC 而言,“依赖注入”明确描述了“被注入对象依赖IoC容器配置依赖对象”。

Dl 注入方式

交代一下服务和接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
//InterFace
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace DotNetCoreDemo.Interface
{
public interface ITestServiceA
{
void Show();
}
}

//Service
using DotNetCoreDemo.Interface;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace DotNetCoreDemo.Service
{
public class TestServiceA : ITestServiceA
{
public TestServiceA()
{
Console.WriteLine(this.GetType().Name + "构造了");
}

public void Show()
{
Console.WriteLine(this.GetType().Name + "Show");
}
}
}

  1. 第一种

    1. Startup 中的 ConfigureServices 中注册。

      1
      services.AddTransient<ITestServiceA, TestServiceA>();
    2. 在控制器中先实例化一个私有只读的ITestServiceA 对象,然后新建构造函数,构造函数的入参也是ITestServiceA 对象。在构造函数中,将入参的ITestServiceA对象赋值给实例化好的ITestServiceA 对象,然后就可以在方法中使用。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
            using DotNetCoreDemo.Interface;
      using Microsoft.AspNetCore.Mvc;
      using System;
      using System.Collections.Generic;
      using System.Linq;
      using System.Threading.Tasks;

      namespace DotNetCoreDemo.Controllers
      {
      public class IOCController : Controller
      {
      //实例化一个私有只读的`ITestServiceA` 对象
      private readonly ITestServiceA _ItestServiceA = null;//

      //构造函数
      public IOCController(ITestServiceA itestserviceA)
      {
      _ItestServiceA = itestserviceA;
      }

      public IActionResult Index()
      {
      //应用
      _ItestServiceA.Show();

      return View();
      }
      }
      }

      2. 第二种

      1. 同第一种一样,同样也得先在Startup中注册。

      2. 在控制器中先实例化一个私有只读的`IServiceProvider` 对象,然后新建构造函数,构造函数的入参也是`IServiceProvider` 对象。在构造函数中,将入参的`IServiceProvider`对象赋值给实例化好的`IServiceProvider` 对象。但是在使用的时候有差异。

      ``` CSharp
      using DotNetCoreDemo.Interface;
      using Microsoft.AspNetCore.Mvc;
      using System;
      using System.Collections.Generic;
      using System.Linq;
      using System.Threading.Tasks;

      namespace DotNetCoreDemo.Controllers
      {
      public class IOCController : Controller
      {
      private readonly ITestServiceA _ItestServiceA = null;

      private readonly IServiceProvider _serviceProvider = null;

      public IOCController(ITestServiceA itestserviceA, IServiceProvider serviceProvider)
      {
      _ItestServiceA = itestserviceA;

      _serviceProvider = serviceProvider;
      }

      public IActionResult Index()
      {
      _ItestServiceA.Show();

      ITestServiceA testServiceA = (ITestServiceA)_serviceProvider.GetService(typeof(ITestServiceA));

      testServiceA.Show();

      return View();
      }
      }
      }
  2. 第三种

    在视图中注册

    1
    2
    3
    4
    5
    @inject DotNetCoreDemo.Interface.ITestServiceA testServiceA

    @{
    testServiceA.Show();
    }

Dl 依赖注入

如果对象B依赖与对象A,就可以先构造A传递给B,然后得到对应得B得对象实力。

依赖注入可以无限层级的注入,前提是先注入服务。

ServiceA 层

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
using DotNetCoreDemo.Interface;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace DotNetCoreDemo.Service
{
public class TestServiceA : ITestServiceA
{
public TestServiceA()
{
Console.WriteLine(this.GetType().Name + "构造了");
}

//拓展方法
public string ReturnStr(string str)
{
return str + GetType().Name.ToString();
}

public void Show()
{
Console.WriteLine(GetType().Name + "Show");
}
}
}

InterfaceA 层

1
2
3
4
5
6
7
8
9
namespace DotNetCoreDemo.Interface
{
public interface ITestServiceA
{
void Show();

string ReturnStr(string str);
}
}

ServiceB层

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
using DotNetCoreDemo.Interface;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace DotNetCoreDemo.Service
{
public class TestServiceB : ITestServiceB
{
private readonly ITestServiceA _testServiceA = null;

/// <summary>
/// TestServiceB 依赖于 TestServiceA
/// </summary>
/// <param name="testServiceA"></param>
/// 到这,testServiceA 已经传递过来了。这个时候,TestServiceB 的方法中就可以使用TestServerA中的内容
public TestServiceB(ITestServiceA testServiceA)
{
_testServiceA = testServiceA;

Console.WriteLine(this.GetType().Name + "构造了");
}

public void Show()
{
_testServiceA.Show();

Console.WriteLine(this.GetType().Name + "Show");
}

public string ReturnStr()
{
//获取接口方法A的值
string str = _testServiceA.ReturnStr("123");

string str_ = str + this.GetType().Name.ToString();
//返回
return str_;
}
}
}

InterfaceB层

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace DotNetCoreDemo.Interface
{
public interface ITestServiceB
{
void Show();

string ReturnStr();
}
}

Startup 注入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public void ConfigureServices(IServiceCollection services)
{
#region IOC注册服务

services.AddTransient<ITestServiceA, TestServiceA>();
services.AddTransient<ITestServiceB, TestServiceB>();//注入 TestServiceB

#endregion

services.AddControllersWithViews();

services.AddSession();

//加入中间件,方便实时调试HTML
services.AddRazorPages().AddRazorRuntimeCompilation();

//注入服务
services.AddTransient<ICoustomInterface, CoustomService>();

//取配置文件中的TEST节点,映射到 TESTMODEL 实体
services.Configure<TESTMODEL>(Configuration.GetSection("TEST"));
}
    上述,呈现出的,就是ServiceB 依赖 ServiceA ,在 ServiceB 的构造方法中,传入 ServiceA ,然后再 ServiceB 中,调用 ServiceA 的方法,构成了ServiceB ReturnStr 方法结果的呈现。应该能诠释 依赖注入的概念了,以此类推,可以衍生出 各种依赖。不怕注册的多服务,尽管上。

IServiceCollection 生命周期

  1. 瞬时生命周期 Addtransient 注册的生命周期:每次都实例化一个新的。正常瞬时生命周期使用的多,每次一个对象

    image-20210828145713938

  2. 单例生命周期:AddSingleton 注册的生命周期:serviceA2_1 走过一遍后,serviceA2_2 直接就过去了,所以这玩意注册,一直玩的就一个。

    image-20210828145845491

  3. 作用域生命周期

    同一个IServiceCollection ,不同的ServiceProvider 实例化出来的对象,在每个ServiceProvider 内实例化的对象相当于单例,但是不同的 ServiceProvider 之间 是不一样的。

    image-20210828151007374

  4. Code

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    public void ConfigureServices(IServiceCollection services)
    {
    #region IOC注册服务

    services.AddTransient<ITestServiceA, TestServiceA>();
    services.AddTransient<ITestServiceB, TestServiceB>();

    #endregion

    #region 服务控制器 生命周期

    #region 瞬时生命周期 每次获取的实例都是不同的实例

    IServiceCollection services1 = new ServiceCollection();

    services1.AddTransient<ITestServiceA, TestServiceA>();

    ServiceProvider serviceProvider = services1.BuildServiceProvider();

    ITestServiceA serviceA1_1 = serviceProvider.GetService<ITestServiceA>();

    ITestServiceA serviceA1_2 = serviceProvider.GetService<ITestServiceA>();
    //比较上述这俩实例是不是同一个
    bool IsOk1 = object.ReferenceEquals(serviceA1_1, serviceA1_2);

    #endregion

    #region 单例生命周期 每次获取的时候都是同一个实例

    IServiceCollection services2 = new ServiceCollection();

    services2.AddSingleton<ITestServiceA, TestServiceA>();

    ServiceProvider serviceProvider2 = services2.BuildServiceProvider();

    ITestServiceA serviceA2_1 = serviceProvider2.GetService<ITestServiceA>();

    ITestServiceA serviceA2_2 = serviceProvider2.GetService<ITestServiceA>();
    //比较上述这俩实例是不是同一个
    bool IsOk2 = object.ReferenceEquals(serviceA2_1, serviceA2_2);

    #endregion

    #region 作用域生命周期

    IServiceCollection services3 = new ServiceCollection();

    services3.AddScoped<ITestServiceA, TestServiceA>();

    ServiceProvider serviceProvider3_1 = services3.BuildServiceProvider();

    ITestServiceA serviceA3_1 = serviceProvider3_1.GetService<ITestServiceA>();

    ITestServiceA serviceA3_2 = serviceProvider3_1.GetService<ITestServiceA>();
    //比较上述这俩实例是不是同一个
    bool IsOk3 = object.ReferenceEquals(serviceA3_1, serviceA3_2);

    ServiceProvider serviceProvider3_2 = services3.BuildServiceProvider();

    ITestServiceA serviceA3_3 = serviceProvider3_2.GetService<ITestServiceA>();

    ITestServiceA serviceA3_4 = serviceProvider3_2.GetService<ITestServiceA>();

    bool IsOk4 = object.ReferenceEquals(serviceA3_3, serviceA3_4);

    bool IsOk5 = object.ReferenceEquals(serviceA3_1, serviceA3_3);

    #endregion

    #endregion



    services.AddControllersWithViews();

    services.AddSession();

    //加入中间件,方便实时调试HTML
    services.AddRazorPages().AddRazorRuntimeCompilation();

    //注入服务
    services.AddTransient<ICoustomInterface, CoustomService>();

    //取配置文件中的TEST节点,映射到 TESTMODEL 实体
    services.Configure<TESTMODEL>(Configuration.GetSection("TEST"));
    }

Autofac 应用

初识 Autofac

Autofac 是一款第三方,很流行的 IOC容器,应用方法如下:

  1. Nuget 获取 Autofac 组件。
  2. 创建一个ContainerBuilder 对象
  3. 注册抽象和实现方法 格式:builder.RegisterType<实现方法>().As<抽象接口>();
  4. Build 得到 container 容器
  5. 管 container 容器 要 ITestServiceA 服务
  6. 应用 要到的 ITestServiceA 服务
1
2
3
4
5
6
7
8
9
10
11
12
13
#region Autofac 应用
//1. Nuget Autofac
//2. 创建一个ContainerBuilder 对象
ContainerBuilder builder = new ContainerBuilder();
//3. 注册抽象和实现方法 格式:builder.RegisterType<实现方法>().As<ITestServiceA>();
builder.RegisterType<TestServiceA>().As<ITestServiceA>();
//4. Build 得到 container 容器
IContainer container = builder.Build();
//5. 管 container 容器 要 ITestServiceA 服务
ITestServiceA testServiceA = container.Resolve<ITestServiceA>();//获取服务
//6. 应用 要到的 ITestServiceA 服务
testServiceA.Show();
#endregion

Autofac 多种注册方式

第一种:构造函数注入

1
2
3
4
5
6
7
ContainerBuilder builder1 = new ContainerBuilder();
builder1.RegisterType<TestServiceA>().As<ITestServiceA>();
builder1.RegisterType<TestServiceB>().As<ITestServiceB>();
builder1.RegisterType<TestServiceC>().As<ITestServiceC>();
IContainer container1 = builder1.Build();
ITestServiceC testServiceC1 = container1.Resolve<ITestServiceC>();
testServiceC1.Show();

这没啥说的,基本操作。

第二种:属性注入

1
2
3
4
5
6
7
8
ContainerBuilder builder2 = new ContainerBuilder();
builder2.RegisterType<TestServiceA>().As<ITestServiceA>();
builder2.RegisterType<TestServiceB>().As<ITestServiceB>();
builder2.RegisterType<TestServiceC>().As<ITestServiceC>();
builder2.RegisterType<TestServiceD>().As<ITestServiceD>().PropertiesAutowired();
IContainer container2 = builder2.Build();
ITestServiceD testServiceD = container2.Resolve<ITestServiceD>();
testServiceD.Show();

关注一下TestServiceD 里,还有 PropertiesAutowired()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
using DotNetCoreDemo.Interface;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace DotNetCoreDemo.Service
{
public class TestServiceD : ITestServiceD
{
public ITestServiceA testServiceA { get; set; }
public ITestServiceB testServiceB { get; set; }
public ITestServiceC testServiceC { get; set; }

public TestServiceD()
{
Console.WriteLine(this.GetType().Name + "构造了");
}

public void Show()
{
//基于TestService ABC 都注册了,在执行下面这行代码的时候 才会去Set属性值,会挨个都执行ABC的构造函数。
testServiceA.Show();
Console.WriteLine(this.GetType().Name + "Show");
}
}
}

第三种:方法注入

1
2
3
4
5
6
7
8
ContainerBuilder builder3 = new ContainerBuilder();
builder3.RegisterType<TestServiceA>().As<ITestServiceA>();
builder3.RegisterType<TestServiceB>().As<ITestServiceB>();
builder3.RegisterType<TestServiceC>().OnActivated(e => e.Instance.SetService(e.Context.Resolve<ITestServiceA>())).As<ITestServiceC>();
builder3.RegisterType<TestServiceD>().As<ITestServiceD>().PropertiesAutowired();
IContainer container3 = builder3.Build();
ITestServiceC testServiceC = container3.Resolve<ITestServiceC>();
testServiceC.Show();

这个地方需要关注两点:

  1. 方法注入种 TestServiceC的注入方式与AB注入方式的差异OnActivated

  2. 上述不同的注册方式是需要结合下面的代码使用,注意 SetService方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    using DotNetCoreDemo.Interface;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;

    namespace DotNetCoreDemo.Service
    {
    public class TestServiceC : ITestServiceC
    {
    private ITestServiceA _testServiceA = null;

    public void SetService(ITestServiceA testServiceA)
    {
    _testServiceA = testServiceA;
    }

    public TestServiceC(ITestServiceA serviceA)
    {
    _testServiceA = serviceA;

    Console.WriteLine(this.GetType().Name + "构造了");
    }

    public void Show()
    {
    _testServiceA.Show();

    Console.WriteLine(this.GetType().Name + "Show");
    }
    }
    }

Autofac 生命周期

第一种:瞬时生命周期(InstancePerDependency)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#region 瞬时生命周期 (InstancePerDependency)
{
ContainerBuilder builder4 = new ContainerBuilder();

builder4.RegisterType<TestServiceA>().As<ITestServiceA>().InstancePerDependency();

IContainer container4 = builder4.Build();

ITestServiceA testServiceA1 = container4.Resolve<ITestServiceA>();
ITestServiceA testServiceA2 = container4.Resolve<ITestServiceA>();

Console.WriteLine("Autofac 生命周期-瞬时:" + object.ReferenceEquals(testServiceA1, testServiceA2));
}
#endregion

第二种:单例生命周期(SingleInstance)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#region 单例生命周期(SingleInstance)
{
ContainerBuilder builder4 = new ContainerBuilder();

builder4.RegisterType<TestServiceA>().As<ITestServiceA>().SingleInstance();

IContainer container4 = builder4.Build();

ITestServiceA testServiceA1 = container4.Resolve<ITestServiceA>();
ITestServiceA testServiceA2 = container4.Resolve<ITestServiceA>();

Console.WriteLine("Autofac 生命周期-单例:" + object.ReferenceEquals(testServiceA1, testServiceA2));
}
#endregion

第三种:作用域生命周期(InstancePerLifetimeScope)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#region 作用域生命周期(InstancePerLifetimeScope)
{
ContainerBuilder builder4 = new ContainerBuilder();

builder4.RegisterType<TestServiceA>().As<ITestServiceA>().InstancePerLifetimeScope();

IContainer container4 = builder4.Build();

ITestServiceA testServiceA5 = null;

ITestServiceA testServiceA6 = null;

using (var Scope = container4.BeginLifetimeScope())
{
ITestServiceA testServiceA1 = Scope.Resolve<ITestServiceA>();

ITestServiceA testServiceA2 = Scope.Resolve<ITestServiceA>();

Console.WriteLine("Autofac 生命周期-作用域1-1:" + object.ReferenceEquals(testServiceA1, testServiceA2));//True

testServiceA5 = testServiceA2;
}

using (var Scope = container4.BeginLifetimeScope())
{
ITestServiceA testServiceA3 = Scope.Resolve<ITestServiceA>();

ITestServiceA testServiceA4 = Scope.Resolve<ITestServiceA>();

Console.WriteLine("Autofac 生命周期-作用域2-2:" + object.ReferenceEquals(testServiceA3, testServiceA4));//True

testServiceA6 = testServiceA4;
}

Console.WriteLine("Autofac 生命周期-作用域1-2:" + object.ReferenceEquals(testServiceA5, testServiceA6));//False
}
#endregion

第三种扩展:加参数标记

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#region 作用域生命周期-扩展(InstancePerLifetimeScope+参数标记)
{
ContainerBuilder builder4 = new ContainerBuilder();

builder4.RegisterType<TestServiceA>().As<ITestServiceA>().InstancePerLifetimeScope();

IContainer container4 = builder4.Build();

ITestServiceA testServiceA5 = null;

ITestServiceA testServiceA6 = null;

using (var Scope = container4.BeginLifetimeScope("Harris"))
{
ITestServiceA testServiceA1 = Scope.Resolve<ITestServiceA>();

using (var Scope1 = container4.BeginLifetimeScope())
{
ITestServiceA testServiceA2 = Scope.Resolve<ITestServiceA>();

Console.WriteLine("Autofac 生命周期-作用域扩展1:" + object.ReferenceEquals(testServiceA1, testServiceA2));
}

testServiceA5 = testServiceA1;
}

using (var Scope = container4.BeginLifetimeScope("Harris"))
{
ITestServiceA testServiceA1 = Scope.Resolve<ITestServiceA>();

using (var Scope1 = container4.BeginLifetimeScope())
{
ITestServiceA testServiceA2 = Scope.Resolve<ITestServiceA>();

Console.WriteLine("Autofac 生命周期-作用域扩展2:" + object.ReferenceEquals(testServiceA1, testServiceA2));
}

testServiceA6 = testServiceA1;
}

Console.WriteLine("Autofac 生命周期-作用域扩展3:" + object.ReferenceEquals(testServiceA5, testServiceA6));
}
#endregion

image-20210829154658355

Autofac 读取配置文件注册

  1. Nuget 获取

    • Autofac.Extensions.DependencyInjection
    • Autofac.Configuration
  2. 准备配置文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    {
    "components": [
    {
    "type": "DotNetCoreDemo.Service.TestServiceA,DotNetCoreDemo.Service",
    "services": [
    {
    "type": "DotNetCoreDemo.Interface.ITestServiceA,DotNetCoreDemo.Interface"
    }
    ],
    "instanceScope": "single-instance", //生命周期
    "injectProperties": true // 是否支持属性注入
    },
    {
    "type": "DotNetCoreDemo.Service.TestServiceB,DotNetCoreDemo.Service",
    "services": [
    {
    "type": "DotNetCoreDemo.Interface.ITestServiceB,DotNetCoreDemo.Interface"
    }
    ],
    "instanceScope": "single-instance", //生命周期
    "injectProperties": true // 是否支持属性注入
    },
    {
    "type": "DotNetCoreDemo.Service.TestServiceC,DotNetCoreDemo.Service",
    "services": [
    {
    "type": "DotNetCoreDemo.Interface.ITestServiceC,DotNetCoreDemo.Interface"
    }
    ],
    "instanceScope": "single-instance", //生命周期
    "injectProperties": true // 是否支持属性注入
    },
    {
    "type": "DotNetCoreDemo.Service.TestServiceD,DotNetCoreDemo.Service",
    "services": [
    {
    "type": "DotNetCoreDemo.Interface.ITestServiceD,DotNetCoreDemo.Interface"
    }
    ],
    "instanceScope": "single-instance", //生命周期
    "injectProperties": true // 是否支持属性注入
    }
    ]
    }
  3. 读取配置文件,通过Autofac 配置文件实现注册抽象服务和具体方法。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    #region Autofac 读取配置文件注册服务
    {
    ContainerBuilder builder = new ContainerBuilder();

    IConfigurationBuilder config = new ConfigurationBuilder();

    IConfigurationSource source = new JsonConfigurationSource()
    {
    Path = "CfgFile/Autofac.json",
    Optional = false,//默认是false ,可以不写
    ReloadOnChange = true//默认是true,可不写
    };

    config.Add(source);

    ConfigurationModule module = new ConfigurationModule(config.Build());

    builder.RegisterModule(module);

    IContainer container = builder.Build();

    ITestServiceA testServiceA = container.Resolve<ITestServiceA>();

    testServiceA.Show();

    ITestServiceD testServiceD = container.Resolve<ITestServiceD>();

    testServiceD.Show();
    }

    #endregion
  4. 有了Autofac 配置文件就可以灵活的调整服务(interface)与服务本身(Service)中的关系。

    举个例子:如果现在有个TestServiceE,依赖于ItestServiceA

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    using DotNetCoreDemo.Interface;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;

    namespace DotNetCoreDemo.Service
    {
    public class TestServiceE : ITestServiceA
    {
    public TestServiceE()
    {
    Console.WriteLine(this.GetType().Name + "构造了");
    }

    public string ReturnStr(string str)
    {
    return str + GetType().Name.ToString();
    }

    public void Show()
    {
    Console.WriteLine(GetType().Name + "Show");
    }
    }
    }

    然后刚才实现读取配置的代码中,ItestServiceA 想用的是 TestServiceE 的实现方法。只需要修改配置文件即可。

    修改前:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    {
    "type": "DotNetCoreDemo.Service.TestServiceA,DotNetCoreDemo.Service",
    "services": [
    {
    "type": "DotNetCoreDemo.Interface.ITestServiceA,DotNetCoreDemo.Interface"
    }
    ],
    "instanceScope": "single-instance", //生命周期
    "injectProperties": true // 是否支持属性注入
    },

    修改后:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    {
    "type": "DotNetCoreDemo.Service.TestServiceE,DotNetCoreDemo.Service",
    "services": [
    {
    "type": "DotNetCoreDemo.Interface.ITestServiceA,DotNetCoreDemo.Interface"
    }
    ],
    "instanceScope": "single-instance", //生命周期
    "injectProperties": true // 是否支持属性注入
    },

    image-20210829182152021

Autofac 整合到MVC

  1. Autofac 是一个第三方容器,需要在Program 中告诉框架,要是使用哪个IOC工厂。(AutofacServiceProviderFactory)

    1
    2
    3
    4
    5
    6
    7
    public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
    .ConfigureWebHostDefaults(webBuilder =>
    {
    webBuilder.UseStartup<Startup>();
    })
    .UseServiceProviderFactory(new AutofacServiceProviderFactory());
  2. Startup 文件中新增方法,这个方法被 Autofac 承包了,是个默认执行的方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    /// <summary>
    /// Autofac 此方法会在加载的时候默认执行
    ///
    /// 有了此方法,不意味着原生的注册方法不灵了,原生的注册方法照样好使
    ///
    /// 注意:在Autofac注册的时候,会将原生的注册方法都接管过来。
    /// </summary>
    /// <param name="container"></param>
    public void ConfigureContainer(ContainerBuilder container)
    {
    //前三个注册服务交给原生注册方法
    //container.RegisterType<TestServiceA>().As<ITestServiceA>();
    //container.RegisterType<TestServiceB>().As<ITestServiceB>();
    //container.RegisterType<TestServiceC>().As<ITestServiceC>();
    //这个给Autofac注册,关注AutofaCollection 知否正常执行
    container.RegisterType<TestServiceD>().As<ITestServiceD>().PropertiesAutowired();
    }
  3. 需要注意:

    1. ConfigureContainer() 方法是默认执行的;
    2. 有了此方法,不意味着原生的注册方法不灵了,原生的注册方法照样好使;
    3. 在Autofac注册的时候,会将原生的注册方法都接管过来,这样在使用时候就连贯了。

Autofac 支持控制器属性注入

  1. 指定控制器的实例让容器来创建 – Startup ConfigureServices()

    1
    2
    3
    4
    5
    #region 指定控制器的实例让容器来创建,告诉框架,要使用Autofac容器来创建控制器的实例。

    services.Replace(ServiceDescriptor.Transient<IControllerActivator, ServiceBasedControllerActivator>());

    #endregion
  2. 注册所有控制器的关系+控制器实例化所需要的所有组件 – Startup ConfigureContainer()

    1
    2
    3
    4
    5
    6
    7
    #region 注册所有控制器的关系+控制器实例化所需要的所有组件

    Type[] controllerTypesInAssmbly = typeof(Startup).Assembly.GetExportedTypes().Where(type => typeof(ControllerBase).IsAssignableFrom(type)).ToArray();

    //注册属性,具体注册哪些属性,交给 CustomPropertySelector()
    //CustomPropertySelector()方法会通过CustomPropertyAttribute这个特性去查找、返回
    container.RegisterTypes(controllerTypesInAssmbly).PropertiesAutowired(new CustomPropertySelector());
  3. CustomPropertySelector 帮助框架去查找符合条件的属性

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    using Autofac.Core;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Reflection;
    using System.Threading.Tasks;

    namespace DotNetCoreDemo.Utility
    {
    public class CustomPropertySelector : IPropertySelector
    {
    public bool InjectProperty(PropertyInfo propertyInfo, object instance)
    {
    //需要一个判断维度,只有满足属性是CustomPropertyAttribute的才能返回 true
    return propertyInfo.CustomAttributes.Any(it => it.AttributeType == typeof(CustomPropertyAttribute));
    }
    }
    }
  4. CustomPropertyAttribute 自定义特性

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading.Tasks;

    namespace DotNetCoreDemo.Utility
    {
    //添加特性类,并且标注这个是专门用在属性上的
    [AttributeUsage(AttributeTargets.Property)]
    public class CustomPropertyAttribute : Attribute
    {

    }
    }
  5. 监控判断属性

    image-20210829200529269

    判断 _ItestServiceAA 结果为 true ,因为它标注属性了

    image-20210829201047537

    反之 _ItestServiceBB 返回 false

    image-20210829200904042

Autofac 抽象多实现的问题1

例如:一个Interface 多个服务实现 ,一对多。

image-20210830232226520

遇到这种情况,在注册的时候就会有问题,尤其是在注册完应用的时候。

第一种:

1
2
3
4
5
6
#region 抽象多实现问题

container.RegisterType<TestServiceA>().As<ITestServiceA>();
container.RegisterType<TestServiceE>().As<ITestServiceA>();

#endregion

结果就是

image-20210830232929753

结论就是:

在同一个接口下多个对象的时候,最后注册的那个对象才会生效。

第二种:

使用 IEnumerable<ITestServiceA> 方式,返回多个。

image-20210830233558148

第三种:

Startup 中注册所有与ItestServiceA相关的对象

1
container.RegisterSource(new AnyConcreteTypeNotAlreadyRegisteredSource(t => t.IsAssignableTo<ITestServiceA>()));

然后如下图:直接实例化对象(不是Interface),然后通过构造函数接下来使用。

image-20210830234629332

扩展一下:

新建 AutofacModule.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
using Autofac;
using Autofac.Features.ResolveAnything;
using DotNetCoreDemo.Interface;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace DotNetCoreDemo.Utility
{
public class AutofacModule : Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterSource(new AnyConcreteTypeNotAlreadyRegisteredSource(t => t.IsAssignableTo<ITestServiceA>()));
}
}
}

然后修改 Startup container.RegisterModule(new AutofacModule());

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#region 抽象多实现问题

////1. 正常操作 在同一个接口下多个对象的时候,最后注册的那个对象才会生效。
container.RegisterType<TestServiceA>().As<ITestServiceA>();
container.RegisterType<TestServiceE>().As<ITestServiceA>();

////2. 使用 `IEnumerable<ITestServiceA>` 方式,返回多个。

////3. 第三种方式如下,需要在控制器中构造函数承接对象。
//container.RegisterSource(new AnyConcreteTypeNotAlreadyRegisteredSource(t => t.IsAssignableTo<ITestServiceA>()));

container.RegisterModule(new AutofacModule());

#endregion

这样的好处,就是可以分模块注册服务。

Autofac 支持 AOP

AOP 面向切片编程:在不修改原来代码逻辑的基础上,可以动态的在某个动作之前或者之后执行某些方法。

  1. 主程序 Nugget 引入 Castle.Core and Castle.DynamicProxy

  2. 新建AutoAop 扩展类,建议这个类不要新建在主程序中,可能会引起服务互相依赖的后果。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    using Castle.DynamicProxy;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;

    namespace DotNetCoreDemo.Common.AutofacExtension
    {
    public class CustomAutofacAOP : IInterceptor
    {
    public void Intercept(IInvocation invocation)
    {
    Console.WriteLine("执行Show前干点事");

    invocation.Proceed();

    Console.WriteLine("执行Show后干点事");

    }
    }
    }
  3. 在Interface 层想管的类上添加特性 [Intercept(typeof(CustomAutofacAOP))],为了AOP能在当前接口生效。也得引入 Castle.DynamicProxy

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    using Autofac.Extras.DynamicProxy;
    using DotNetCoreDemo.Common.AutofacExtension;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;

    namespace DotNetCoreDemo.Interface
    {
    [Intercept(typeof(CustomAutofacAOP))]//为了AOP能在当前接口生效
    public interface ITestServiceA
    {
    void Show();

    string ReturnStr(string str);
    }
    }
  4. 需要在 Startup.cs 中注册 container.RegisterType(typeof(CustomAutofacAOP));

    1
    2
    3
    4
    5
    #region Autofac 支持 AOP
    container.RegisterType(typeof(CustomAutofacAOP));//注册
    //EnableInterfaceInterceptors() 告诉框架这个要支持AOP
    container.RegisterType<TestServiceA>().As<ITestServiceA>().EnableInterfaceInterceptors();//接口式支持AOP
    #endregion
  5. 应用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    using DotNetCoreDemo.Interface;
    using DotNetCoreDemo.Utility;
    using Microsoft.AspNetCore.Mvc;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading.Tasks;

    namespace DotNetCoreDemo.Controllers
    {
    public class FourController : Controller
    {
    [CustomProperty]
    private ITestServiceA _ItestServiceAA { get; set; }

    private readonly ITestServiceA _ItestServiceA = null;

    public FourController(ITestServiceA ItestServiceA, ITestServiceA testServiceA)
    {
    _ItestServiceAA = testServiceA;

    _ItestServiceA = ItestServiceA;
    }

    public IActionResult Index()
    {
    _ItestServiceAA.Show();

    _ItestServiceA.Show();

    return View();
    }
    }
    }
  6. 结果

    image-20210901221357225

Autofac 支持 AOP2

EnableInterfaceInterceptors 支持 Interface中标记[Intercept(typeof(CustomAutofacAOP))],只要实现这个就可以实现AOP

EnableClassInterceptors 支持 Service 中标记 [Intercept(typeof(CustomAutofacAOP))] ,只有标记这个特性,才能支持AOP。

EnableClassInterceptors 还需要 设置 虚方法 virtual

注意:如果同时使用EnableInterfaceInterceptors 就会重复执行AOP.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
using Autofac.Extras.DynamicProxy;
using DotNetCoreDemo.Common.AutofacExtension;
using DotNetCoreDemo.Interface;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace DotNetCoreDemo.Service
{
//标记特性
[Intercept(typeof(CustomAutofacAOP))]//为了AOP能在当前接口生效
public class TestServiceA : ITestServiceA
{
public TestServiceA()
{
Console.WriteLine(this.GetType().Name + "构造了");
}

public string ReturnStr(string str)
{
return str + GetType().Name.ToString();
}

/// <summary>
/// 标记虚方法
/// </summary>
public virtual void Show()
{
Console.WriteLine(GetType().Name + "Show");
}
}
}

image-20210902225117135

Autofac 抽象多个实现构造函数注入

  1. 先上一波传统手艺

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    //在 方法 :ConfigureServices 中
    #region 一抽象多个实现问题
    {
    ContainerBuilder builder = new ContainerBuilder();

    builder.RegisterType<TestServiceA>().Named<ITestServiceA>("TestServiceA");
    builder.RegisterType<TestServiceE>().Named<ITestServiceA>("TestServiceE");

    IContainer container = builder.Build();

    ITestServiceA testServiceA1 = container.ResolveKeyed<ITestServiceA>("TestServiceA");
    ITestServiceA testServiceA2 = container.ResolveKeyed<ITestServiceA>("TestServiceE");

    Console.WriteLine("一抽象多个实现问题:" + object.ReferenceEquals(testServiceA1, testServiceA2));
    }

    注意 NamedResolveKeyed 搭配使用更丝滑。

    image-20210903214211926

  2. 在通过AutofacIOC 实现一波

    Startup.cs - ConfigureContainer 注册 ,注意 Named ,同时实现AOP

    1
    2
    3
    4
    5
    #region 一抽象多实现问题2
    {
    container.RegisterType<TestServiceA>().Named<ITestServiceA>("TestServiceA").EnableClassInterceptors();
    container.RegisterType<TestServiceE>().Named<ITestServiceA>("TestServiceE").EnableClassInterceptors();
    }

    应用:

    1
    2
    ITestServiceA obja = _componentContext.ResolveNamed<ITestServiceA>("TestServiceA");
    ITestServiceA objb = _componentContext.ResolveNamed<ITestServiceA>("TestServiceE");

    全文:需要注意注册 Autofac 上下文

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    using Autofac;
    using DotNetCoreDemo.Interface;
    using DotNetCoreDemo.Utility;
    using Microsoft.AspNetCore.Mvc;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading.Tasks;

    namespace DotNetCoreDemo.Controllers
    {
    public class FourController : Controller
    {
    [CustomProperty]
    private ITestServiceA _ItestServiceAA { get; set; }

    /// <summary>
    /// Autofac 上下文
    /// </summary>
    private IComponentContext _componentContext = null;

    public FourController(IComponentContext componentContext)
    {
    _componentContext = componentContext;
    }

    public IActionResult Index()
    {
    if (_ItestServiceAA != null)
    {
    _ItestServiceAA.Show();
    }

    ITestServiceA obja = _componentContext.ResolveNamed<ITestServiceA>("TestServiceA");
    ITestServiceA objb = _componentContext.ResolveNamed<ITestServiceA>("TestServiceE");

    obja.Show();
    objb.Show();

    return View();
    }
    }
    }

    image-20210904135936278

    image-20210904140002918

Autofac 抽象多个实现属性注入

其他与一个抽象多个实现构造函数注入一样

1
2
3
4
5
6
//首先,把属性注入
[CustomProperty]
private IComponentContext componentContextprop { get; set; }

ITestServiceA objc = componentContextprop.ResolveNamed<ITestServiceA>("TestServiceA");
ITestServiceA objd = componentContextprop.ResolveNamed<ITestServiceA>("TestServiceE");

全文:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
using Autofac;
using DotNetCoreDemo.Interface;
using DotNetCoreDemo.Utility;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace DotNetCoreDemo.Controllers
{
public class FourController : Controller
{
[CustomProperty]
private ITestServiceA _ItestServiceAA { get; set; }

[CustomProperty]
private IComponentContext componentContextprop { get; set; }

/// <summary>
/// Autofac 上下文
/// </summary>
private IComponentContext _componentContext = null;

public FourController(IComponentContext componentContext)
{
_componentContext = componentContext;
}

public IActionResult Index()
{
if (_ItestServiceAA != null)
{
_ItestServiceAA.Show();
}

ITestServiceA obja = _componentContext.ResolveNamed<ITestServiceA>("TestServiceA");
ITestServiceA objb = _componentContext.ResolveNamed<ITestServiceA>("TestServiceE");

obja.Show();
objb.Show();

ITestServiceA objc = componentContextprop.ResolveNamed<ITestServiceA>("TestServiceA");
ITestServiceA objd = componentContextprop.ResolveNamed<ITestServiceA>("TestServiceE");

objc.Show();
objd.Show();

return View();
}
}
}

Filter

AOP :可以在不修改之前的代码的情况下动态增加新的功能。

Filter : 过滤器 ActionFilter 即动作过滤器

ActionFilter+AOP

  1. 新建 CustomActionFilterAttrubute.cs 特性,并且继承 IActionFilter 接口 和实现 IActionFilter 接口

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    using Microsoft.AspNetCore.Mvc.Filters;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading.Tasks;

    namespace DotNetCoreDemo.Utility
    {
    public class CustomActionFilterAttribute : Attribute, IActionFilter
    {

    public void OnActionExecuting(ActionExecutingContext context)
    {
    Console.WriteLine("OnActionExecuting 执行");
    }


    public void OnActionExecuted(ActionExecutedContext context)
    {
    Console.WriteLine("OnActionExecuted 执行");
    }
    }
    }
  2. 控制器类 需要在对应的Action 方法上标记 [CustomActionFilterAttribute]特性

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    using DotNetCoreDemo.Utility;
    using Microsoft.AspNetCore.Mvc;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading.Tasks;

    namespace DotNetCoreDemo.Controllers
    {
    public class FiveController : Controller
    {
    public FiveController()
    {
    Console.WriteLine("FiveController 被构造");
    }

    [CustomActionFilterAttribute]
    public IActionResult Index()
    {
    return View();
    }
    }
    }

    如下图,执行顺序为:

    1. CustomActionFilterAttribute - OnActionExecuting
    2. Action
    3. CustomActionFilterAttribute - OnActionExecuted

    image-20210904154410691

ActionFilter 多种实现

  1. 通过继承 IActionFilter 实现接口来实现具体前后的功能。
  2. 通过继承 ActionFilterAttribute 通过override 重写方法。(系统框架提供)
  3. 通过继承 IAnsyncActionFilter 实现接口实现,此为异步版本。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
using Microsoft.AspNetCore.Mvc.Filters;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace DotNetCoreDemo.Utility
{
/// <summary>
/// 自定义版本
/// </summary>
public class CustomActionFilterAttribute : Attribute, IActionFilter
{
public void OnActionExecuting(ActionExecutingContext context)
{
Console.WriteLine("OnActionExecuting 执行");
}

public void OnActionExecuted(ActionExecutedContext context)
{
Console.WriteLine("OnActionExecuted 执行");
}
}

/// <summary>
/// 提供提供版本
/// </summary>
public class CustomActionFilterChildeAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
Console.WriteLine(" OnActionExecuting 执行");
}

public override void OnActionExecuted(ActionExecutedContext context)
{
Console.WriteLine("OnActionExecuted 执行");
}
}

/// <summary>
/// 异步版本 这个后面再研究
/// </summary>
public class CustomActionFilterAsyncAttribute : IAsyncActionFilter
{
public Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
return Task.Run(() =>
{

});
}
}
}

ActionFilter 做日志

  1. 先上结果。

    1. 先写构造方法
    2. 执行Action-Index 之前执行操作
    3. 执行Action-Index
    4. 执行Action-Index 之后执行操作

    image-20210904204835244

  2. 自定义版本中写的,需要注意Ilogger 的注册,还有就是context的各种扩展功能。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    /// <summary>
    /// 自定义版本
    /// </summary>
    public class CustomActionFilterAttribute : Attribute, IActionFilter
    {
    public readonly ILogger<CustomActionFilterAttribute> _logger = null;

    public CustomActionFilterAttribute(ILogger<CustomActionFilterAttribute> logger)
    {
    Console.WriteLine("CustomActionFilterAttribute 构造");

    _logger = logger;
    }

    public void OnActionExecuting(ActionExecutingContext context)
    {
    _logger.LogInformation("OnActionExecuting 开始");

    _logger.LogInformation(Newtonsoft.Json.JsonConvert.SerializeObject(context.HttpContext.Request.Query));

    _logger.LogInformation("OnActionExecuting 结束");
    }

    public void OnActionExecuted(ActionExecutedContext context)
    {
    _logger.LogInformation("OnActionExecuted 开始");

    _logger.LogInformation(Newtonsoft.Json.JsonConvert.SerializeObject(context.Result));

    _logger.LogInformation("OnActionExecuted 结束");
    }
    }
  3. 控制器代码

    这里需要注意的是,由于修改了上述的代码,导致给Action特性[CustomActionFilterAttribute]会报错,所以将Action特性修改为[TypeFilter(typeof(CustomActionFilterAttribute))] 为的是让CustomActionFilterAttribute 可以支持依赖注入。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    using DotNetCoreDemo.Utility;
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.Extensions.Logging;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading.Tasks;

    namespace DotNetCoreDemo.Controllers
    {
    public class FiveController : Controller
    {
    private ILogger<FiveController> _ilogger = null;

    public FiveController(ILogger<FiveController> logger)
    {
    _ilogger = logger;

    _ilogger.LogInformation("FiveController 被构造");
    }

    //[CustomActionFilterAttribute]
    //[CustomActionFilterChildeAttribute]

    [TypeFilter(typeof(CustomActionFilterAttribute))]
    public IActionResult Index()
    {
    _ilogger.LogInformation("执行FiveController Index Action");

    return View();
    }
    }
    }

Filter 多种注册

  1. CustomActionFilterAttribute] 必须是无参构造函数才行,可以结合上一节内容

  2. [TypeFilter(typeof(CustomActionFilterAttribute))] 可以无参构造函数,可以支持依赖注入

  3. [ServiceFilter(typeof(CustomActionFilterAttribute))] 可以无参构造函数,可以支持依赖注入,但是必须注册服务

    Startup.cs

    1
    2
    //注册自定义Filter扩展,且支持属性注入(PropertiesAutowired)
    container.RegisterType(typeof(CustomActionFilterAttribute)).PropertiesAutowired();

    CustomActionFilterAttribute

    1
    2
    3
    4
    5
    //属性
    public ILogger<CustomActionFilterAttribute> loggerProp { get; set; }

    //应用,别写在构造函数里
    loggerProp.LogInformation("CustomActionFilterAttribute 支持属性注入");

FilterFactory 扩展

本节相当于是对 Filter 多种注册中 TypeFilter 与 ServiceFilter 为什么可以支持依赖注册的递进理解,因为一定是IOC在后面站台。

把 TypeFilter 与 ServiceFilter 后面的大哥 IFilterFactory 提溜出来盘盘。

  1. 首先先创建一个自定义的类 CustomActionFilterFactory, 继承Attribute 实现 IFilterFactory 接口

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    using Microsoft.AspNetCore.Mvc.Filters;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading.Tasks;

    namespace DotNetCoreDemo.Utility.Filter
    {
    public class CustomActionFilterFactory : Attribute, IFilterFactory
    {
    public readonly Type _type = null;

    public CustomActionFilterFactory(Type type)
    {
    _type = type;
    }

    public bool IsReusable => true;

    public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
    {
    object oInstance = serviceProvider.GetService(_type);

    return (IFilterMetadata)oInstance;
    }
    }
    }
  2. 然后,替换 TypeFilter 和 ServiceFilter 的地位,将 CustomActionFilterFactory 特性用上。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    using DotNetCoreDemo.Utility;
    using DotNetCoreDemo.Utility.Filter;
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.Extensions.Logging;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading.Tasks;

    namespace DotNetCoreDemo.Controllers
    {
    public class FiveController : Controller
    {

    private ILogger<FiveController> _ilogger = null;

    public FiveController(ILogger<FiveController> logger)
    {
    _ilogger = logger;

    _ilogger.LogInformation("FiveController 被构造");
    }

    //[CustomActionFilterAttribute]
    //[CustomActionFilterChildeAttribute]
    //[TypeFilter(typeof(CustomActionFilterAttribute))]
    //[ServiceFilter(typeof(CustomActionFilterAttribute))]
    [CustomActionFilterFactory(typeof(CustomActionFilterAttribute))]
    public IActionResult Index()
    {
    _ilogger.LogInformation("执行FiveController Index Action");

    return View();
    }
    }
    }
  3. 结果完美,复盘一下就是CustomActionFilterFactory 实现了 IFilterFactory 接口的功。

Filter 生效范围与执行顺序

`[CustomActionFilterFactory(typeof(CustomActionFilterAttribute))]`
  1. 标记在Action 上只对当前 Action 生效

  2. 标记在Controller 上,对 当前Controller 中所有Action 生效

  3. 全局注册,对当前整个项目所有 Action生效。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // Startup.cs - ConfigureServices
    #region 全局注册 Filter

    services.AddMvc(option =>
    {
    option.Filters.Add<CustomActionFilterAttribute>();
    });

    #endregion
  4. 如果有三个Filter注册在全局、标记在Controller、标记在 Action 上,执行顺序

    如果按照默认(不注入Order的前提)

    1. 全局 OnActionExecuting
    2. 控制器 OnActionExecuting
    3. Action OnActionExecuting
    4. 正方法
    5. Action OnActionExecuted
    6. 控制器 OnActionExecuted
    7. 全局 OnActionExecuted
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    using Microsoft.AspNetCore.Mvc.Filters;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading.Tasks;

    namespace DotNetCoreDemo.Utility.Filter
    {
    /// <summary>
    /// 用来注册全局的
    /// </summary>
    public class TestGlobalActionFilterAttribute : ActionFilterAttribute
    {
    public override void OnActionExecuting(ActionExecutingContext context)
    {
    Console.WriteLine("TestGlobalActionFilterAttribute OnActionExecuting");
    }

    public override void OnActionExecuted(ActionExecutedContext context)
    {
    Console.WriteLine("TestGlobalActionFilterAttribute OnActionExecuted");
    }
    }

    /// <summary>
    /// 用来标记控制器的
    /// </summary>
    public class TestControllerActionFilterAttribute : ActionFilterAttribute
    {
    public override void OnActionExecuting(ActionExecutingContext context)
    {
    Console.WriteLine("TestControllerActionFilterAttribute OnActionExecuting");
    }

    public override void OnActionExecuted(ActionExecutedContext context)
    {
    Console.WriteLine("TestControllerActionFilterAttribute OnActionExecuted");
    }
    }

    /// <summary>
    /// 用来标记Action的
    /// </summary>
    public class TestAction_ActionFilterAttribute : ActionFilterAttribute
    {
    public override void OnActionExecuting(ActionExecutingContext context)
    {
    Console.WriteLine("TestAction_ActionFilterAttribute OnActionExecuting");
    }

    public override void OnActionExecuted(ActionExecutedContext context)
    {
    Console.WriteLine("TestAction_ActionFilterAttribute OnActionExecuted");
    }
    }
    }

    如果想修改执行顺序

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    using DotNetCoreDemo.Utility;
    using DotNetCoreDemo.Utility.Filter;
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.Extensions.Logging;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading.Tasks;

    namespace DotNetCoreDemo.Controllers
    {
    //标记测试Filter
    [TestControllerActionFilterAttribute(Order =-1)]
    public class FiveController : Controller
    {

    private ILogger<FiveController> _ilogger = null;

    public FiveController(ILogger<FiveController> logger)
    {
    _ilogger = logger;

    _ilogger.LogInformation("FiveController 被构造");
    }

    //[CustomActionFilterAttribute]
    //[CustomActionFilterChildeAttribute]
    //[TypeFilter(typeof(CustomActionFilterAttribute))]
    //[ServiceFilter(typeof(CustomActionFilterAttribute))]
    //[CustomActionFilterFactory(typeof(CustomActionFilterAttribute))]
    //标记测试Filter
    [TestAction_ActionFilterAttribute(Order =-2)]
    public IActionResult Index()
    {
    _ilogger.LogInformation("执行FiveController Index Action");

    return View();
    }
    }
    }

    上述代码得出的执行顺序就是

    1. Action OnActionExecuting
    2. 控制器 OnActionExecuting
    3. 全局 OnActionExecuting
    4. 正方法
    5. 全局 OnActionExecuted
    6. 控制器 OnActionExecuted
    7. Action OnActionExecuted

ResourceFilter 扩展定制-支持缓存

新建自定义ResourceFilter 扩展类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace DotNetCoreDemo.Utility.Filter
{
public class CustomResourceFilterAttribute : Attribute, IResourceFilter
{
public static Dictionary<string, object> CacheDic = new Dictionary<string, object>();

public void OnResourceExecuting(ResourceExecutingContext context)
{
var key = context.HttpContext.Request.Path;

if (CacheDic.Any(item => item.Key == key))
{
context.Result = CacheDic[key] as IActionResult;
}
}

public void OnResourceExecuted(ResourceExecutedContext context)
{
var key = context.HttpContext.Request.Path;

CacheDic[key] = context.Result;
}
}
}

控制器应用

1
2
3
4
5
6
7
[CustomResourceFilterAttribute]
public IActionResult IndexTestResource()
{
ViewBag.Date = DateTime.Now;

return View();
}

IndexTestResource.cshtml

1
2
3
4
5
6
7
8
9
10

@{
ViewData["Title"] = "IndexTestResource";
}

<h1>IndexTestResource</h1>

<h3>缓存时间:@ViewBag.Date</h3>

<h3>页面时间:@DateTime.Now</h3>

上述主要功能是:新建ResourceFilter扩展特性类,并且在控制器FiveController 中 IndexTestResource Action 标记应用。应用扩展标记的作用是为了建立缓存。相比较ActionFilter ,ResourceFilter 更适合做缓存,参考下图

image-20210905121153341

Filter 匿名

结合 Filter 生效范围与执行顺序来看,如果全局注册 Filter 的话,那么所有Action都会生效,如果这个时候,不想某个Action被生效,那么匿名的作用就来咯。

现在全局注册: TestGlobalActionFilterAttribute

1
2
3
4
5
6
7
8
#region 全局注册 Filter 
services.AddMvc(option =>
{
//option.Filters.Add<CustomActionFilterAttribute>();
//注册全局 测试执行顺序
option.Filters.Add<TestGlobalActionFilterAttribute>();
});
#endregion

现在需要跳过的Action上标注特性:[AllowAnonymousAttribute]

然后再去 TestGlobalActionFilterAttribute 添加判断就OK了,不走这玩意TestGlobalActionFilterAttribute了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/// <summary>
/// 用来注册全局的
/// </summary>
public class TestGlobalActionFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
//判断执行到这的Action有没有标记AllowAnonymousAttribute 特性
if (context.ActionDescriptor.EndpointMetadata.Any(item => item.GetType() == typeof(AllowAnonymousAttribute)))
{
return;
}

Console.WriteLine("TestGlobalActionFilterAttribute OnActionExecuting");
}

public override void OnActionExecuted(ActionExecutedContext context)
{
////判断执行到这的Action有没有标记AllowAnonymousAttribute 特性
if (context.ActionDescriptor.EndpointMetadata.Any(item => item.GetType() == typeof(AllowAnonymousAttribute)))
{
return;
}

Console.WriteLine("TestGlobalActionFilterAttribute OnActionExecuted");
}
}

上面这这个 AllowAnonymousAttribute 也可以自定写个特性,到时候在Action和判断中替换一下特性名称即可。

通过查询资料,这个匿名一般用于授权AuthorizeAttribute

ExceptionFilter

这个过滤器就是为了处理异常。

  1. 首先创建CustomExceptionFilterAttribute 特性,实现IExceptionFilter接口
  2. 实现方法中需要判断执行到这的异常有没有被处理
  3. 如果没有被处理,那么需要判断异常的源头,如果是ajax请求的异常,返回 Jsonresult,否则,跳转到Error页面显示异常信息。
  4. 上述如果没有毛病的话,那就进行全局注册

image-20210905161745655

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace DotNetCoreDemo.Utility.Filter
{
public class CustomExceptionFilterAttribute : Attribute, IExceptionFilter
{
private IModelMetadataProvider _modelmetadataProvider = null;

public CustomExceptionFilterAttribute(IModelMetadataProvider modelmetadataProvider)
{
_modelmetadataProvider = modelmetadataProvider;
}

public void OnException(ExceptionContext context)
{
if (!context.ExceptionHandled)//如果有异常未被处理
{
if (IsAjaxRequest(context.HttpContext.Request))
{
context.Result = new JsonResult(new { Result = false, Msg = context.Exception.Message });
}
else
{
var result = new ViewResult { ViewName = "~/Views/Shared/Error.cshtml" };

result.ViewData = new ViewDataDictionary(_modelmetadataProvider, context.ModelState);

result.ViewData.Add("Exception", context.Exception);

context.Result = result;//断路器
}

//处理完给个True
context.ExceptionHandled = true;
}
}

private bool IsAjaxRequest(HttpRequest request)
{
string header = request.Headers["X-Requested-With"];

return "XMLHttpRequest".Equals(header);
}
}
}

Controller

1
2
3
4
5
6
7
8
9
10
11
[TypeFilter(typeof(CustomExceptionFilterAttribute))] // 带参的就得这么标记哦
public IActionResult IndexException()
{
int i = 0;

int j = 5;

int k = j / i;

return View();
}

Error.cshtml

1
2
3
4
5
6
7
8
9
10
@{ 
ViewData["Title"] = "Error";
Exception exception = base.ViewData["Exception"] as Exception;
}

<h1 class="text-danger">Error.</h1>
<h2 class="text-danger">An error occurred while processing your request.</h2>

<h3>Context Exception : @exception.Message</h3>

上述是在Action上标记CustomExceptionFilterAttribute ,最后,再加上全局注册。

ExceptionFilter 能捕捉到的异常

异常分类 可否捕捉
控制器实例化异常 true
异常发生在Try Cache中 false(TryCache默认是已经处理过的异常)
在视图中发生异常 false(会直接体现在页面上)
Service层发生异常 true
在Action 中发生异常 true
请求路径错误异常 true (需要中间件来完成)

主要阐述下 如果请求路径错误异常需要如何处理

  1. Startup.cs 中的 Configure 方法中添加中间件,记得放前面点,放最后不管用。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    #region 捕捉异常补充

    app.UseStatusCodePagesWithReExecute("/Home/Error/{0}");//只要不是状态200的请求,都能进来

    app.UseExceptionHandler(errorapp =>
    {
    errorapp.Run(async context =>
    {
    context.Response.StatusCode = 200;
    context.Response.ContentType = "text/html";
    await context.Response.WriteAsync("<html lang=\"en\"><body>\r\n");
    await context.Response.WriteAsync("Error! <br><br>\r\n");
    var exceptionHandlePathFeature = context.Features.Get<IExceptionHandlerPathFeature>();
    Console.WriteLine("**********************************************");
    Console.WriteLine($"{ exceptionHandlePathFeature?.Error.Message}");
    Console.WriteLine("**********************************************");

    if (exceptionHandlePathFeature?.Error is FileNotFoundException)
    {
    await context.Response.WriteAsync("File Error throw! <br><br>\r\n");
    }

    await context.Response.WriteAsync("<a href =\"Home/Index\">Home</a><br><br>\r\n");
    await context.Response.WriteAsync("</body></html><br><br>\r\n");
    await context.Response.WriteAsync(new string(' ', 512));
    });
    });

    #endregion
    image-20210905170801999

ResultFilter

这个过滤器是为了Action 方法里 return view() 服务的,类似AOP这种写法。

下面代码综合之前的Filter的磨砺,路子都差不多,新建一个自定义特性类,继承 Attribute 实现 IResultFilter 方法。

  1. IModeIMetadataProvider 是为了接受参数的
  2. 通过接收的参数在Controller 标记 [TypeFilter(typeof(CustomResultFilterAttribute))] Action 方法中,在return View() 这个操作时,判断参数跳转不同的cshtml页。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace DotNetCoreDemo.Utility.Filter
{
public class CustomResultFilterAttribute : Attribute, IResultFilter
{
private IModelMetadataProvider _modelmetadataProvider = null;

public CustomResultFilterAttribute(IModelMetadataProvider modelmetadataProvider)
{
_modelmetadataProvider = modelmetadataProvider;
}

/// <summary>
/// 执行视图之前
/// </summary>
/// <param name="context"></param>
public void OnResultExecuting(ResultExecutingContext context)
{
Console.WriteLine("执行视图之前");

var view = context.HttpContext.Request.Query["View"];

if (view == "1")
{
var result = new ViewResult { ViewName = "~/Views/Five/IndexResult.cshtml" };
result.ViewData = new ViewDataDictionary(_modelmetadataProvider, context.ModelState);
context.Result = result;
}
else
{
var result = new ViewResult { ViewName = "~/Views/Five/IndexResult_EN.cshtml" };
result.ViewData = new ViewDataDictionary(_modelmetadataProvider, context.ModelState);
context.Result = result;
}
}

/// <summary>
/// 执行视图之后
/// </summary>
/// <param name="context"></param>
public void OnResultExecuted(ResultExecutedContext context)
{
Console.WriteLine("执行视图之后");
}
}
}

image-20210905185056411

鉴权授权方式

代码:SevenController

基础授权

  1. 使用鉴权 app.UseAuthentication() 必须放在 app.UseRouting(); 后面

  2. Startup.cs - ConfigureServices

    1
    2
    3
    4
    5
    6
    7
    8
    #region Auth-cookie-验证

    services.AddAuthentication("Cookies").AddCookie(option =>
    {
    option.LoginPath = new Microsoft.AspNetCore.Http.PathString("/Seven/LoginOut");
    });

    #endregion
  3. 正文

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    using Microsoft.AspNetCore.Authentication;
    using Microsoft.AspNetCore.Authorization;
    using Microsoft.AspNetCore.Mvc;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Security.Claims;
    using System.Threading.Tasks;

    namespace DotNetCoreDemo.Controllers
    {
    /// <summary>
    /// Auth Cookie
    /// </summary>
    public class SevenController : Controller
    {
    [Authorize]
    public IActionResult Index()
    {
    return View();
    }

    public IActionResult Login()
    {
    List<Claim> Claims = new List<Claim>
    {
    new Claim(ClaimTypes.Name,"Harris"),
    new Claim(ClaimTypes.Email,"hou3125378@126.com"),
    new Claim("Xing","Hou")
    };

    ClaimsIdentity identity = new ClaimsIdentity(Claims, "HarrisIdentity");

    HttpContext.SignInAsync(new ClaimsPrincipal(identity));

    return View();
    }

    public IActionResult LoginOut()
    {
    HttpContext.SignOutAsync();

    return View();
    }
    }
    }

    上述代码:可以打开F12,Application-Cookie,观察Cookie的变化。

    如果没有Cookie 去访问Index的时候,就会自动跳转到LoginOut,这时候访问Login,记录下Cookie之后,再访问Index就可以了,最后,访问LoginOut,清除Cookie.

JWT

Token鉴权授权

上图是访问的过程。

搭建授权中心(WEBAPI

  1. Nugget

    image-20211001192934524

  2. appsettings.json 主要关注 JWTTokenOptions

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    {
    "Logging": {
    "LogLevel": {
    "Default": "Information",
    "Microsoft": "Warning",
    "Microsoft.Hosting.Lifetime": "Information"
    }
    },
    "AllowedHosts": "*",
    "JWTTokenOptions": {
    "Audience": "http://localhost:5200",
    "Issuer": "http://localhost:5200",
    "SecurityKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDI2a2EJ7m872v0afyoSDJT2o1+SitIeJSWtLJU8/Wz2m7gStexajkeD+Lka6DSTy8gt9UwfgVQo6uKjVLG5Ex7PiGOODVqAEghBuS7JzIYU5RvI543nNDAPfnJsas96mSA7L/mD7RTE2drj6hf3oZjJpMPZUQI/B1Qjb5H3K3PNwIDAQAB"
    }
    }
  3. 注册

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    using DotNetCoreDemo.AuthenticationCenter.Utility;
    using Microsoft.AspNetCore.Builder;
    using Microsoft.AspNetCore.Hosting;
    using Microsoft.AspNetCore.HttpsPolicy;
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.Extensions.Configuration;
    using Microsoft.Extensions.DependencyInjection;
    using Microsoft.Extensions.Hosting;
    using Microsoft.Extensions.Logging;
    using Microsoft.OpenApi.Models;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading.Tasks;

    namespace DotNetCoreDemo.AuthenticationCenter
    {
    public class Startup
    {
    public Startup(IConfiguration configuration)
    {
    Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
    //HS加密方式
    //services.AddTransient<ICustomJWTService, CustomHSJWTService>();
    //RSS加密方式
    services.AddTransient<ICustomJWTService, CustomRSSJWTervice>();
    services.Configure<JWTTokenOptions>(this.Configuration.GetSection("JWTTokenOptions"));
    services.AddControllers();
    services.AddSwaggerGen(c =>
    {
    c.SwaggerDoc("v1", new OpenApiInfo { Title = "DotNetCoreDemo.AuthenticationCenter", Version = "v1" });
    });
    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
    if (env.IsDevelopment())
    {
    app.UseDeveloperExceptionPage();
    app.UseSwagger();
    app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "DotNetCoreDemo.AuthenticationCenter v1"));
    }

    app.UseHttpsRedirection();

    app.UseRouting();

    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
    endpoints.MapControllers();
    });
    }
    }
    }
  4. 入口

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    using DotNetCoreDemo.AuthenticationCenter.Utility;
    using Microsoft.AspNetCore.Mvc;
    using Newtonsoft.Json;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading.Tasks;

    namespace DotNetCoreDemo.AuthenticationCenter.Controllers
    {
    [Route("api/[controller]")]
    [ApiController]
    public class AuthenticationController : Controller
    {
    //构造函数注入
    private ICustomJWTService _iJWTService = null;
    public AuthenticationController(ICustomJWTService customJWTService)
    {
    _iJWTService = customJWTService;
    }

    //[Route("Get")]
    //[HttpGet]
    //public IEnumerable<int> Get()
    //{
    // return new List<int>() { 1, 2, 3, 4, 6, 7 };
    //}

    [Route("Login")]
    [HttpPost]
    public string Login(string name, string password)
    {
    //在这里需要去数据库中做数据验证
    if ("Harris".Equals(name) && "123456".Equals(password))
    {
    //就应该生成Token
    string token = this._iJWTService.GetToken(name, password);
    return JsonConvert.SerializeObject(new
    {
    result = true,
    token
    });
    }
    else
    {
    return JsonConvert.SerializeObject(new
    {
    result = false,
    token = ""
    });
    }
    }
    }
    }
  5. 抽象类

    1
    2
    3
    4
    public interface ICustomJWTService
    {
    string GetToken(string UserName, string password);
    }
  6. HS JWT

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    using Microsoft.Extensions.Options;
    using Microsoft.IdentityModel.Tokens;
    using System;
    using System.Collections.Generic;
    using System.IdentityModel.Tokens.Jwt;
    using System.Linq;
    using System.Security.Claims;
    using System.Text;
    using System.Threading.Tasks;

    namespace DotNetCoreDemo.AuthenticationCenter.Utility
    {
    public class CustomHSJWTService : ICustomJWTService
    {
    private readonly JWTTokenOptions _JWTTokenOptions;
    public CustomHSJWTService(IOptionsMonitor<JWTTokenOptions> jwtTokenOptions)
    {
    this._JWTTokenOptions = jwtTokenOptions.CurrentValue;
    }

    public string GetToken(string UserName, string password)
    {
    #region 有效载荷,大家可以自己写,爱写多少写多少;尽量避免敏感信息
    var claims = new[]
    {
    new Claim(ClaimTypes.Name, UserName),
    new Claim("NickName",UserName),
    new Claim("Role","Administrator"),//传递其他信息
    new Claim("ABCC","ABCC"),
    new Claim("Student","甜酱油")
    };

    //需要加密:需要加密key:
    //Nuget引入:Microsoft.IdentityModel.Tokens
    SymmetricSecurityKey key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_JWTTokenOptions.SecurityKey));

    SigningCredentials creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);

    //Nuget引入:System.IdentityModel.Tokens.Jwt
    JwtSecurityToken token = new JwtSecurityToken(
    issuer: _JWTTokenOptions.Issuer,
    audience: _JWTTokenOptions.Audience,
    claims: claims,
    expires: DateTime.Now.AddMinutes(5),//5分钟有效期
    signingCredentials: creds);

    string returnToken = new JwtSecurityTokenHandler().WriteToken(token);
    return returnToken;
    #endregion
    }
    }
    }
  7. RSA JWT

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    using Microsoft.Extensions.Options;
    using Microsoft.IdentityModel.Tokens;
    using System;
    using System.Collections.Generic;
    using System.IdentityModel.Tokens.Jwt;
    using System.IO;
    using System.Linq;
    using System.Security.Claims;
    using System.Security.Cryptography;
    using System.Threading.Tasks;

    namespace DotNetCoreDemo.AuthenticationCenter.Utility
    {
    public class CustomRSSJWTervice : ICustomJWTService
    {
    #region Option注入
    private readonly JWTTokenOptions _JWTTokenOptions;
    public CustomRSSJWTervice(IOptionsMonitor<JWTTokenOptions> jwtTokenOptions)
    {
    _JWTTokenOptions = jwtTokenOptions.CurrentValue;
    }
    #endregion

    public string GetToken(string UserName, string password)
    {
    #region 使用加密解密Key 非对称
    string keyDir = Directory.GetCurrentDirectory();
    if (RSAHelper.TryGetKeyParameters(keyDir, true, out RSAParameters keyParams) == false)
    {
    keyParams = RSAHelper.GenerateAndSaveKey(keyDir);
    }
    #endregion

    //string jtiCustom = Guid.NewGuid().ToString();//用来标识 Token
    Claim[] claims = new[]
    {
    new Claim(ClaimTypes.Name, UserName),
    new Claim(ClaimTypes.Role,"admin"),
    new Claim("password",password)
    };

    SigningCredentials credentials = new SigningCredentials(new RsaSecurityKey(keyParams), SecurityAlgorithms.RsaSha256Signature);

    var token = new JwtSecurityToken(
    issuer: this._JWTTokenOptions.Issuer,
    audience: this._JWTTokenOptions.Audience,
    claims: claims,
    expires: DateTime.Now.AddMinutes(60),//5分钟有效期
    signingCredentials: credentials);

    var handler = new JwtSecurityTokenHandler();
    string tokenString = handler.WriteToken(token);
    return tokenString;
    }
    }
    }

  8. JWTTokenOptions

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading.Tasks;

    namespace DotNetCoreDemo.AuthenticationCenter.Utility
    {
    public class JWTTokenOptions
    {
    public string Audience
    {
    get;
    set;
    }
    public string SecurityKey
    {
    get;
    set;
    }
    //public SigningCredentials Credentials
    //{
    // get;
    // set;
    //}
    public string Issuer
    {
    get;
    set;
    }
    }
    }
  9. RSA Helper

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    using Newtonsoft.Json;
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.Security.Cryptography;
    using System.Threading.Tasks;

    namespace DotNetCoreDemo.AuthenticationCenter.Utility
    {
    public class RSAHelper
    {
    /// <summary>
    /// 从本地文件中读取用来签发 Token 的 RSA Key
    /// </summary>
    /// <param name="filePath">存放密钥的文件夹路径</param>
    /// <param name="withPrivate"></param>
    /// <param name="keyParameters"></param>
    /// <returns></returns>
    public static bool TryGetKeyParameters(string filePath, bool withPrivate, out RSAParameters keyParameters)
    {
    string filename = withPrivate ? "key.json" : "key.public.json";
    string fileTotalPath = Path.Combine(filePath, filename);
    keyParameters = default(RSAParameters);
    if (!File.Exists(fileTotalPath))
    {
    return false;
    }
    else
    {
    keyParameters = JsonConvert.DeserializeObject<RSAParameters>(File.ReadAllText(fileTotalPath));
    return true;
    }
    }
    /// <summary>
    /// 生成并保存 RSA 公钥与私钥
    /// </summary>
    /// <param name="filePath">存放密钥的文件夹路径</param>
    /// <returns></returns>
    public static RSAParameters GenerateAndSaveKey(string filePath, bool withPrivate = true)
    {
    RSAParameters publicKeys, privateKeys;
    using (var rsa = new RSACryptoServiceProvider(2048))//即时生成
    {
    try
    {
    privateKeys = rsa.ExportParameters(true);
    publicKeys = rsa.ExportParameters(false);
    }
    finally
    {
    rsa.PersistKeyInCsp = false;
    }
    }
    File.WriteAllText(Path.Combine(filePath, "key.json"), JsonConvert.SerializeObject(privateKeys));
    File.WriteAllText(Path.Combine(filePath, "key.public.json"), JsonConvert.SerializeObject(publicKeys));
    return withPrivate ? privateKeys : publicKeys;
    }
    }
    }

权限消费端

  1. appsetting.json 主要关注 JWTTokenOptions

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    {
    "Logging": {
    "LogLevel": {
    "Default": "Information",
    "Microsoft": "Warning",
    "Microsoft.Hosting.Lifetime": "Information"
    }
    },
    "AllowedHosts": "*",
    "TEST": {
    "TESTID": "1111",
    "TESTNAME": "HARRIS",
    "_TESTAdress": {
    "Sheng": "SHANXI",
    "Shi": "XINZHOU"
    },
    "Family": [
    "baba",
    "mama",
    "son"
    ]
    },
    "JWTTokenOptions": {
    "Audience": "http://localhost:5200",
    "Issuer": "http://localhost:5200",
    "SecurityKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDI2a2EJ7m872v0afyoSDJT2o1+SitIeJSWtLJU8/Wz2m7gStexajkeD+Lka6DSTy8gt9UwfgVQo6uKjVLG5Ex7PiGOODVqAEghBuS7JzIYU5RvI543nNDAPfnJsas96mSA7L/mD7RTE2drj6hf3oZjJpMPZUQI/B1Qjb5H3K3PNwIDAQAB"
    }
    }
  2. Startup.csConfigureServices 方法,先把之前测试Cookie的方法注释掉。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    #region RSA
    {
    // 读取公钥
    string path = Path.Combine(Directory.GetCurrentDirectory(), "key.public.json");
    string key = File.ReadAllText(path);//this.Configuration["SecurityKey"];
    Console.WriteLine($"KeyPath:{path}");
    var keyParams = JsonConvert.DeserializeObject<RSAParameters>(key);
    //SigningCredentials credentials = new SigningCredentials(new RsaSecurityKey(keyParams), SecurityAlgorithms.RsaSha256Signature);

    JWTTokenOptions tokenOptions = new JWTTokenOptions();
    Configuration.Bind("JWTTokenOptions", tokenOptions);

    services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
    options.TokenValidationParameters = new TokenValidationParameters
    {
    ValidateIssuer = true,//是否验证Issuer
    ValidateAudience = true,//是否验证Audience
    ValidateLifetime = true,//是否验证失效时间
    ValidateIssuerSigningKey = true,//是否验证SecurityKey
    ValidAudience = tokenOptions.Audience,//Audience
    ValidIssuer = tokenOptions.Issuer,//Issuer,这两项和前面签发jwt的设置一致
    IssuerSigningKey = new RsaSecurityKey(keyParams),
    //IssuerSigningKeyValidator = (m, n, z) =>
    // {
    // Console.WriteLine("This is IssuerValidator");
    // return true;
    // },
    //IssuerValidator = (m, n, z) =>
    // {
    // Console.WriteLine("This is IssuerValidator");
    // return "http://localhost:5726";
    // },
    //AudienceValidator = (m, n, z) =>
    //{
    // Console.WriteLine("This is AudienceValidator");
    // return true;
    // //return m != null && m.FirstOrDefault().Equals(this.Configuration["Audience"]);
    //},//自定义校验规则,可以新登录后将之前的无效
    };
    });
    }
    #endregion

    3. 具体入口 `SixController` 特性 `[Authorize]`

    ``` CSharp
    using Microsoft.AspNetCore.Authorization;
    using Microsoft.AspNetCore.Mvc;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading.Tasks;

    namespace DotNetCoreDemo.Controllers
    {
    public class SixController : Controller
    {
    [Authorize] //2.第二部:标记特性,表示Privacy 可以支持验证
    public IActionResult Index()
    {
    var user = HttpContext.User;

    return View();
    }

    public IActionResult Login()
    {
    return View();
    }
    }
    }
  3. View

    1
    2
    3
    4
    5
    6
    7
    8

    @{
    ViewData["Title"] = "Index";
    }

    <h1>Six Index</h1>

    <h3>HS 授权完毕</h3>

测试过程

  1. 生成+获取Token

    生成key.jsonkey.public.json

    授权中心通过WebApi 访问,生产key.jsonkey.public.jsonkey.jsonkey.public.json 是互相呼应的。

    图一:

    image-20211001191326110

    1. 点击 Tryitout 按钮到
    2. 输入 name & password
    3. 点击 Execute 按钮,生成如下内容。

    图二:

    image-20211001191428193

    Response body 内容作为备用。

    key.public.json 文件放到访问的相对位置,本地放到

    image-20211001191845619

  2. 启动PostMan 工具

    image-20211001195024174

    1. 输入主程序 http://localhost:5000/Six/Index

    2. Authorization 选择 Bearer Token 输入 图二 中 Response bodyToken 内容.

    3. 点击 Send提交,则可以看到访问成功。

    4. 否则 则报 401 错误

      image-20211001195539225

.NetCore Docker Nginx

Test Program

新建 .net core web应用

新建应用

添加Docker 支持-linux

docker支持

发布

发布设置

修改发布后的DockerFile

发布后的文件

修改DockerFile

1
2
3
4
5
6
7
8
#See https://aka.ms/containerfastmode to understand how Visual Studio uses 
FROM mcr.microsoft.com/dotnet/aspnet:5.0 AS base
WORKDIR /app
EXPOSE 80

WORKDIR /app
COPY . /app
ENTRYPOINT ["dotnet", "WebDemo.dll"]

上传到服务器

xftp

Docker image

build test program

testProgramBuild

pull nginx

1
docker pull nginx

Docker Run

run test program image

1
docker run -d -p 5000:80 --name webdemo.nginx webdemo:v1.0

添加Nginx配置

myNginx.conf,192.168.137.6为宿主IP

1
2
3
4
5
6
7
8
9
vim /root/nginx/my_nginx.conf
# 内容
server {
listen 80;

location / {
proxy_pass http://192.168.137.6:5000;
}
}

run nignx image

挂载到/root/nginx/my_nginx.conf

1
docker run -d -p 8080:80 -v /root/nginx/my_nginx.conf:/etc/nginx/conf.d/default.conf nginx

Last

本篇主要是将.Net core web应用放到Linux服务器中,使用Nginx反向代理,进行访问。

docker容器列表

web应用服务

ngixn代理端口

EFCore

img

what

简单来说EFCore是一种ORM的技术.

  1. EF主要有三种模式进行开发:DataFirst、CodeFirst、ModelFirst

  2. EFCore主要支持两种开发方法: 1. CodeFirst(代码优先) 2. DataFirst(数据库优先)

  3. EFCore主要针对代码优先方法,很少提供对数据库优先方法的支持,因为从EF Core 2.0开始不支持可视化的DB模型设计器或向导

why

特点

  1. 支持多中关系型数据库
  2. 支持linq查询
  3. 支持反向工程,可以将数据库表与EFCore模型同步。
  4. 支持迁移,EFCore模型的更改可以通过迁移同步到数据库
  5. 支持日志记录
  6. 支持原生SQL对数据库进行操作,半自动ORM

支持的数据库和Nugget库

数据库 Nuget程序包
SQL Server Microsoft.EntityFrameworkCore.SqlServer
MySQL MySql.Data.EntityFrameworkCore(官方版,不建议使用)
MySQL Pomelo.EntityFrameworkCore(第三方提供,Bug少建议使用)
PostgreSQL Npgsql.EntityFrameworkCore.PostgreSQL
SQLite Microsoft.EntityFrameworkCore.SQLite
SQL Compact Microsoft.EntityFrameworkCore.SQLite
In-memory Microsoft.EntityFrameworkCore.InMemory

DataFirst

通过数据库表生产相关模型类

Mysql

1
2
3
4
5
6
7
8
9
10
11
12
13
14
--新建数据库 EFCoreLearn
CREATE DATABASE
IF NOT EXISTS EFCoreLearn
DEFAULT CHARSET utf8
COLLATE utf8_general_ci;

--新建表 Student
CREATE TABLE IF NOT EXISTS `Students`
(
`StudentId` INT UNSIGNED AUTO_INCREMENT,
`Name` VARCHAR(50) NOT NULL,
`Class` VARCHAR(50) NOT NULL,
PRIMARY KEY (`StudentId`)
);

新建项目

我本地测试新建的.net 6 的控制台程序

项目Nugget安装

1
2
Install-Package Microsoft.EntityFrameworkCore.Tools
Install-Package Pomelo.EntityFrameworkCore.MySql

通过Nugget 控制台管理

1
Scaffold-DbContext -Force “Server=127.0.0.1;User Id=root;Password=root;Database=efcorelearn” -Provider “Pomelo.EntityFrameworkCore.MySql” -OutputDir Models

通过动态库 Pomelo.EntityFrameworkCore.MySql、连接字符串,生产Model类

生产结果

image-20220521200827957

image-20220521200848450

image-20220521200855313

连接类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
using System;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata;

namespace EFCore_DataFirst.Models
{
public partial class EFCoreLearnContext : DbContext
{
public EFCoreLearnContext()
{
}

public EFCoreLearnContext(DbContextOptions<EFCoreLearnContext> options)
: base(options)
{
}

public virtual DbSet<Student> Students { get; set; } = null!;

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
{
optionsBuilder.UseMySql("server=;user id=;password=;database=EFCoreLearn", Microsoft.EntityFrameworkCore.ServerVersion.Parse("5.6.50-mysql"));
}
}

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.UseCollation("utf8_general_ci")
.HasCharSet("utf8");

modelBuilder.Entity<Student>(entity =>
{
entity.Property(e => e.StudentId).HasColumnType("int(10) unsigned");

entity.Property(e => e.Class).HasMaxLength(50);

entity.Property(e => e.Name).HasMaxLength(50);
});

OnModelCreatingPartial(modelBuilder);
}

partial void OnModelCreatingPartial(ModelBuilder modelBuilder);
}
}

生产实体类

1
2
3
4
5
6
7
8
9
10
11
12
using System;
using System.Collections.Generic;

namespace EFCore_DataFirst.Models
{
public partial class Student
{
public uint StudentId { get; set; }
public string Name { get; set; } = null!;
public string Class { get; set; } = null!;
}
}

CURD

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
// See https://aka.ms/new-console-template for more information
using EFCore_DataFirst.Models;
using Microsoft.EntityFrameworkCore;

Console.WriteLine("Hello, World!");

using (var db = new EFCoreLearnContext())
{
Student student = new Student()
{
Name = "小明",
Class = "软件一班"
};
db.Students.Add(student);
db.SaveChanges();//提交更改后才能保存到数据库。
}

using (var db = new EFCoreLearnContext())
{
//更新方式1 Update
Student student = new Student()
{
StudentId = 1,
Name = "小明",
Class = "软件2班"
};
db.Students.Update(student);
db.SaveChanges();

//更新方式2 先查询,再修改

var stu1 = db.Students.AsEnumerable().First(a => a.StudentId == 1);//或者var stu1=db.Students.Single(a => a.StudentId == 1)
stu1.Name = "小红";
EntityState ss = db.Entry(stu1).State;//unchanged
db.SaveChanges();

//更新方式3 设置 EntityState的状态和Update类似
Student stu2 = new Student();
stu2.Name = "小李";
stu2.StudentId = 1;
stu2.Class = "软件3班";
db.Entry(stu2).State = EntityState.Modified;
db.SaveChanges();

//更新方式4 Attach方法
Student stu3 = new Student();
stu3.Name = "小明";
stu3.StudentId = 1;
stu3.Class = "软件1班";
db.Students.Attach(stu3);
db.Entry(stu3).State = EntityState.Modified;
db.SaveChanges();
}


using (var db = new EFCoreLearnContext())
{
//删除方式1 Remove
Student student = new Student();
student.StudentId = 1;
db.Students.Remove(student);
db.SaveChanges();

//删除方式2 先查询再删除

db.Students.Remove(db.Students.Find(1));//这里Find只要填写主键的值就能找到
db.SaveChanges();

//删除方式3 EntityState.Deleted
Student student2 = new Student();
student.StudentId = 1;
db.Entry(student).State = EntityState.Deleted;
db.SaveChanges();
}

CodeFirst

nugget 添加右侧动态库

image-20220523113526861

Nugget 控制台管理中输入

  1. Add-Migration Initial – 添加迁移
  2. Update-DataBase –提交到数据库

生成的表

Data Migration

Model

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace EFCoreLearn
{
public class Student
{
public int StudentId { get; set; }
public string Name { get; set; }
public string Sex { get; set; }
public int Age { get; set; }

//导航属性
public StudentAddress Address { get; set; }
public IList<Course> Courses { get; set; } = new List<Course>();
}

public class Teacher
{
public int TeacherId { get; set; }
public string Name { get; set; }
public string Title { get; set; }

public int Age { get; set; }
public string Hobby { get; set; }
public IList<Course> Courses { get; set; } = new List<Course>();
}

public class Course
{
public int CourseId { get; set; }
public string Name { get; set; }

//导航属性
public Teacher Teacher { get; set; }
public IList<Student> Students { get; set; } = new List<Student>();
}

public class StudentAddress
{
public int StudentAddressId { get; set; }
public string Address { get; set; }
public string City { get; set; }

//导航属性
public int StudentId { get; set; }
public Student Student { get; set; }
}
}

CodeFirst

  1. Add-Migration Initial – 添加迁移
  2. Update-DataBase –提交到数据库

EFLearnDbContext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace EFCoreLearn
{
public class EFLearnDbContext : DbContext
{
public EFLearnDbContext()
{
//同时为了除此应用的时候代码能够自动创建数据库可以在EFLearnDbContext里添加,程序运行时保证数据库创建。
Database.EnsureCreated();
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseMySql("server=;user id=;password=;database=EFCoreLearn", Microsoft.EntityFrameworkCore.ServerVersion.Parse("5.6.50-mysql"));
}

protected override void OnModelCreating(ModelBuilder modelBuilder)
{

#region 基本配置
modelBuilder.Entity<Student>().Property(x => x.StudentId).ValueGeneratedOnAdd();//设置Id自增
//设置姓名最大长度为50,字符为unicode,不能为空
modelBuilder.Entity<Student>().Property(x => x.Name).HasMaxLength(50).IsUnicode().IsRequired();
//设置性别最大长度为5 字符为Unicode,不能为空
modelBuilder.Entity<Student>().Property(x => x.Sex).HasMaxLength(5).IsUnicode().IsRequired();
//一对一只需要配置一个类就行了,
modelBuilder.Entity<Student>().HasOne(x => x.Address).WithOne(x => x.Student).HasForeignKey<StudentAddress>(ad => ad.StudentId);
#endregion

#region Course
modelBuilder.Entity<Course>().Property(x => x.Name).HasMaxLength(50).IsUnicode();
//课程一对一
modelBuilder.Entity<Course>().HasOne(x => x.Teacher).WithMany(t => t.Courses);
modelBuilder.Entity<Course>().HasMany(x => x.Students).WithMany(st => st.Courses);
#endregion

#region Teacher配置
modelBuilder.Entity<Teacher>().Property(x => x.Name).HasMaxLength(50).IsUnicode();
modelBuilder.Entity<Teacher>().Property(x => x.Title).HasMaxLength(50).IsUnicode();
#endregion

#region 配置地址
modelBuilder.Entity<StudentAddress>().Property(x => x.City).HasMaxLength(100).IsRequired().IsUnicode();
modelBuilder.Entity<StudentAddress>().Property(x => x.Address).HasMaxLength(500).IsRequired().IsUnicode();
#endregion

}


public DbSet<Student> Students { get; set; }
public DbSet<Teacher> Teachers { get; set; }
public DbSet<Course> Courses { get; set; }
public DbSet<StudentAddress> Addresses { get; set; }
}
}

测试数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
using (var dbContext = new EFLearnDbContext())
{
//1.添加一些数据

//Course course1 = new Course() { CourseId = 3001, Name = "高等数学" };
//Course course2 = new Course() { CourseId = 3002, Name = "计算机原理" };
//Course course3 = new Course() { CourseId = 3003, Name = "操作系统原理" };
//Course course4 = new Course() { CourseId = 3004, Name = "编译原理" };


//Teacher teacher1 = new Teacher() { TeacherId = 10001, Name = "张教授", Title = "教授" };
//Teacher teacher2 = new Teacher() { TeacherId = 10002, Name = "王讲师", Title = "讲师" };

//teacher1.Courses = new Course[] { course1, course2 };
//teacher2.Courses = new Course[] { course3, course4 };

//dbContext.Teachers.AddRange(teacher1, teacher2);

//StudentAddress Address1 = new StudentAddress()
//{
// StudentAddressId = 37001,
// Address = "北京朝阳区",
// City = "北京",
//};

//StudentAddress Address2 = new StudentAddress()
//{
// StudentAddressId = 37002,
// Address = "上海徐汇区",
// City = "上海",
//};

//StudentAddress Address3 = new StudentAddress()
//{
// StudentAddressId = 37003,
// Address = "广州白云区",
// City = "广州",
//};



//Student student1 = new Student()
//{
// StudentId = 2022001,
// Name = "王二小",
// Age = 19,
// Sex = "男",
// Address = Address1,
// Courses = new Course[] { course1, course2, course3 },

//};

//Student student2 = new Student()
//{
// StudentId = 2022002,
// Name = "张小五",
// Age = 20,
// Sex = "男",
// Address = Address2,
// Courses = new Course[] { course1, course2 },
//};

//Student student3 = new Student()
//{
// StudentId = 2022003,
// Name = "刘小花",
// Age = 20,
// Sex = "女",
// Address = Address3,
// Courses = new Course[] { course1, course3 },

//};
//dbContext.Students.AddRange(student1, student2, student3);

//dbContext.SaveChanges();

//var students = dbContext.Students.ToList();

//var students = dbContext.Students.Include(x => x.Address).ToList();

//添加自动引用迁移
dbContext.Database.Migrate();

var students = dbContext.Students.Include(x => x.Address).Include(x => x.Courses).ToList();

foreach (var st in students)
{
//Console.WriteLine($"StudentId:{st.StudentId},Name:{st.Name},City:{st.Address.City}, Address:{st.Address.Address}");

Console.WriteLine($"StudentId:{st.StudentId},Name:{st.Name},City:{st.Address.City}, Address:{st.Address.Address},Courses:{string.Join(",", st.Courses.Select(x => x.Name))}");
}

#region 删除数据

//var std = dbContext.Students.Single(x => x.StudentId == 2022001);
//dbContext.Remove(std);
//dbContext.SaveChanges();

#endregion
}

数据库

image-20220523183253394

鸣谢


DotNet Core Note
http://example.com/2021/10/31/Net-Core-Note/
Author
Harris
Posted on
October 31, 2021
Licensed under