c# .Net Core 判断字符串是否为英文(包括符号)

做短信网关程序, 有英文和非英文短信的计费问题. 需要根据短信内容类型来确定条数.
英文短信为140个字符为一条, 非英文内容的则为70个字符为一条.
该方法的原理是根据字符编码规范来检查是否是纯英文字符.
UTF8编码规范中, 中文为3或者4位, ASCII字符为1位, 只需要将内容转换为UTF8码后判断长度是否和原内容一致即可.

public static bool IsEn(string content){
    if(string.IsNullOrWhiteSpace(content))
    {
        return false;
    }
    var bytes = Encoding.UTF8.GetBytes(content);
    bool result = bytes.Length == content.Length;
    return result;
}

示例代码如下

using System;
using System.Diagnostics;
using System.Text;

namespace ConsoleDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            string[] arr = new[] {"张三", "zhangsan", "张三aa", "zhangsan123.//*-" };
            for (int i = 0; i < arr.Length; i++)
            {
                Console.WriteLine($"'{arr[i]}' 为 {(IsEn(arr[i])? "英文" : "非英文")}");
            }

            Stopwatch sw = new Stopwatch();
            sw.Start();
            for (int i = 0; i < 100000; i++)
            {
                IsEn("zhangsan11111");
            }
            sw.Stop();
            Console.WriteLine($"英文循环10万次用时{sw.ElapsedMilliseconds} 毫秒");

            Stopwatch sw1 = new Stopwatch();
            sw1.Start();
            for (int i = 0; i < 100000; i++)
            {
                IsEn("张三zhangsan11111");
            }
            sw1.Stop();
            Console.WriteLine($"非英文循环10万次用时{sw1.ElapsedMilliseconds} 毫秒");
        }

        public static bool IsEn(string content)
        {
            if (string.IsNullOrWhiteSpace(content))
            {
                return false;
            }
            var bytes = Encoding.UTF8.GetBytes(content);
            bool result = bytes.Length == content.Length;
            return result;
        }
    }
}

执行结果

'张三' 为 非英文
'zhangsan' 为 英文
'张三aa' 为 非英文
'zhangsan123.//*-' 为 英文
英文循环10万次用时7 毫秒
非英文循环10万次用时7 毫秒

.Net Core 控制台程序读取appsettings.json

安装 nuget 包 Microsoft.Extensions.ConfigurationMicrosoft.Extensions.Configuration.FileExtensionsMicrosoft.Extensions.Configuration.Json
在main方法中执行如下代码

var conf = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", true, true)
.Build();
var value = conf.GetSection("key").Value;

为.net core网站添加通用的操作引导指南(IntroJs)

接上级领导命令, 需要为开发的网站添加一个操作引导功能,让用户进入网站后可以大概了解如何操作.

实现该功能使用intro.js插件及asp.net core中组件(由于.net core中 @html.renderaction已经移除)来完成.

点击下载introjs

一. 首先是创建components

1. 在views/shared/components(没有请新建)目录下创建”IntroJs”目录

2. 创建ViewModel实体类

    public class IntroJsViewModel
    {
        /// <summary>
        /// 区域名
        /// </summary>
        public string Area { get; set; }

        /// <summary>
        /// 控制器名
        /// </summary>
        public string Controller { get; set; }

        /// <summary>
        /// 页面名
        /// </summary>
        public string Action { get; set; }

        /// <summary>
        /// 步骤列表
        /// </summary>
        public IntroStep[] Steps { get; set; }
    }

    public class IntroStep
    {
        /// <summary>
        /// 唯一选择器
        /// </summary>
        public string Element { get; set; }

        /// <summary>
        /// 步骤序号 1 ,2 ,3 ,4
        /// </summary>
        public int Step { get; set; }

        /// <summary>
        /// 提示文本, 可以为html
        /// </summary>
        public string Intro { get; set; }

        /// <summary>
        /// 位置, left right top  可为空
        /// </summary>
        public string Position { get; set; }
    }

