依赖注入、配置、日志

一、依赖注入DI

需要引入包 Microsoft.Extensions.DependencyInjection

1、基本使用
internal class Program
    {
        static void Main(string[] args)
        {
            IServiceCollection services = new ServiceCollection();//构造容器对象
            services.AddSingleton<IClass, Class1>();//想容器中注入一个服务

            using (ServiceProvider provider = services.BuildServiceProvider())
            {
                IClass ic = provider.GetRequiredService<IClass>();
                ic.Name = "ccc";
                Console.WriteLine(ic.Name);
            }
        }
    }

    public class Class1:IClass
    {
        public string Name { get; set; }
        public int Age { get; set; }
    }

    public interface IClass
    {
        public string Name { get; set; }
    }
internal class Program
    {
        static void Main(string[] args)
        {
            IServiceCollection services = new ServiceCollection();//构造容器对象
            services.AddSingleton<IClass, Class1>();//想容器中注入一个服务

            using (ServiceProvider provider = services.BuildServiceProvider())
            {
                using (IServiceScope scope = provider.CreateScope())
                {
                    IClass ic1 = scope.ServiceProvider.GetRequiredService<IClass>();
                    ic1.Name = "cheng1";
                    Console.WriteLine(ic1.Name);//cheng1

                    IClass ic2 = scope.ServiceProvider.GetRequiredService<IClass>();
                    ic2.Name = "cheng2";
                    Console.WriteLine(ic2.Name);//cheng2,此时ic1和ic2的name都是cheng2

                    Console.WriteLine(ic1.Name);//cheng2
                    Console.WriteLine(Object.ReferenceEquals(ic1,ic2));//true,因为是在同一个scoped范围内的
                }
            }
        }
    }

    public class Class1:IClass
    {
        public string Name { get; set; }
        public int Age { get; set; }
    }

    public interface IClass
    {
        public string Name { get; set; }
    }

2、三种生命周期

2.1、瞬时模式(Transient)

定义:在瞬时生命周期中,每次从依赖注入容器中请求服务时,都会创建一个新的服务实例。

使用场景:适用于轻量级的、无状态的服务,这些服务不需要在多个请求之间共享状态。

public void ConfigureServices(IServiceCollection services)  
{  
    services.AddTransient<IMyTransientService, MyTransientService>();  
}

2.2、作用域模式(Scoped)

定义:在作用域生命周期中,服务实例是在同一个作用域内共享的。对于Web应用程序,作用域通常与HTTP请求相关联,即每个HTTP请求都会创建一个新的作用域,并在请求结束时销毁该作用域及其中的所有服务实例。

使用场景:适用于需要在同一请求内共享状态的服务,如数据库上下文、httpcontext、会话状态等。

public void ConfigureServices(IServiceCollection services)  
{  
    services.AddScoped<IMyScopedService, MyScopedService>();  
}

2.3、单例模式(Singleton)

定义:在单例生命周期中,整个应用程序生命周期内只创建一个服务实例,并且该实例会在整个应用程序中共享。

使用场景:适用于那些在整个应用程序中需要保持不变的服务,如配置服务、日志记录器等。

public void ConfigureServices(IServiceCollection services)  
{  
    services.AddSingleton<IMySingletonService, MySingletonService>();  
}

3、注入方式

3.1、构造函数注入

构造函数注入是最常见和推荐的依赖注入方式。在这种方式中,依赖项作为参数传递给类的构造函数。这确保了类的依赖项在对象被创建时就已经被注入,并且对象在整个生命周期内都持有这些依赖项的引用。

public interface IMyDependency  
{  
    void DoSomething();  
}  
  
public class MyDependency : IMyDependency  
{  
    public void DoSomething()  
    {  
        // 实现逻辑  
    }  
}  
  
public class MyClass  
{  
    private readonly IMyDependency _dependency;  
  
    public MyClass(IMyDependency dependency)  
    {  
        _dependency = dependency;  
    }  
  
    // 使用 _dependency 调用所需的方法  
}  
  
// 在Startup.cs或Program.cs中配置依赖注入  
public void ConfigureServices(IServiceCollection services)  
{  
    services.AddTransient<IMyDependency, MyDependency>();  
}

3.2、方法注入

