1 原理
Redis数据库量:以延时操作为基础,把数据库中的数据缓存到Redis数据库中,以减少用户对数据库进行直接访问的频率,从而提高程序的渲染显示性能。在使用Redis数据库,为页面的渲染显示提供数据时,由于Redis数据库所缓存数据的延时性,即页面所渲染显示的数据是几分钟以前的,1个用户最新向数据库提交的数据,是不能立即被其他的用户看到的;因为。为了解决这个问题nopCommerce开发者为我们提供了触发注销器的解决方案,原理如下图所示:
2 准备工作
2.1 OptenTypeExtension
///
/// 【继承于泛型扩展】
///
///
/// 摘要:
/// 该类中的方法用于验证1个指定继承目标类/接口,是否继承于1个指定的泛型类/接口。
/// 说明:
/// 由于“IsAssignableFrom”只能用于验证1个指定类/接口是否继承于1个指定的非泛型的类/接口,如果用于验证泛型的类/接口,则会一直返回:false,
/// 所以定义了该扩展类,以实现对泛型的类/接口继承的验证操作。
///
public static class OptenTypeExtension
{
/// name="openGeneric">1个被继承的指定泛型类/接口的类型实例。
/// name="type">1个指定继承目标类/接口的类型实例。
///
/// 【继承于泛型?】
///
/// 摘要:
/// 获取1个值false(不继承于泛型)/true(继承于泛型),该值指示1个指定类/接口是否继承于1个指定的泛型类/接口。
/// 说明:
/// 由于“IsAssignableFrom”只能用于验证1个指定类/接口是否继承于1个指定的非泛型的类/接口,如果用于验证泛型的类/接口,则会一直返回:false。
///
///
/// 返回:
/// 1个值false(不继承于泛型)/true(继承于泛型)。
///
///
public static bool DoesTypeImplementOpenGeneric(this Type openGeneric, Type type)
{
try
{
//获取1个被继承的指定泛型类/接口的类型实例。
var genericTypeDefinition = openGeneric.GetGenericTypeDefinition();
//依次对继承目标类/接口,中的所有被继承的类/接口的类型实例进行验证,只要所继承的目标类/接口中有1个是泛型的就返回:true。
foreach (var implementedInterface in type.FindInterfaces((objType, objCriteria) => true, null))
{
//如果被继承的类/接口的类型实例,不是泛型的,则继续验证继承目标类/接口中其它的被继承的类/接口的类型实例。
if (!implementedInterface.IsGenericType)
continue;
//如果被继承的类/接口的类型实例是泛型的,就立即返回:true,因为已经可以确认目标类/接口继承了指定泛型类/接口。
if (genericTypeDefinition.IsAssignableFrom(implementedInterface.GetGenericTypeDefinition()))
return true;
}
return false;
}
catch
{
return false;
}
}
}
2.2 ServiceProviderExtension
public static class ServiceProviderExtension
{
public static ServiceProvider ServiceProvider { get; set; }
}
3 注销器接口(IConsumer
3.1 IConsumer
namespace Linkage.Events
{
///
///
/// 【注销器--接口】
/// 摘要:
/// 如果1指类型的实例发生改变,需要对定义在任意命名空间的该实例,进行更新,
/// 那么触发器(EventPublisher)就会通过所有的(继承于Consumer
/// 说明:
/// nopCommerce开发者的定义该方法的主要意图是:在有数据的增/删/修操作发生时,通过触发器(EventPublisher),来对缓存在Redis数据库中的数据进行即时的更新,
/// 从而使页面始在渲染显示时,始终能够以最新的缓存数据,呈现给其他的用户,
///
public interface IConsumer<T>
{
#region 方法
/// name="eventMessage">1个指定指定类(T)的实例。
///
/// 【异步触发句柄】
///
/// 摘要:
/// 如果1指类型的实例发生改变,需要对定义在任意命名空间的该实例,进行更新,
/// 那么触发器(EventPublisher)就会通过所有的(继承于Consumer
/// 说明:
/// nopCommerce开发者的定义该方法的主要意图是:在有数据的增/删/修操作发生时,通过触发器(EventPublisher),来对缓存在Redis数据库中的数据进行即时的更新,
/// 从而使页面始在渲染显示时,始终能够以最新的缓存数据,呈现给其他的用户。
///
///
Task HandleEventAsync(T eventMessage);
#endregion
}
}
3.2 Linkage.Models.DateTimeConsumerModel
using Linkage.Events;
namespace Linkage.Models
{
///
/// 【时间注销器--类】
/// 摘要:
/// 如果时间类型(DateTime)的实例发生改变时,需要对定义在Linkage.Models.DateTimeConsumerModel命名空间的时间类型(DateTime)的实例,进行更新,
/// 那么触发器(EventPublisher)就会调用当前类中的“HandleEventAsync”方法,来对定义在Linkage.Models.DateTimeConsumerModel命名空间的时间类型(DateTime)的实例,进行更新。
///
public class DateTimeConsumerModel : IConsumer
{
#region 属性
///
/// 【时间】
///
/// 摘要:
/// 获取/设置触发器更新操作完成后的时间值(需要被更新的时间类型(DateTime)的实例)。
///
///
public static DateTime DateTime { get; set; }
#endregion
#region 方法
/// name="eventMessage">时间类型(DateTime)的实例。
///
/// 【异步触发句柄】
///
/// 摘要:
/// 如果时间类型(DateTime)的实例发生改变,需要对定义在Linkage.Models.DateTimeConsumerModel 命名空间的时间类型(DateTime)的实例,进行更新,
/// 那么触发器(EventPublisher)就会调用当前类中的“HandleEventAsync”方法,来对定义在Linkage.Models.DateTimeConsumerModel 命名空间的时间类型(DateTime)的实例,进行更新。
///
///
public Task HandleEventAsync(DateTime eventMessage)
{
//把触发器更新操作完成后的时间值,存储到当前类的静态属性成员(需要被更新的时间类型(DateTime)的实例)中。
DateTime = eventMessage;
return Task.CompletedTask;
}
#endregion
}
}
3.3 Linkage.Controllers.PrincipleController.DateTimeConsumer
using Linkage.Events;
using Linkage.Models;
using Microsoft.AspNetCore.Mvc;
namespace Linkage.Controllers
{
public class PrincipleController : Controller
{
public static DateTime _dateTime = DateTime.Now;
private static bool _isUpdate = false;
private readonly IEventPublisher _eventPublisher;
public PrincipleController(IEventPublisher eventPublisher)
{
_eventPublisher = eventPublisher;
}
public async Task
{
if (_isUpdate)
{
await _eventPublisher.PublishAsync(_dateTime.AddYears(10));
}
else
{
DateTimeConsumer.DateTime = _dateTime;
DateTimeConsumerModel.DateTime = _dateTime;
}
return View();
}
public IActionResult Upate()
{
_isUpdate = _isUpdate == false ? true : false;
return RedirectToAction("Index");
}
///
/// 【时间注销器--类】
/// 摘要:
/// 如果时间类型(DateTime)的实例发生改变时,需要对定义在Linkage.Controllers.PrincipleController.DateTimeConsumer命名空间的时间类型(DateTime)的实例,进行更新,
/// 那么触发器(EventPublisher)就会调用当前类中的“HandleEventAsync”方法,来对定义在Linkage.Controllers.PrincipleController.DateTimeConsumer命名空间的时间类型(DateTime)的实例,进行更新。
///
public class DateTimeConsumer : IConsumer
{
#region 属性
///
/// 【时间】
///
/// 摘要:
/// 获取/设置触发器更新操作完成后的时间值(需要被更新的时间类型(DateTime)的实例)。
///
///
public static DateTime DateTime { get; set; }
#endregion
#region 方法
/// name="eventMessage">时间类型(DateTime)的实例。
///
/// 【异步触发句柄】
///
/// 摘要:
/// 如果时间类型(DateTime)的实例发生改变,需要对定义在Linkage.Controllers.PrincipleController.DateTimeConsumer命名空间的时间类型(DateTime)的实例,进行更新,
/// 那么触发器(EventPublisher)就会调用当前类中的“HandleEventAsync”方法,来对定义在Linkage.Controllers.PrincipleController.DateTimeConsumer命名空间的时间类型(DateTime)的实例,进行更新。
///
///
public Task HandleEventAsync(DateTime eventMessage)
{
//把触发器更新操作完成后的时间值,存储到当前类的静态属性成员(需要被更新的时间类型(DateTime)的实例)中。
DateTime = eventMessage;
return Task.CompletedTask;
}
#endregion
}
}
}
4 触发器接口(IEventPublisher)及其具体实现类(EventPublisher)的定义实现
4.1 IEventPublisher
namespace Linkage.Events
{
///
/// 【触发器--接口】
///
/// 摘要:
/// 如果1指类型(“T”)的实例发生改变,需要对定义在任意命名空间同1类型(“T”)的实例,进行更新,
/// 继承该接口的具体实现类中的同名方法就会通过所有的(继承于Consumer
/// 说明:
/// nopCommerce开发者的定义该方法的主要意图是:在有数据的增/删/修操作发生时,通过该方法,来对缓存在Redis数据库中的数据进行即时的更新,
/// 从而使页面始在渲染显示时,始终能够以最新的缓存数据,呈现给其他的用户。
///
public partial interface IEventPublisher
{
#region 方法
///
/// name="@event">IConsumer
///
/// 【异步触发】
///
/// 摘要:
/// 如果1指类型(“TEvent”=“T”)的实例发生改变,需要对定义在任意命名空间同1类型(“TEvent”=“T”)的实例,进行更新,
/// 该方法就会通过所有的(继承于IConsumer
/// 说明:
/// nopCommerce开发者的定义该方法的主要意图是:在有数据的增/删/修操作发生时,通过该方法,来对缓存在Redis数据库中的数据进行即时的更新,
/// 从而使页面始在渲染显示时,始终能够以最新的缓存数据,呈现给其他的用户。
///
///
Task PublishAsync<TEvent>(TEvent @event);
#endregion
}
}
4.2 EventPublisher
using Linkage.Extensions;
namespace Linkage.Events
{
///
/// 【触发器--类】
///
/// 摘要:
/// 如果1指类型(“T”)的实例发生改变,需要对定义在任意命名空间同1类型(“T”)的实例,进行更新,
/// 继承该具体实现类中的方法就会通过所有的(继承于Consumer
/// 说明:
/// nopCommerce开发者的定义该方法的主要意图是:在有数据的增/删/修操作发生时,通过该方法,来对缓存在Redis数据库中的数据进行即时的更新,
/// 从而使页面始在渲染显示时,始终能够以最新的缓存数据,呈现给其他的用户。
///
public partial class EventPublisher : IEventPublisher
{
#region 方法
///
/// name="@event">Consumer
///
/// 【异步触发】
///
/// 摘要:
/// 如果1指类型(“TEvent”=“T”)的实例发生改变,需要对定义在任意命名空间同1类型(“TEvent”=“T”)的实例,进行更新,
/// 该方法就会通过所有的(继承于IConsumer
/// 说明:
/// nopCommerce开发者的定义该方法的主要意图是:在有数据的增/删/修操作发生时,通过该方法,来对缓存在Redis数据库中的数据进行即时的更新,
/// 从而使页面始在渲染显示时,始终能够以最新的缓存数据,呈现给其他的用户。
///
/// s>
public virtual async Task PublishAsync<TEvent>(TEvent @event)
{
//从依赖注入到.Net(Core)6框架内置容器中,获取所有继承于IConsumer
List
//对所有具体实现类的实例,依次执行“HandleEventAsync”方法,从而实现对定义在任意命名空间的同1类型(“TEvent”=“T”)的实例,进行更新操作
foreach (var consumer in _eventConsumerList)
{
try
{
//对1指定具体实现类中的同1类型(“TEvent”=“T”)的实例,进行更新操作。
await consumer.HandleEventAsync(@event);
}
catch (Exception exception)
{
Console.WriteLine(exception.Message);
}
}
}
#endregion
}
}
5 Program.cs
//把继承于“IConsumer<>”泛型接口的所有具体实现类的实例,依赖注入到.Net(Core)6框架内置容器中。
//获取所有继承于“IConsumer<>”泛型接口的所有具体实现类的类型实例。
var _consumerList = AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(a => a.GetTypes().Where(t => typeof(IConsumer<>).DoesTypeImplementOpenGeneric(t)))
.ToList();
//通过所有具体实现类的类型实例,依次实例化所有具体实现类,并把这些实例,依赖注入到.Net(Core)6框架内置容器中。
foreach (var consumer in _consumerList)
{
var interfaces = consumer.FindInterfaces((type, criteria) => type.IsGenericType && ((Type)criteria).IsAssignableFrom(type.GetGenericTypeDefinition()), typeof(IConsumer<>));
foreach (var findInterface in interfaces)
{
builder.Services.AddTransient(findInterface, consumer);
}
}
//把“ EventPublisher”类的实例,依赖注入到.Net(Core)6框架内置容器中。
builder.Services.AddSingleton
//通过AddRazorRuntimeCompilation依赖注入中间件实现页面修改热加载(Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation)。
builder.Services
.AddControllersWithViews()
.AddRazorRuntimeCompilation();
//注意:从依赖注入到.Net(Core)6框架内置容器中,获取“ServiceProvider”实例,必须定义在最后,否则“ServiceProvider.GetServices”方法将不能获取类的实例,
//因为类的实例还没有依赖注入到.Net(Core)6框架内置容器中。
ServiceProviderExtension.ServiceProvider = builder.Services.BuildServiceProvider();
6 Principle\Index.cshtml
@{
ViewData["Title"] = "注销器实现原理与触发";
}
@{
<h1 class="text-success">当前时间(TestController):@Linkage.Controllers.PrincipleController._dateTimeh1>
<h1 class="text-success">当前时间(Linkage.Models):@Linkage.Controllers.PrincipleController._dateTimeh1>
if (Linkage.Controllers.PrincipleController.DateTimeConsumer.DateTime != Linkage.Controllers.PrincipleController._dateTime
&& Linkage.Models.DateTimeConsumerModel.DateTime != Linkage.Controllers.PrincipleController._dateTime)
{
<hr/>
<h1 class="text-warning">当前时间(TestController):@Linkage.Controllers.PrincipleController.DateTimeConsumer.DateTimeh1>
<h1 class="text-warning">当前时间(Linkage.Models):@Linkage.Models.DateTimeConsumerModel.DateTimeh1>
}
}
<hr />
@{
if (Linkage.Controllers.PrincipleController.DateTimeConsumer.DateTime == Linkage.Controllers.PrincipleController._dateTime
&& Linkage.Models.DateTimeConsumerModel.DateTime == Linkage.Controllers.PrincipleController._dateTime)
{
<h1 class="text-success">更新时间:@Linkage.Controllers.PrincipleController._dateTimeh1>
}
else
{
<h1 class="text-danger">更新时间(TestController):@Linkage.Controllers.PrincipleController.DateTimeConsumer.DateTimeh1>
<h1 class="text-danger">更新时间(Linkage.Models):@Linkage.Models.DateTimeConsumerModel.DateTimeh1>
}
}
<hr />
<a href="/Principle/Upate" class="btn btn-primary">更 新a>
对以上功能更为具体实现和注释见:221027_15Linkage(注销器实现原理与触发)。