3. 创建ViewComponent类

    public class IntroJsViewComponent: ViewComponent
    {
        public IntroJsViewComponent()
        {

        }

        public IViewComponentResult Invoke()
        {
            var routes = RouteData.Routers[0];
            var area = RouteData.Values["Area"] as string;
            area = string.IsNullOrWhiteSpace(area) ? "" : area.ToLower();
            var controller = RouteData.Values["Controller"].ToString().ToLower();
            var action = RouteData.Values["Action"].ToString().ToLower();
            StringBuilder sb = new StringBuilder();
            string line = string.Empty;
            string filepath = System.IO.Directory.GetCurrentDirectory() + "/IntroSettings.json";
            if (File.Exists(filepath))
            {
                using (StreamReader sr = new StreamReader(filepath))
                {
                    while ((line = sr.ReadLine()) != null)
                    {
                        sb.Append(line);
                    }
                }
                var stepList = JsonConvert.DeserializeObject<List<IntroJsViewModel>>(sb.ToString());
                var step = stepList.FirstOrDefault(x =>
                    x.Area.ToLower() == area && x.Controller.ToLower() == controller && x.Action.ToLower() == action);
                step = step == null ? new IntroJsViewModel{ Steps = new IntroStep[0]}: step;
                return View(step);
            }
            return View(new IntroJsViewModel { Steps = new IntroStep[0] });
        }
    }

4. 添加默认的Default.cshtml视图文件

文件代码如下:

@using WebProject.Views.Shared.Components.IntroJs
@model IntroJsViewModel
@{

}
<script type="text/javascript" src="~/Scripts/introjs/intro.min.js"></script>
<script>
    var showIntro = true;
    var steps = [];
    $(function () {

        @{
            foreach (var item in Model.Steps)
            {
                <text>steps.push({ element: '@item.Element', intro: '@item.Intro', position: '@item.Position' });</text>
            }
        }
        if (showIntro) {
            introJs().setOptions({
                prevLabel: "上一步",
                nextLabel: "下一步",
                skipLabel: "跳过",
                doneLabel: "完成",
                showProgress: true, //显示进度条
                steps:steps
            }).start();
        }
    });

</script>

二. 在需要添加引导的页面引用该组件

一般在母版页底部进行引用.

@await Component.InvokeAsync("IntroJs")

三. 添加配置文件

在项目根目录添加”IntroSettings.json”配置文件

area和controller及action用来确定页面(可以多个)

steps节点下的element为该页面下的dom选择器,如ID等, 请务必保证唯一,否则将影响效果

step为该提示的步骤, 从数字1开始

intro为引导的文字说明

position为文字说明在该dom元素的哪个方向. left,right,top, bottom

[
  {
    "Area": "FormStat",
    "Controller": "WKEntryYear",
    "Action": "Index",
    "Steps": [
      {
        "Element": "#divContent",
        "Step": 1,
        "Intro": "这里是头部......",
        "Position": ""
      },
      {
        "Element": "#btnSearch",
        "Step": 2,
        "Intro": "搜索内容 ",
        "Position": ""
      }
    ]
  }
]

 

五. 效果图

c# asp.net ip黑名单