方法注入是一种较为少见的依赖注入方式,它通过在类的方法中注入依赖项来实现。这种方法通常用于需要动态地或基于特定条件来注入依赖项的场景。

public class MyClass  
{  
    private IMyDependency _dependency;  
  
    public void SetDependency(IMyDependency dependency)  
    {  
        _dependency = dependency;  
    }  
  
    public void DoSomething()  
    {  
        // 使用 _dependency 调用所需的方法  
    }  
}  
  
// 实际上,在配置依赖注入时,你仍然需要在Startup.cs或Program.cs中注册服务  
// 但在使用时,你需要手动调用SetDependency方法来注入依赖项

3.3、属性注入

属性注入是另一种依赖注入方式,它通过类的属性来注入依赖项。虽然属性注入在某些情况下很有用,但它不如构造函数注入那样受到推荐,因为它可能使类的依赖关系不那么明显,并且可能允许类的实例在完全初始化之前就被使用。

需要注意的是,在ASP.NET Core的默认依赖注入容器中,并不直接支持属性注入到控制器中,但可以通过其他方式(如中间件、视图等)实现属性注入,或者使用第三方库来支持。

public class MyClass  
{  
    // 假设有某种方式支持属性注入  
    public IMyDependency Dependency { get; set; }  
  
    // 使用 Dependency 调用所需的方法  
}  
  
// 实际上,在ASP.NET Core中,你可能需要自定义实现或使用第三方库来支持属性注入

二、配置系统

1、基础用法

nuget包 Microsoft.Extensions.Configuration,Microsoft.Extensions.Configuration.Json

config.json文件如下,要属性设置为始终复制

{
  "student1": {
    "name": "kawa1",
    "age": "18",
    "address": {
      "province": "henan",
      "xian": "luyi"
    }
  },
  "student2": "kawa2"
}

namespace Configuration_lianxi
{
    internal class Program
    {
        static void Main(string[] args)
        {
            //optional表示文件是否可选,true表示文件不存在时,不会报错,否则反之。
            //reloadonchange表示如果文件更改了是否重新加载配置
            IConfigurationBuilder configurationBuilder = new ConfigurationBuilder()
                .SetBasePath(Directory.GetCurrentDirectory())//SetBasePath方法设置了配置文件的基路径
                .AddJsonFile("config.json",optional:false,reloadOnChange:true);//基路径下的config.json配置文件

            IConfigurationRoot root = configurationBuilder.Build();
            Console.WriteLine(root["student2"]);//kawa2
            Console.WriteLine(root.GetSection("student2").Value);//kawa2
            Console.WriteLine(root.GetSection("student1:age").Value);//18

            //需要nuget包 Microsoft.Extensions.Configuration.Binder
            Console.WriteLine(root.GetValue<int>("student1:age"));//18(int类型)
            Class1 class1 = root.GetSection("student1:address").Get<Class1>();//绑定到Class1
            Console.WriteLine(class1.province);//henan
        }
    }

    class Class1
    {
        public string province { get; set; }
        public string xian { get; set; }
    }

}

在web api中也可以这样读取配置

namespace Configuration_lianxi2.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class Test2Controller : ControllerBase
    {

        private readonly IConfiguration _configuration;
        public Test2Controller(IConfiguration configuration)
        {
            this._configuration = configuration;
        }

        [HttpGet]
        public string Method1()
        {
            string str1 = _configuration.GetSection("Student1:name").Value;//kawa1
            return str1;
        }

    }
}

2、选项方式读取配置

使用选项方式读取配置是.NET Core中推荐的方式,因为他不仅和依赖注入机制结果得更好,而且它可以实现配置修改后自动刷新。

IOptions<T>:在配置改变后,我们不能读取新的值,必须重启程序。

IOptionsMonitor<T>:配置改变后,可以读到新的值。

IOptionsSnapshot<T>:配置改变后,可以读到新的值,与上者不同的是,上者在同一范围内会保持一致性。

(比如A,B代码都读取同一个配置,在A运行后B运行前更改了配置,使用IOptionsMonitor的话A读取的是旧值,B是新值,使用IOptionsSnapshot的话A,B都是旧值,只有再次进入这个范围才会是新值)

首先配置json,appsetting.json

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "student1": {
    "name": "kawa1",
    "age": "18",
    "address": {
      "province": "henan",
      "xian": "luyi"
    }
  },
  "student2": "kawa2"
}

