gpt4 book ai didi

c# - 具有程序依赖注入(inject)的 Asp.Net 核心规则引擎 - 未找到类型 'type' 的构造函数

转载 作者:行者123 更新时间:2023-12-04 10:50:15 28 4
gpt4 key购买 nike

我开发了一个名为 RulesChain 的规则引擎库,当规则不需要注入(inject)任何依赖项时,它可以完美运行。

该库的主要目标是简化基于规则设计模式和责任链模式在 .NET 环境中编写业务规则,使其像 ASP.Net Core 中间件一样工作。

当我需要注入(inject)任何依赖项时,我会收到此错误:

System.MissingMethodException: 'Constructor on type 'AspNetCoreRulesChainSample.Rules.ShoppingCartRules.IsValidCupomRule' not found.'



问题是什么?

我的抽象 Rule 类需要接收要在其构造函数上调用的下一个规则。但是我不能在构造函数上添加一个特定的规则,因为链是在 RuleChain 上解析的。类(class)

这个怎么运作?

基本上所有规则都有 ShouldRun定义是否应该调用 run 方法的方法 Run应用业务规则的方法。和 Invoke当需要调用下一个规则时,在规则内部调用的方法。

这是引发错误的依赖注入(inject)规则:
public class IsValidCupomRule : Rule<ApplyDiscountContext>
{
private ISalesRepository _salesRepository;

public IsValidCupomRule(Rule<ApplyDiscountContext> next, ISalesRepository salesRepository) : base(next)
{
_salesRepository = salesRepository;
}

public override ApplyDiscountContext Run(ApplyDiscountContext context)
{
// Gets 7% of discount;
var myDiscount = context.Context.Items.Sum(i => i.Price * 0.07M);
context = _next.Invoke(context) ?? context;

// Only apply first order disccount if the discount applied by the other rules are smaller than this
if (myDiscount > context.DiscountApplied)
{
context.DiscountApplied = myDiscount;
context.DiscountTypeApplied = "Cupom";
}

return context;
}

public override bool ShouldRun(ApplyDiscountContext context)
{
return !string.IsNullOrWhiteSpace(context.Context.CupomCode)
&& context.Context.Items?.Count > 1
&& _salesRepository.IsCupomAvaliable(context.Context.CupomCode);
}
}

没有依赖的基本规则就是这样。
public class BirthdayDiscountRule : Rule<ApplyDiscountContext>
{
public BirthdayDiscountRule(Rule<ApplyDiscountContext> next) : base(next)
{ }

public override ApplyDiscountContext Run(ApplyDiscountContext context)
{
// Gets 10% of discount;
var birthDayDiscount = context.Context.Items.Sum(i => i.Price * 0.1M);
context = _next.Invoke(context);

// Only apply birthday disccount if the discount applied by the other rules are smaller than this
if (birthDayDiscount > context.DiscountApplied)
{
context.DiscountApplied = birthDayDiscount;
context.DiscountTypeApplied = "Birthday Discount";
}

return context;
}

public override bool ShouldRun(ApplyDiscountContext context)
{
var dayAndMonth = context.ClientBirthday.ToString("ddMM");
var todayDayAndMonth = DateTime.Now.ToString("ddMM");
return dayAndMonth == todayDayAndMonth;
}
}

抽象的规则是:
public abstract class Rule<T> : IRule<T>
{
protected readonly Rule<T> _next;

protected Rule(Rule<T> next)
{
_next = next;
}

/// <summary>
/// Valides if the rules should be executed or not
/// </summary>
/// <returns></returns>
public abstract bool ShouldRun(T context);

/// <summary>
/// Executes the rule
/// </summary>
/// <returns></returns>
public abstract T Run(T context);

public virtual T Invoke(T context)
{
if(ShouldRun(context))
return Run(context);
else
return _next != null
? _next.Invoke(context)
: context;
}
}