短信验证码接口被恶意请求,写了一个简单的黑名单规则.
使用静态数组的形式实现,优点为不占用数据库资源且速度更快效率更高. 缺点为数据无持久化,网站重启后黑名单就清空了.
效果为: 如果指定时间内请求次数大于最大请求数并且发送的手机号码大于等于指定号码数量,则加入黑名单,时长为 {BlockTime}
根据如下属性进行配置后,
IsInBlackList方法为判断IP是否在黑名单内,返回bool
RecordRequest方法记录IP和手机号(可以根据功能自己修改具体功能),如满足列入黑名单条件,则自动列入黑名单, 返回bool. 如满足拉黑规则,则返回true,否则返回false

    public class IpBlock
    {

        /// <summary>
        /// 统计时间单位(秒)
        /// </summary>
        private const int Seconds = 60*50;//50分钟

        /// <summary>
        /// 单位时间内请求超出指定次数,则加入黑名单
        /// </summary>
        private const int RequestCount = 3;

        /// <summary>
        /// 单位时间内请求的手机号码数
        /// </summary>
        private const int RequestPhoneCount = 3;
        /// <summary>
        /// 黑名单时间 分钟
        /// </summary>
        private const int BlockTime = 60*24*7; //7天

        /// <summary>
        /// IP黑名单
        /// </summary>
        private static List<BlockedIp> BlackList;

        /// <summary>
        /// 请求详情
        /// </summary>
        private static List<IpRequestModel> RequestList;

        static IpBlock()
        {
            BlackList = new List<BlockedIp>();
            RequestList = new List<IpRequestModel>();
        }


        /// <summary>
        /// IP地址是否在黑名单中
        /// </summary>
        /// <param name="ip"></param>
        /// <returns></returns>
        public static bool IsInBlackList(string ip)
        {
            UpdateBlackList();
            return BlackList.FirstOrDefault(x => x.Ip.Equals(ip)) != null;
        }

        /// <summary>
        /// 记录请求信息并将符合的IP列入黑名单
        /// </summary>
        /// <param name="ip"></param>
        public static bool RecordRequest(string ip, string phone)
        {
            UpdateRequestList();
            RequestList.Add(new IpRequestModel
            {
                Ip = ip,
                RequestTime = DateTime.Now,
                Phone = phone
            });
            var requestRecord = RequestList.Where(x => x.Ip.Equals(ip));
            var count = requestRecord.Count();
            var phoneCount = requestRecord.Select(x => x.Phone).Distinct().Count();
            if (count >= RequestCount && phoneCount >= RequestPhoneCount)
            {
                AddToBlackList(ip);
                return true;
            }
            return false;
        }





        /// <summary>
        /// 更新黑名单
        /// </summary>
        private static void UpdateBlackList()
        {
            var expireList = BlackList.Where(x => x.ExpireTime < DateTime.Now).ToList();
            foreach (var item in expireList)
            {
                BlackList.Remove(item);
            }
        }

        /// <summary>
        /// 将IP列入黑名单
        /// </summary>
        /// <param name="ip"></param>
        private static void AddToBlackList(string ip)
        {
            var item = BlackList.FirstOrDefault(x => x.Ip.Equals(ip));
            if (item != null)
            {
                item.ExpireTime = DateTime.Now.AddMinutes(BlockTime);
            }
            else
            {
                BlackList.Add(new BlockedIp
                {
                    ExpireTime = DateTime.Now.AddMinutes(BlockTime),
                    Ip = ip
                });
            }
        }


        /// <summary>
        /// 更新请求信息
        /// </summary>
        private static void UpdateRequestList()
        {
            var expireList = RequestList.Where(x => x.RequestTime < DateTime.Now.AddSeconds(0-Seconds)).ToList();
            foreach (var item in expireList)
            {
                RequestList.Remove(item);
            }

        }

    }

    public class BlockedIp
    {
        /// <summary>
        /// IP地址
        /// </summary>
        public string Ip { get; set; }

        /// <summary>
        /// 过期时间
        /// </summary>
        public DateTime ExpireTime { get; set; }
    }

    public class IpRequestModel
    {
        /// <summary>
        /// IP地址
        /// </summary>
        public string Ip { get; set; }

        /// <summary>
        /// 请求时间
        /// </summary>
        public DateTime RequestTime { get; set; }

        /// <summary>
        /// 手机号
        /// </summary>
        public string Phone { get; set; }

    }

效果:

......
2018-08-31 10:47:51.007 INFO ASP.mobilergister_aspx: 发送验证码短信 时间:2018/8/31 10:47:51 IP地址:223.68.67.198 手机号码:177****8475
2018-08-31 10:50:55.412 INFO ASP.mobilergister_aspx: IP加入黑名单 时间:2018/8/31 10:50:55 IP地址:220.190.28.133
2018-08-31 10:50:58.302 INFO ASP.mobilergister_aspx: IP加入黑名单 时间:2018/8/31 10:50:58 IP地址:60.181.172.196
2018-08-31 10:51:00.474 INFO ASP.mobilergister_aspx: IP加入黑名单 时间:2018/8/31 10:51:00 IP地址:39.171.235.161
2018-08-31 10:51:14.037 INFO ASP.mobilergister_aspx: IP加入黑名单 时间:2018/8/31 10:51:14 IP地址:60.181.164.123
2018-08-31 10:51:15.677 INFO ASP.mobilergister_aspx: IP加入黑名单 时间:2018/8/31 10:51:15 IP地址:182.200.0.14
2018-08-31 10:51:21.037 INFO ASP.mobilergister_aspx: IP加入黑名单 时间:2018/8/31 10:51:21 IP地址:112.12.136.83
2018-08-31 10:51:23.724 INFO ASP.mobilergister_aspx: IP加入黑名单 时间:2018/8/31 10:51:23 IP地址:42.234.41.134
2018-08-31 10:51:25.802 INFO ASP.mobilergister_aspx: IP加入黑名单 时间:2018/8/31 10:51:25 IP地址:60.180.34.139
2018-08-31 10:51:28.443 INFO ASP.mobilergister_aspx: IP加入黑名单 时间:2018/8/31 10:51:28 IP地址:182.200.179.203
2018-08-31 10:51:31.130 INFO ASP.mobilergister_aspx: IP加入黑名单 时间:2018/8/31 10:51:31 IP地址:115.223.150.237
2018-08-31 10:51:39.990 INFO ASP.mobilergister_aspx: IP加入黑名单 时间:2018/8/31 10:51:39 IP地址:113.87.130.72
2018-08-31 10:51:46.771 INFO ASP.mobilergister_aspx: IP加入黑名单 时间:2018/8/31 10:51:46 IP地址:113.247.23.88
2018-08-31 10:51:47.990 INFO ASP.mobilergister_aspx: IP加入黑名单 时间:2018/8/31 10:51:47 IP地址:60.180.50.65
2018-08-31 10:51:52.459 INFO ASP.mobilergister_aspx: IP加入黑名单 时间:2018/8/31 10:51:52 IP地址:182.200.7.6
2018-08-31 10:51:57.506 INFO ASP.mobilergister_aspx: IP加入黑名单 时间:2018/8/31 10:51:57 IP地址:115.223.135.93
2018-08-31 10:52:07.008 INFO ASP.mobilergister_aspx: IP加入黑名单 时间:2018/8/31 10:52:07 IP地址:223.68.67.198
2018-08-31 10:52:08.758 INFO ASP.mobilergister_aspx: IP加入黑名单 时间:2018/8/31 10:52:08 IP地址:60.180.235.58
2018-08-31 10:52:28.555 INFO ASP.mobilergister_aspx: IP加入黑名单 时间:2018/8/31 10:52:28 IP地址:27.18.202.136
2018-08-31 10:52:59.367 INFO ASP.mobilergister_aspx: IP加入黑名单 时间:2018/8/31 10:52:59 IP地址:113.139.240.243
2018-08-31 10:55:06.790 INFO ASP.mobilergister_aspx: 发送验证码短信 时间:2018/8/31 10:55:06 IP地址:125.47.67.224 手机号码:130****1322
2018-08-31 10:55:34.775 INFO ASP.mobilergister_aspx: IP加入黑名单 时间:2018/8/31 10:55:34 IP地址:59.109.117.88
2018-08-31 10:57:40.229 INFO ASP.mobilergister_aspx: IP加入黑名单 时间:2018/8/31 10:57:40 IP地址:180.120.160.12
2018-08-31 11:02:21.105 INFO ASP.mobilergister_aspx: IP加入黑名单 时间:2018/8/31 11:02:21 IP地址:113.139.242.216
2018-08-31 11:14:03.632 INFO ASP.mobilergister_aspx: 发送验证码短信 时间:2018/8/31 11:14:03 IP地址:1.194.21.226 手机号码:130****2793
2018-08-31 11:27:52.295 INFO ASP.mobilergister_aspx: IP加入黑名单 时间:2018/8/31 11:27:52 IP地址:27.17.148.80
2018-08-31 11:31:31.718 INFO ASP.mobilergister_aspx: 发送验证码短信 时间:2018/8/31 11:31:31 IP地址:125.47.55.70 手机号码:130****1323
2018-08-31 11:31:40.968 INFO ASP.mobilergister_aspx: IP加入黑名单 时间:2018/8/31 11:31:40 IP地址:182.200.3.11
2018-08-31 11:31:45.843 INFO ASP.mobilergister_aspx: IP加入黑名单 时间:2018/8/31 11:31:45 IP地址:125.47.55.70
......

C# 静态构造函数实现单例

使用静态构造函数只执行一次的特性,创建单例

public class SingletonClass
{
    private SingletonClass()=> Thread.Sleep(1000);
    static SingletonClass()=> _instance = new SingletonClass();
    private static readonly SingletonClass _instance;
    public static SingletonClass GetInstance()=> _instance;
}

dnc(.Net core/Dot Net Core) 中使用automapper

最近在学习架构方面的知识,顺便先写一个基于dnc(.Net core/Dot Net Core)的单体架构.有用到Automapper,先记录一下
一.安装Nuget包:
1. AutoMapper
2. AutoMapper.Extensions.Microsoft.DependencyInjection
二.创建AutoMapper的profile类

public class MyProfile:Profile
{
    public MyProfile()
    {
        CreateMap<SysUser, SysUserDTO>();
        CreateMap<Advance, AdvanceDTO>();
    }
}

三.StartUp类中配置Automapper
1.如果使用单个profile类,在ConfigureServices方法中加入:

services.AddAutoMapper(config =>
{
    config.AddProfile<MyProfile>();
});

2.如果使用多个profile类或者profile在不同的项目中,则加入如下:

services.AddAutoMapper(config =>
{
    var types = Assembly
                    .GetEntryAssembly()
                    .GetReferencedAssemblies()
                    .Select(Assembly.Load)
                    .SelectMany(y => y.DefinedTypes)
                    .Where(type => typeof(Profile).GetTypeInfo().IsAssignableFrom(type.AsType())
                    && type.FullName.ToUpper().StartsWith("CHCMS")
                    && type.BaseType.Equals(typeof(Profile)));//CHCMS是我的项目命名空间开头部分,请修改为你项目的对应命名空间开头部分.
    var typeList = new List<Type>();
    foreach (var ti in types)
    {
        var type = ti.AsType();
        typeList.Add(type);
    }
    config.AddProfiles(typeList);
});

四.开始使用

var user = _dbContext.SysUser.Where(x => x.UserName.Equals(userName)).FirstOrDefault();
var userDto = Mapper.Map<SysUserDTO>(user);

.net coe 类库中使用ef core遇到的问题

1.提示”dotnet-ef未知命令”:
在csproj文件中新增itemgroup,加入如下:

<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="2.0.0" />

2.dotnet ef migrations add xxx 时提示如下信息
The specified framework version ‘2.0’ could not be parsed
The specified framework ‘Microsoft.NETCore.App’, version ‘2.0’ was not found.
– Check application dependencies and target a framework version installed at:
\
– Alternatively, install the framework version ‘2.0’.
此信息的错误原因为未指定runtime版本号. 在 节点下加入如下:

<RuntimeFrameworkVersion>2.0.3</RuntimeFrameworkVersion>

版本号试项目而定
3. dotnet ef migrations add xxx时提示如下错误:
Unable to create an object of type ‘xxxDbContext’. Add an implementation of ‘IDesignTimeDbContextFactory‘ to the project, or see https://go.microsoft.com/fwlink/?linkid=851728 for additional patterns supported at design time.
此错误为,没有找到dbcontext的初始化信息,未指定所用数据库以及数据库链接字符串.
解决此问题需要在项目中 dbcontext同目录下增加 IDesignTimeDbContextFactory 接口的实现类

    public class DesignTimeDbContextFactory : IDesignTimeDbContextFactory<CHCmsDbContext>

    {
       

        public CHCmsDbContext CreateDbContext(string[] args)

        {

            var builder = new DbContextOptionsBuilder<CHCmsDbContext>();
           
            builder.UseMySql("Server=localhost;User=chcms;Password=123456;DataBase=CHCms;");

            return new CHCmsDbContext(builder.Options);

        }

    }

[转]ASP.Net MVC Core-依赖注入IoC

一、Ioc

IoC全称Inverse of Control,控制反转。

类库和框架的不同之处在于,类库是实现某种单一功能的API,框架是针对一个任务把这些单一功能串联起来形成一个完整的流程,这个流程在一个引擎驱动下被执行。

IoC的总体设计是要把在应用程序的流程控制转移到框架中,实现对流程的复用,这 符合软件设计的基本原则-重用性。

IoC不是一种设计模式,它是一种设计原则。并且很多设计模式都遵循了这种原则。比如:模板模式、工厂模式、抽象工厂模式。

 

二、DI

DI全称Dependency Indection,依赖注入。它是IoC模式的一种。

我们写的对象要完成某个功能可能要依赖于另外一个对象,比如我们要添加一个人员,往往会在逻辑层调用数据操作层来实现添加到数据库的操作。DI会在程序初始化的时候,把数据操作层的接口和实现接口的类关联起来,那么在逻辑层我们只要声明要调用的具体数据操作层的接口,调用接口的方法,这样就实现了逻辑层和数据操作层的解耦。

所谓依赖注入可以理解为一种针对依赖的字段或者属性的一种自动初始化方式。

 

举例说明,当我们通过VS2015创建了一个.Net Core项目,并且带有个人身份验证Identity,具体如何创建在此不再细说。

我们看到在Startup中,有个方法

 