再定义实体类用于绑定配置

public class Student1
    {
        public string Name { get; set; }
        public int age { get; set; }

        public Address Address { get; set; }
    }

    public class Address
    {
        public string Province { get; set; }
        public string xian { get; set; }


    }

之后,在program.cs中注入服务

//选项配置
builder.Services.Configure<Student1>(builder.Configuration.GetSection("Student1"));
builder.Services.Configure<Address>(builder.Configuration.GetSection("student1:address"));

最后,新建控制器测试

namespace Configuration_lianxi2.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class Test1Controller : ControllerBase
    {
        private readonly IOptionsSnapshot<Student1> _optionsSnapshot;
        private readonly IOptionsSnapshot<Address> _optionsSnapshot1;
        public Test1Controller(IOptionsSnapshot<Student1> optionsSnapshot, IOptionsSnapshot<Address> optionsSnapshot1)
        {
            _optionsSnapshot = optionsSnapshot;
            _optionsSnapshot1 = optionsSnapshot1;
        }

        [HttpGet]
        public IActionResult Method1()
        {
            ArrayList list1 = new();
            string str1 = _optionsSnapshot.Value.Name;//kawa1
            int str2 = _optionsSnapshot.Value.age;//18
            string str3 = _optionsSnapshot1.Value.Province;//henan
            
            return Ok(list1);
        }

    }
}

三、日志系统

1、基础

1.1、日志级别

Trace: 用于详细诊断信息,通常只在诊断问题时启用。
Debug: 用于调试信息,通常在开发过程中使用。
Information: 用于常规信息性消息,确认程序按预期工作。
Warning: 用于表示某些意外事件的提示,或者表明一些问题在不久的将来可能会发生(例如“磁盘空间低”)。程序仍然按预期工作。
Error: 用于严重错误,导致程序无法继续正常运行。
Critical: 用于非常严重的错误,表明程序本身可能无法继续运行。

2、NLog

特点:部署在本地文件,日志结构不明确

1.1、装包:NLog.Extensions.Logging

1.2、项目根目录下建配置文件:nlog.config(建议这个名字),设置为“如果较新则赋值”

<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

    <!--输出目标,往哪里输出-->
    <targets>
        <!--type:日志输出类型 File:文件 ColoredConsole:控制台-->
        <!--fileName:日志存储的路径及名称-->
        <!--layout:日志输出格式-->

        <target name="log_file" xsi:type="File"
          fileName="${basedir}/Logs/${shortdate}/${shortdate}.txt"
          layout="${longdate} | ${event-properties:item=EventId_Id:whenEmpty=0} | ${uppercase:${level}} | ${logger} | ${message} ${exception:format=tostring}"
          archiveFileName="${basedir}/archives/${shortdate}-{#####}.txt"
          archiveAboveSize="102400"
          archiveNumbering="Sequence"
          concurrentWrites="true"
          keepFileOpen="false" />

        <!--<target name="console" xsi:type="ColoredConsole" layout="[${date:format=HH\:mm\:ss}]:${message} ${exception:format=message}" />-->
    </targets>

    <!--定义使用哪个target输出-->
    <rules>
        <logger name="*" minlevel="Trace" writeTo="log_file" />
        <!--<logger name="*" minlevel="Debug" writeTo="console" />-->
    </rules>
</nlog>

1.3、program.cs

builder.Logging.AddNLog("nlog.config");

var app = builder.Build();

1.4、新建个API用来测试

namespace NlogTest.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class NlogTest1 : ControllerBase
    {
        private readonly ILogger<NlogTest1> logger;
        public NlogTest1(ILogger<NlogTest1> logger)
        {
            this.logger = logger;
        }


        [HttpGet]
        public IActionResult Test1()
        {
            logger.LogError("好像报错了");
            return Ok("okle ");
        }

    }
}

结果:会在根目录下的bin文件下生成一个log文件夹

3、Serilog

  • Serilog.AspNetCore:ASP.NET Core的集成包。
  • Serilog.Sinks.File:用于将日志写入文件的包。

program.cs