要创建我的规则链,我只需要这样做:
    public ShoppingCart ApplyDiscountOnShoppingCart(ShoppingCart shoppingCart)
{
// Create the chain
var shoppingCartRuleChain = new RuleChain<ApplyDiscountContext>()
.Use<IsValidCupomRule>()
.Use<BirthdayDiscountRule>()
.Use<FirstOrderDiscountRule>()
.Build();

// Create the chain context
var shoppingCartRuleContext = new ApplyDiscountContext(shoppingCart);
shoppingCartRuleContext.Properties["IsFirstOrder"] = true;
shoppingCartRuleContext.ClientBirthday = DateTime.UtcNow;

// Invoke the RulesChain
shoppingCartRuleContext = shoppingCartRuleChain.Invoke(shoppingCartRuleContext);

// Get data form the Chain result and return a ShoppingCart with new data.
shoppingCart.Discount = shoppingCartRuleContext.DiscountApplied;
shoppingCart.DiscountType = shoppingCartRuleContext.DiscountTypeApplied;
return shoppingCart;
}

我的主要观点是我可以在 .Use<IRule>() 中放置任何规则。调用,它允许 rules不依赖于彼此,并且可以更改链而无需重构每个规则。我在 Build() 上这样做方法。

这种方法只是颠倒链上每个规则的顺序,并为每个规则创建一个新实例,并添加最后一个 Rule实例作为下一个 Rule他新的 Rule .

这是 RuleChain 类
public class RuleChain<T> : IRuleChain<T>
{
private readonly IList<Type> _components = new List<Type>();

public IRuleChain<T> Use<TRule>()
{
_components.Add(typeof(TRule));
return this;
}

public IRule<T> Build()
{
IRule<T> app = EndOfChainRule<T>.EndOfChain();

foreach (var component in _components.Reverse())
{
app = (IRule<T>)Activator.CreateInstance(component,app);
}

return app;
}
}

这是我如何实例化新的 Rules下一个 Rule : app = (IRule<T>)Activator.CreateInstance(component,app);
其他可能有用的信息:

这就是我解决对 IoC 模块的依赖的方式
public static class Modules
{
public static void AddRepository(this IServiceCollection services)
{
services.AddScoped<ISalesRepository, SalesRepository>();
}

public static void AddRules(this IServiceCollection services)
{
services.AddScoped<IsValidCupomRule>();
services.AddScoped<FirstOrderDiscountRule>();
services.AddScoped<BirthdayDiscountRule>();
services.AddScoped<ShoppingCartRulesChain>();
}
}

我的 startup.cs 配置是这样的:
public void ConfigureServices(IServiceCollection services)
{
services.AddRepository();
services.AddRules();

services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});

services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}

我的问题是什么?

如何基于相同的 Rule<T> 实例化一个新类类和具有 IServiceCollection 的依赖项?

规则链源代码位于: https://github.com/lutticoelho/RulesChain
此示例源代码位于: https://github.com/lutticoelho/AspNetCoreRulesChainSample

如果有人需要有关该问题的更多信息或在该问题上添加更多代码,请随时在评论中提问,我将提供此问题所需的任何更改。

最佳答案

现在这里有很多东西要解压。第一个观察结果是 RuleChain类(class)。

如果意图是允许通过构造函数注入(inject)进行依赖注入(inject),则需要重构类的当前设计以实现这一点。

由于当前的设计是在 Asp.Net Core Middleware 管道后面建模的,我建议使用委托(delegate)来存储和处理所需的调用。

首先定义一个delegate处理规则处理

/// <summary>
/// A function that can process a <see cref="TContext"/> dependent rule.
/// </summary>
/// <typeparam name="TContext"></typeparam>
/// <param name="context"></param>
/// <returns>A task that represents the completion of rule processing</returns>
public delegate Task RuleHandlingDelegate<TContext>(TContext context);

使用委托(delegate)的优点是它可以在解决所有必要的依赖关系后延迟绑定(bind)到实际实现。

另请注意,此通用委托(delegate)定义使用 Task允许异步操作

这确实需要更改 IRuleChain<T>定义。
/// <summary>
/// Defines a class that provides the mechanisms to configure an application's rules pipeline execution.
/// </summary>
/// <typeparam name="TContext">The context shared by all rules in the chain</typeparam>
public interface IRuleChain<TContext> {

/// <summary>
/// Adds a rule to the application's request chain.
/// </summary>
/// <returns>The <see cref="IRuleChain{T}"/>.</returns>
IRuleChain<TContext> Use<TRule>();

/// <summary>
/// Builds the delegate used by this application to process rules executions.
/// </summary>
/// <returns>The rules handling delegate.</returns>
RuleHandlingDelegate<TContext> Build();
}