// This method gets called by the runtime. Use this method to add services to the container.

        public void ConfigureServices(IServiceCollection services)

        {

            // Add framework services.

            services.AddDbContext<ApplicationDbContext>(options =>

                options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

            services.AddIdentity<ApplicationUser, IdentityRole>()

                .AddEntityFrameworkStores<ApplicationDbContext>()

                .AddDefaultTokenProviders();

            services.AddMvc();

            // Add application services.

            services.AddTransient<IEmailSender, AuthMessageSender>();

            services.AddTransient<ISmsSender, AuthMessageSender>();

        }

Controller中:

[Authorize]
public class AccountController : Controller
{
     private readonly UserManager<ApplicationUser> _userManager;
     private readonly SignInManager<ApplicationUser> _signInManager;
     private readonly IEmailSender _emailSender;
     private readonly ISmsSender _smsSender;
     private readonly ILogger _logger;

     public AccountController(
         UserManager<ApplicationUser> userManager,
         SignInManager<ApplicationUser> signInManager,
         IEmailSender emailSender,
         ISmsSender smsSender,
         ILoggerFactory loggerFactory)
     {
         _userManager = userManager;
         _signInManager = signInManager;
         _emailSender = emailSender;
         _smsSender = smsSender;
         _logger = loggerFactory.CreateLogger<AccountController>();
     }
}

我们看到,我们并没有实例化AccountController中的字段,只是在构造函数把相关联的字段当做参数传进来,系统自动就会实例化。这个实例化的过程就是DI帮助我们完成的。

DI注入方式有3种:构造器注入、属性注入、方法注入。

DI容器就是一个服务的提供者。当需要某个服务的时候,只要从DI容器中获取就可以。

三、.NET 中的DI

ServiceProvider对象是DI容器的核心对象,他在Microsoft.Extensions.DependencyInjection.dll程序集的同名命名空间下。是一个程序集内部类。这个类的主要任务就是根据服务类型提供服务对象。

与这个类相关联的类有:ServiceCallSite、Service、ServiceEntry和ServiceTable。他们都有相应的接口。

1.ServiceCallSite

服务的最终提供者就是这个对象,这个对象继承自接口IServiceCallSite。

namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
{
    using Microsoft.Extensions.DependencyInjection;
    using System;
    using System.Linq.Expressions;

    internal interface IServiceCallSite
    {
        Expression Build(Expression provider);
        object Invoke(ServiceProvider provider);
    }
}

当需要一个服务实例的时候,会去对应的ServiceCallSite中调用Invoke方法,返回需要的实例。同时,还会调用Invoke方法返回表达式,并会缓存起来,等到下次获取相同实例的时候直接获取。

2.Service

当我们获取服务对象实例的时候是根据ServiceCollection提供的。ServiceCollection和我们连接非常紧密,就是我们在Startup中添加的服务于实现的关联。

ServiceCollection中保存的就是ServiceDescriptor,每个ServiceDescriptor都会被转化成Service,继承自接口IService。

namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
{
    using Microsoft.Extensions.DependencyInjection;
    using System;
    using System.Collections.Generic;

    internal interface IService
    {
        IServiceCallSite CreateCallSite(ServiceProvider provider, ISet<Type> callSiteChain);

        ServiceLifetime Lifetime { get; }

        IService Next { get; set; }
    }
}

我们看到,这个接口中有CreateCallSite得到一个ServiceCallSite实例。每个Service都是以链表的形式存在,Next表示链表的下一个节点,这个链表就是具有相同的服务类型组成。

3.ServiceEntry

表示上面说的链表。

namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
{
    using System;
    using System.Runtime.CompilerServices;

    internal class ServiceEntry
    {
        private object _sync = new object();

        public ServiceEntry(IService service)
        {
            this.First = service;
            this.Last = service;
        }

        public void Add(IService service)
        {
            object obj2 = this._sync;
            lock (obj2)
            {
                this.Last.Next = service;
                this.Last = service;
            }
        }

        public IService First { get; private set; }

        public IService Last { get; private set; }
    }
}

4.ServiceTable

多个ServiceEntry组成一个ServiceTable。

namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
{
    using Microsoft.Extensions.DependencyInjection;
    using System;
    using System.Collections.Concurrent;
    using System.Collections.Generic;
    using System.Reflection;
    using System.Runtime.InteropServices;

    internal class ServiceTable
    {
        private readonly Dictionary<Type, List<IGenericService>> _genericServices = new Dictionary<Type, List<IGenericService>>();
        private readonly ConcurrentDictionary<Type, Func<ServiceProvider, object>> _realizedServices = new ConcurrentDictionary<Type, Func<ServiceProvider, object>>();
        private readonly Dictionary<Type, ServiceEntry> _services = new Dictionary<Type, ServiceEntry>();
        private readonly object _sync = new object();

        public ServiceTable(IEnumerable<ServiceDescriptor> descriptors)
        {
            foreach (ServiceDescriptor descriptor in descriptors)
            {
                if (IntrospectionExtensions.GetTypeInfo(descriptor.ServiceType).IsGenericTypeDefinition)
                {
                    TypeInfo info = (descriptor.ImplementationType == null) ? null : IntrospectionExtensions.GetTypeInfo(descriptor.ImplementationType);
                    if ((info == null) || !info.IsGenericTypeDefinition)
                    {
                        throw new ArgumentException(Resources.FormatOpenGenericServiceRequiresOpenGenericImplementation(descriptor.ServiceType), "descriptors");
                    }
                    if (info.IsAbstract || info.IsInterface)
                    {
                        throw new ArgumentException(Resources.FormatTypeCannotBeActivated(descriptor.ImplementationType, descriptor.ServiceType));
                    }
                    this.Add(descriptor.ServiceType, new GenericService(descriptor));
                }
                else if (descriptor.ImplementationInstance != null)
                {
                    this.Add(descriptor.ServiceType, new InstanceService(descriptor));
                }
                else if (descriptor.ImplementationFactory != null)
                {
                    this.Add(descriptor.ServiceType, new FactoryService(descriptor));
                }
                else
                {
                    TypeInfo typeInfo = IntrospectionExtensions.GetTypeInfo(descriptor.ImplementationType);
                    if ((typeInfo.IsGenericTypeDefinition || typeInfo.IsAbstract) || typeInfo.IsInterface)
                    {
                        throw new ArgumentException(Resources.FormatTypeCannotBeActivated(descriptor.ImplementationType, descriptor.ServiceType));
                    }
                    this.Add(descriptor.ServiceType, new Service(descriptor));
                }
            }
        }

        public void Add(Type serviceType, IGenericService genericService)
        {
            object obj2 = this._sync;
            lock (obj2)
            {
                List<IGenericService> list;
                if (!this._genericServices.TryGetValue(serviceType, ref list))
                {
                    list = new List<IGenericService>();
                    this._genericServices.set_Item(serviceType, list);
                }
                list.Add(genericService);
            }
        }

        public void Add(Type serviceType, IService service)
        {
            object obj2 = this._sync;
            lock (obj2)
            {
                ServiceEntry entry;
                if (this._services.TryGetValue(serviceType, ref entry))
                {
                    entry.Add(service);
                }
                else
                {
                    this._services.set_Item(serviceType, new ServiceEntry(service));
                }
            }
        }

        public bool TryGetEntry(Type serviceType, out ServiceEntry entry)
        {
            object obj2 = this._sync;
            lock (obj2)
            {
                if (this._services.TryGetValue(serviceType, ref entry))
                {
                    return true;
                }
                if (IntrospectionExtensions.GetTypeInfo(serviceType).IsGenericType)
                {
                    List<IGenericService> list;
                    Type genericTypeDefinition = serviceType.GetGenericTypeDefinition();
                    if (this._genericServices.TryGetValue(genericTypeDefinition, ref list))
                    {
                        using (List<IGenericService>.Enumerator enumerator = list.GetEnumerator())
                        {
                            while (enumerator.MoveNext())
                            {
                                IService service = enumerator.get_Current().GetService(serviceType);
                                if (service != null)
                                {
                                    this.Add(serviceType, service);
                                }
                            }
                        }
                        return this._services.TryGetValue(serviceType, ref entry);
                    }
                }
            }
            return false;
        }

        public ConcurrentDictionary<Type, Func<ServiceProvider, object>> RealizedServices
        {
            get
            {
                return this._realizedServices;
            }
        }
    }
}

主要的方法就是在ServiceTable构造函数中,根据传过来的ServiceDescriptor列表,其实就是我们在Startup中注册的服务与具体实现转换过来的。

在这个构造函数中会先根据ServiceType转换成ServiceEntry列表,再添加到Dictionary<Type, ServiceEntry> _services 中,也就是最终得到的是_services类型。

5.ServiceProvider

      internal class ServiceProvider : IServiceProvider, IDisposable
    {
        public ServiceProvider Root { get; private set; }
        public ServiceTable ServiceTable { get; private set; }
        public ConcurrentDictionary<Type, Func<ServiceProvider, object>> RealizedServices { get; private set; } = new ConcurrentDictionary<Type, Func<ServiceProvider, object>>();
        public IList<IDisposable> TransientDisposableServices { get; private set; } = new List<IDisposable>();
        public ConcurrentDictionary<IService, object> ResolvedServices { get; private set; } = new ConcurrentDictionary<IService, object>();
        
        public ServiceProvider(IServiceCollection services)
        {
           this.Root         = this;
           this.ServiceTable     = new ServiceTable(services);
        }
     
        public object GetService(Type serviceType)
        {
            Func<ServiceProvider, object> serviceAccessor;
            if (this.RealizedServices.TryGetValue(serviceType, out serviceAccessor))
            {
               return serviceAccessor(this);
            }
    
            IServiceCallSite serviceCallSite = this.GetServiceCallSite(serviceType, new HashSet<Type>());
            if (null != serviceCallSite)
            {
                var providerExpression = Expression.Parameter(typeof(ServiceProvider), "provider");
                this.RealizedServices[serviceType] = Expression.Lambda<Func<ServiceProvider, object>>(serviceCallSite.Build(providerExpression), providerExpression).Compile();
                return serviceCallSite.Invoke(this);
            }
    
            this.RealizedServices[serviceType] = _ => null;
            return null;
        }
     
        public IServiceCallSite GetServiceCallSite(Type serviceType, ISet<Type> callSiteChain)
        {
                try
                {
                    if (callSiteChain.Contains(serviceType))
                    {
                        throw new InvalidOperationException(string.Format("A circular dependency was detected for the service of type '{0}'", serviceType.FullName);
                    }
                    callSiteChain.Add(serviceType);
     
                   ServiceEntry serviceEntry;
                    if (this.ServiceTable.ServieEntries.TryGetValue(serviceType, 
                        out serviceEntry))
                    {
                        return serviceEntry.Last.CreateCallSite(this, callSiteChain);
                    }
     
                    //省略其他代码
     
                    return null;
                }
                finally
                {
                    callSiteChain.Remove(serviceType);
                }
        }    
     
        public void Dispose()
        {
            Array.ForEach(this.TransientDisposableServices.ToArray(), _ => _.Dispose());
            Array.ForEach(this.ResolvedServices.Values.ToArray(), _ => (_ as IDisposable)?.Dispose());
            this.TransientDisposableServices.Clear();
            this.ResolvedServices.Clear();
        }
        //其他成员
    }

以上借助他人的代码片段,ServiceProvider中主要有几个属性,root指向自己;RealizedServices 就是在我们讲解ServiceCallSite时说道,最后得到的Service会被生成委托以便下次调用,这个委托就存在这个属性中。

这个类最主要的方法就是GetService,主要逻辑就是从RealizedServices 获取当前服务的实例,如果有,直接返回,如果没有,会从ServiceTable中找到对应的ServiceEntry,如果没有返回null,如果有,调用ServiceEntry所在列表最后一个Service的CreateServiceCallSite方法创建一个ServiceCallSite对象(这一点说明了如果针对同一个服务类型注册了多个ServiceDescriptor,在提供单个服务的时候总是使用最后一个 ServiceDescriptor)。

 

综上,.net core中的DI容器的主要对象就是ServiceProvider、ServiceCallSite、Service、ServiceEntry和ServiceTable。主要的流程控制都放在ServiceProvider类中,这个类有一个ServiceTable(就是ServiceType和ServiceEntry的对应列表)。ServiceEntry就是一个链表,链接了当前ServiceType的所有的实例(不过得到的实例总是以最后一个为准),实例的类型都是Service类型。Service主要就是获取ServiceCallSite对象,这个对象就是封装了所有的获取具体服务实例的逻辑,主要通过Invoke得到实例,再调用Build生成表达式委托,存在ServiceProvider中。

ServiceProvider主要有一个方法GetService获取服务实例。主要逻辑就是从RealizedServices 获取当前服务的实例,如果有,直接返回,如果没有,会从ServiceTable中找到对应的ServiceEntry,如果没有返回null,如果有,调用ServiceEntry所在列表最后一个Service的CreateServiceCallSite方法创建一个ServiceCallSite对象(这一点说明了如果针对同一个服务类型注册了多个ServiceDescriptor,在提供单个服务的时候总是使用最后一个 ServiceDescriptor)。

 

过后思考:

1. ServiceCallSite:获取我们要的最终的服务实例并缓存起来以备下次调用。

2.Service:获取ServiceCallSite,由我们注册的服务转换而来,链表形式存在,整个链表表示相同实例类型的实例。

3.ServiceEntry:对链表Service的封装,有First、Last表示链表的第一个和最后一个,还有个Add方法。

4.ServiceTable:主要对象就是Dictionary<Type,ServiceEntry> _service,表示服务类型和服务实例链表的对应关系。

5.ServiceProvider:整个DI功能的控制者,主要方法就是GetService,根据类型获取实例。

 

原文地址:http://www.cnblogs.com/zh1990/p/5674128.html