// 配置Serilog  
Log.Logger = new LoggerConfiguration()
    .MinimumLevel.Debug() // 设置最小日志级别  
    .MinimumLevel.Override("Microsoft", LogEventLevel.Information) // 覆盖Microsoft的日志级别  
    .Enrich.FromLogContext() // 从日志上下文中丰富日志  
    .WriteTo.Console(new CompactJsonFormatter()) // 输出到控制台  
    .WriteTo.File("logs/myapp-.txt", rollingInterval: RollingInterval.Day) // 按天滚动写入文件  
                                                                           // 如果需要,可以添加更多接收器,如数据库等  
    .CreateLogger();

builder.Host.UseSerilog(); // 将Serilog注册为日志提供者  

新建控制器方法测试

namespace Serilog_lianxi.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class Test1Controller : ControllerBase
    {
        private readonly ILogger<Test1Controller> _logger;
        public Test1Controller(ILogger<Test1Controller> logger)
        {
            _logger = logger;
        }

        [HttpGet]
        public string Method1()
        {
            _logger.LogInformation("我来了");
            return "猜猜我是谁";
        }
    }
}

四、缓存

缓存指的是在软件应用运行过程中,将一些数据生成副本直接进行存取,而不是从原始源(数据库,业务逻辑计算等)读取数据,减少生成内容所需的工作,从而显著提高应用的性能和可伸缩性。

1、缓存分为:响应缓存(客户端响应缓存、服务器端响应缓存)、内存缓存、分布式缓存

2、缓存命中:从缓存中获取到了要获取的数据

缓存命中率:多次请求中,命中的请求占全部请求的百分比

1、客户端响应缓存

不需要手动控制响应报文头的cache-control,只需要给需要缓存的控制器的操作方法添加ResponCache特性即可

namespace WebAPI0407_Cache.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class TestController : ControllerBase
    {
        //浏览器端缓存时间60s
        [HttpGet]
        [ResponseCache(Duration = 60)]
        public DateTime GetNow()
        {
            return DateTime.Now;
        }

    }
}

2、服务器端响应缓存

1、除了需要添加[ResponseCache(Duration = 60)]特性以外,还需要在program的app.MapControllers()之前添加app.UseResponseCaching()。

2、如果启用了CORS(跨域),要确保app.UseCors写在app.UseResponseCaching之前

3、内存缓存

 1、内存缓存保存的是一系列的键值对,类似Dictionary。多个不同网站是运行在不同进程中的,所以不同网站的内存缓存是互不干扰的。网站重启或服务器故障,则清空内存缓存数据。nuget包是Microsoft.Extensions.Caching.Memory

2、使用:在program.cs的builder.Build()前添加builder.Services.AddMemoryCache();来把内存缓存相关服务注册到依赖注入容器中。

3、然后使用nuget包中的IMemoryCache接口。其中有一个经常用的异步方法GetOrCreateAsync

Task GetOrCreateAsync(this IMemoryCache cache, object key, Func> factory)

尝试获取缓存键为key的缓存值,方法的返回值为获取的缓存值。如果没有为缓存键key的缓存值,则调用factory指向的回调函数从数据源去获取数据,把获取到的数据作为返回值以及缓存值

using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Caching.Memory;

namespace WebAPI0407_Cache.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class TestController : ControllerBase
    {
        public readonly ILogger<TestController> _logger;
        private readonly MyDbcontent _myDbcontent;
        private readonly IMemoryCache _memoryCache;

        public TestController(ILogger<TestController> logger, MyDbcontent myDbcontent, IMemoryCache memoryCache)
        {
            _logger = logger;
            _myDbcontent = myDbcontent;
            _memoryCache = memoryCache;
        }

        [HttpGet]
        public async Task<Book[]> GetBooks()
        {
            _logger.LogInformation("开始执行GetBooks");
            var items = await _memoryCache.GetOrCreateAsync("AllBooks", async (o) =>
            {
                _logger.LogInformation("从数据库中读取数据");
                return await _myDbcontent.Books.ToArrayAsync();
            });
            _logger.LogInformation("把数据库返回个调用者");
            return items;
        }
    }
}

4、通过设置缓存过期时间来解决缓存数据不一致的问题。

a1)、绝对过期时间:自设置缓存之后的指定时间过期

a2)、滑动过期时间:设置完时间后,如果在过期时间内被访问了,则重新计时过期时间。