和实现。

为了允许将其他参数注入(inject)到规则实现中,链需要能够解析构造函数参数。
public abstract class RuleChain<TContext> : IRuleChain<TContext> {
private readonly Stack<Func<RuleHandlingDelegate<TContext>, RuleHandlingDelegate<TContext>>> components =
new Stack<Func<RuleHandlingDelegate<TContext>, RuleHandlingDelegate<TContext>>>();
private bool built = false;

public RuleHandlingDelegate<TContext> Build() {
if (built) throw new InvalidOperationException("Chain can only be built once");
var next = new RuleHandlingDelegate<TContext>(context => Task.CompletedTask);
while (components.Any()) {
var component = components.Pop();
next = component(next);
}
built = true;
return next;
}

public IRuleChain<TContext> Use<TRule>() {
components.Push(createDelegate<TRule>);
return this;
}

protected abstract object GetService(Type type, params object[] args);

private RuleHandlingDelegate<TContext> createDelegate<TRule>(RuleHandlingDelegate<TContext> next) {
var ruleType = typeof(TRule);
MethodInfo methodInfo = getValidInvokeMethodInfo(ruleType);
//Constructor parameters
object[] constructorArguments = new object[] { next };
object[] dependencies = getDependencies(ruleType, GetService);
if (dependencies.Any())
constructorArguments = constructorArguments.Concat(dependencies).ToArray();
//Create the rule instance using the constructor arguments (including dependencies)
object rule = GetService(ruleType, constructorArguments);
//return the delegate for the rule
return (RuleHandlingDelegate<TContext>)methodInfo
.CreateDelegate(typeof(RuleHandlingDelegate<TContext>), rule);
}

private MethodInfo getValidInvokeMethodInfo(Type type) {
//Must have public method named Invoke or InvokeAsync.
var methodInfo = type.GetMethod("Invoke") ?? type.GetMethod("InvokeAsync");
if (methodInfo == null)
throw new InvalidOperationException("Missing invoke method");
//This method must: Return a Task.
if (!typeof(Task).IsAssignableFrom(methodInfo.ReturnType))
throw new InvalidOperationException("invalid invoke return type");
//and accept a first parameter of type TContext.
ParameterInfo[] parameters = methodInfo.GetParameters();
if (parameters.Length != 1 || parameters[0].ParameterType != typeof(TContext))
throw new InvalidOperationException("invalid invoke parameter type");
return methodInfo;
}

private object[] getDependencies(Type middlewareType, Func<Type, object[], object> factory) {
var constructors = middlewareType.GetConstructors().Where(c => c.IsPublic).ToArray();
var constructor = constructors.Length == 1 ? constructors[0]
: constructors.OrderByDescending(c => c.GetParameters().Length).FirstOrDefault();
if (constructor != null) {
var ctorArgsTypes = constructor.GetParameters().Select(p => p.ParameterType).ToArray();
return ctorArgsTypes
.Skip(1) //Skipping first argument since it is suppose to be next delegate
.Select(parameter => factory(parameter, null)) //resolve other parameters
.ToArray();
}
return Array.Empty<object>();
}
}

有了这个抽象链,现在它的实现有责任定义如何解决任何依赖关系。

按照示例上下文,这很简单。由于使用了默认的 DI 扩展,那么链应该依赖于 IServiceProvider对于参数未知的类型和 Activator对于那些提供了构造函数参数的人。
public class DiscountRuleChain : RuleChain<ApplyDiscountContext> {
private readonly IServiceProvider services;

public DiscountRuleChain(IServiceProvider services) {
this.services = services;
}

protected override object GetService(Type type, params object[] args) =>
args == null || args.Length == 0
? services.GetService(type)
: Activator.CreateInstance(type, args);
}

到目前为止提供了以上所有内容,有一些更改可以实现更简洁的设计。

具体 IRule<TContext>及其默认实现。
public interface IRule<TContext> {
Task Invoke(TContext context);
}

public abstract class Rule<TContext> : IRule<TContext> {
protected readonly RuleHandlingDelegate<TContext> next;

protected Rule(RuleHandlingDelegate<TContext> next) {
this.next = next;
}

public abstract Task Invoke(TContext context);
}