过期时间通过GetOrCreateAsync中的IcacheEntry类型的参数来设置

b1)AbsoluteExpirationRelativeToNow用来设置绝对过期时间

[HttpGet]
        public async Task<Book[]> GetBooks()
        {
            _logger.LogInformation("开始执行GetBooks");
            var items = await _memoryCache.GetOrCreateAsync("AllBooks", async (o) =>
            {
                o.AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(10);//AbsoluteExpirationRelativeToNow用来设置绝对过期时间
                _logger.LogInformation("从数据库中读取数据");
                return await _myDbcontent.Books.ToArrayAsync();
            });
            _logger.LogInformation("把数据库返回个调用者");
            return items;
        }

b2)SlidingExpiration用来设置滑动过期时间

[HttpGet]
        public async Task<Book[]> GetBooks()
        {
            _logger.LogInformation("开始执行GetBooks");
            var items = await _memoryCache.GetOrCreateAsync("AllBooks", async (o) =>
            {
                o.SlidingExpiration = TimeSpan.FromSeconds(10);//SlidingExpiration用来设置滑动过期时间
                _logger.LogInformation("从数据库中读取数据");
                return await _myDbcontent.Books.ToArrayAsync();
            });
            _logger.LogInformation("把数据库返回个调用者");
            return items;
        }

b3)绝对过期时间和滑动过期时间混合使用

[HttpGet]
        public async Task<Book[]> GetBooks()
        {
            _logger.LogInformation("开始执行GetBooks");
            var items = await _memoryCache.GetOrCreateAsync("AllBooks", async (o) =>
            {
                o.AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(30);//AbsoluteExpirationRelativeToNow用来设置滑动过期时间
                o.SlidingExpiration = TimeSpan.FromSeconds(10);//AbsoluteExpirationRelativeToNow用来设置滑动过期时间
                _logger.LogInformation("从数据库中读取数据");
                return await _myDbcontent.Books.ToArrayAsync();
            });
            _logger.LogInformation("把数据库返回个调用者");
            return items;
        }

5、缓存穿透问题的规避

缓存穿透问题指的是访问的资源不存在,数据为null,导致所有访问都会去查询数据库,从而增加数据库压力的问题。

下面这种写法会引起缓存穿透:

 因为当数据库中不存在查询的数据时,变量b为null,再次访问,又会去访问数据库 。

解决办法:使用GetOrCreate的写法对数据进行缓存,不会导致缓存穿透,因为查询结果虽然为null,但也会被放入缓存中。

6、缓存雪崩问题

缓存项集中过期,引起请求在一段时间内同时访问数据库,导致数据库压力增大,这样的现象称为缓存雪崩。

解决缓存雪崩问题,可以在缓存的过期时间之上,增加一个随机的过期时间,避免在同一时间内大量缓存项同时失效。

4、Redis缓存

redis安装教程连接 Windows 安装Redis教程(图文详解)_下载使用redis_Redis可视化_配置Redis环境变量_redis-server redis.windows.conf-CSDN博客

1、包 Microsoft.Extensions.Caching.StackExchangeRedis

2、program.cs

//添加Redis缓存服务,最好放到配置文件中,这里省事了
builder.Services.AddStackExchangeRedisCache(option =>
{
    option.Configuration = "localhost";
    option.InstanceName = "webapi_kawa_";//添加前缀,避免缓存名称混乱
});

3、测试,引入依赖包中的IDistributedCache接口

namespace RedisCache_lianxi.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class Test1Controller : ControllerBase
    {
        private readonly IDistributedCache _distributedCache;
        public Test1Controller(IDistributedCache distributedCache)
        {
            _distributedCache = distributedCache;
        }

        [HttpGet]
        public string Method1()
        {
            string str1 = _distributedCache.GetString("kawa1");
            if(str1 == null)
            {
                _distributedCache.Set("kawa1", Encoding.UTF8.GetBytes(DateTime.Now.ToString()) , new DistributedCacheEntryOptions().SetAbsoluteExpiration(TimeSpan.FromMinutes(1)));
                str1 = _distributedCache.GetString("kawa1");
            }            
            return str1;//一分钟后缓存值才会刷新
        }

    }
}

Redis缓存可视化界面效果

注:缓存穿透等问题参考内存缓存

  • 11
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值