现在可以抽象任何特定于上下文的规则以针对特定域

例如
public abstract class DiscountRule : Rule<ApplyDiscountContext> {
protected DiscountRule(RuleHandlingDelegate<ApplyDiscountContext> next) : base(next) {
}
}

这简化了示例中特定于折扣的实现,并允许注入(inject)依赖项

IsValidCupomRule
public class IsValidCupomRule : DiscountRule {
private readonly ISalesRepository _salesRepository;

public IsValidCupomRule(RuleHandlingDelegate<ApplyDiscountContext> next, ISalesRepository salesRepository)
: base(next) {
_salesRepository = salesRepository;
}

public override async Task Invoke(ApplyDiscountContext context) {
if (cupomAvailable(context)) {
// Gets 7% of discount;
var myDiscount = context.Context.Items.Sum(i => i.Price * 0.07M);

await next.Invoke(context);

// Only apply discount if the discount applied by the other rules are smaller than this
if (myDiscount > context.DiscountApplied) {
context.DiscountApplied = myDiscount;
context.DiscountTypeApplied = "Cupom";
}
} else
await next(context);
}

private bool cupomAvailable(ApplyDiscountContext context) {
return !string.IsNullOrWhiteSpace(context.Context.CupomCode)
&& context.Context.Items?.Count > 1
&& _salesRepository.IsCupomAvaliable(context.Context.CupomCode);
}
}

一阶折扣规则
public class FirstOrderDiscountRule : DiscountRule {
public FirstOrderDiscountRule(RuleHandlingDelegate<ApplyDiscountContext> next) : base(next) { }

public override async Task Invoke(ApplyDiscountContext context) {
if (shouldRun(context)) {
// Gets 5% of discount;
var myDiscount = context.Context.Items.Sum(i => i.Price * 0.05M);

await next.Invoke(context);

// Only apply discount if the discount applied by the other rules are smaller than this
if (myDiscount > context.DiscountApplied) {
context.DiscountApplied = myDiscount;
context.DiscountTypeApplied = "First Order Discount";
}
} else
await next.Invoke(context);
}

bool shouldRun(ApplyDiscountContext context) {
return (bool)(context.Properties["IsFirstOrder"] ?? false);
}
}

以下测试用于验证规则引擎的预期行为。
[TestClass]
public class RulesEngineTests {
[TestMethod]
public async Task Should_Apply_Cupom_Discount() {
//Arrange
var cupomCode = "cupomCode";
var services = new ServiceCollection()
.AddSingleton<ISalesRepository>(sp =>
Mock.Of<ISalesRepository>(_ => _.IsCupomAvaliable(cupomCode) == true)
)
.BuildServiceProvider();

// Create the chain
var shoppingCartRuleChain = new DiscountRuleChain(services)
.Use<IsValidCupomRule>()
.Use<FirstOrderDiscountRule>()
.Build();

ShoppingCart shoppingCart = new ShoppingCart {
CupomCode = cupomCode,
Items = new List<ShoppingCartItem> {
new ShoppingCartItem { Price = 10M },
new ShoppingCartItem { Price = 10M },
}
};
var expectedDiscountType = "Cupom";
var expectedDiscountApplied = 1.40M;

// Create the chain context
var shoppingCartRuleContext = new ApplyDiscountContext(shoppingCart);
shoppingCartRuleContext.Properties["IsFirstOrder"] = true;
shoppingCartRuleContext.ClientBirthday = DateTime.UtcNow;

//Act
await shoppingCartRuleChain.Invoke(shoppingCartRuleContext);

// Get data from the context result and verify new data.
shoppingCart.Discount = shoppingCartRuleContext.DiscountApplied;
shoppingCart.DiscountType = shoppingCartRuleContext.DiscountTypeApplied;

//Assert (using FluentAssertions)
shoppingCart.DiscountType.Should().Be(expectedDiscountType);
shoppingCart.Discount.Should().Be(expectedDiscountApplied);
}
}

请注意如何模拟要注入(inject)的依赖项以单独测试预期的行为。

关于c# - 具有程序依赖注入(inject)的 Asp.Net 核心规则引擎 - 未找到类型 'type' 的构造函数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59513882/

28 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com