新文章 网摘 文章 随笔 日记

如何在Orleans筒仓中实现DI(1.x版本,依赖注入)

本文介绍如何在Orleans Silo中实现DI支持。

介绍

从1.1.0版开始,奥尔良已支持ASP.NET vNext样式依赖项注入支持。然而,关于如何  在奥尔良筒仓中充分利用它的实际记载很少本文旨在提供一个完整的分步指南,从头到尾将DI支持集成到筒仓中。本文不是关于奥尔良本身的教程,也不是关于温莎城堡的教程。假定两者都有粗略的知识。另外,尽管本文着重于与Castle的集成,但是替换任何其他DI容器(例如AutoFac,StructureMap,Ninject等)应该是相当琐碎的。

免责声明

应该提到的是,我是一个完全的初学者,涉及奥尔良并仍在学习中。如果有人在这里发现任何明显的错误,请指出来,以便我修复。这很大程度上是我将DI系统连接到奥尔良的研究纪事。

使用代码

最初,我们将创建一个不带DI支持的标准三项目解决方案。一切就绪后,我们将更新项目以通过Castle Windsor支持DI。这有点漫长,但是我想尽可能地透彻,不要错过任何东西。

第1步-创建项目

我们要做的第一件事是创建我们的项目。第一个项目(ClientApp)将是Orleans开发/测试主机。第二个(谷物)将是奥尔良谷物类集合。最终的(Grains.Interfaces)将是Orleans Grain接口集合。一旦创建并引用了项目,它应该看起来像以下屏幕截图(注意:我从项目中删除了示例代码)。

图片1

图片2

图片3

第2步-将Orleans.Server添加到Grains项目

我们想在一个单独的过程中托管我们的筒仓。最简单的方法是将nuget包“ Microsoft.Orleans.Server”添加到Grains项目中。这包括OrleansHost.exe,可以轻松为我们的谷物旋转筒仓。最简单的方法是通过powershell添加软件包:

Install-Package Microsoft.Orleans.Server -ProjectName Grains

步骤3-配置客户端和主机

我们需要添加两个配置文件。客户端配置(ClientConfiguration.xml)到ClientApp项目和服务器配置(OrleansConfiguration.xml)与纹理项目。两者都应设置为内容/副本(如果较新)。

该OrleansConfiguration.xml(谷物)应该是这样的:

 
<?xml version="1.0" encoding="utf-8"?>
<OrleansConfiguration xmlns="urn:orleans">
  <Globals>
    <SeedNode Address="localhost" Port="10000" />
  </Globals>
  <Defaults>
    <Networking Address="localhost" Port="10000" />
    <ProxyingGateway Address="localhost" Port="30000" />
  </Defaults>
</OrleansConfiguration>

 

 

ClientConfiguration.xml(ClientApp)应该如下所示:

<?xml version="1.0" encoding="utf-8" ?>
<ClientConfiguration xmlns="urn:orleans">
  <Gateway Address="localhost" Port="30000" />
</ClientConfiguration>

 

步骤4-配置Visual Studio以同时启动两个项目(可选)

这是一个可选步骤。如果跳过此步骤,则需要在启动ClientApp项目之前手动启动OrleansHost.exe。

在配置Visual Studio解决方案,推出多个项目本身是平凡的,因为我们的谷物的项目是不是一个可执行我们需要告诉Visual Studio的实际运行什么。最简单的方法是转到项目属性>调试选项卡>启动外部程序。但是,执行此路由时,必须在文本框中输入可执行文件的完整路径。如果将解决方案复制到另一个文件夹(或者如果有人将其下载到他们的计算机上),则可能导致问题。一种更可靠的解决方案是在设置OrleansHost.exe文件的路径时使用$(SolutionDir)宏。不幸的是,由于UI不支持宏,因此无法在Visual Studio本身中完成此操作。为此,我们需要修改csproj文件本身。打开Grains.csproj并修改< PropertyGroup Condition =“'$(Configuration)| $(Platform)'=='Debug | AnyCPU'”>包含此信息的元素(请参见下文:突出显示)。项目加载时,Visual Studio会自动为您扩展此选项。

 <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
    <DebugSymbols>true</DebugSymbols>
    <DebugType>full</DebugType>
    <Optimize>false</Optimize>
    <OutputPath>bin\Debug\</OutputPath>
    <DefineConstants>DEBUG;TRACE</DefineConstants>
    <ErrorReport>prompt</ErrorReport>
    <WarningLevel>4</WarningLevel>
    <Prefer32Bit>false</Prefer32Bit>
    <StartAction>Program</StartAction>
    <StartProgram>$(SolutionDir)Grains\bin\Debug\OrleansHost.exe</StartProgram>
    <StartWorkingDirectory>
    </StartWorkingDirectory>
  </PropertyGroup>

 

完成此操作并重新加载项目后,我们只需右键单击“解决方案”>“属性”>“启动项目”,然后选择“多个启动项目”(请参见下文)

图片4

步骤5-编写样板代码以启动ClientApp

现在(大部分)项目就位了,让我们返回ClientApp中的Program.cs并对其进行修改,以便它可以连接到我们的Grain筒仓。删除所有代码,并将其替换为以下代码:

using System;
using Orleans;

namespace ClientApp
{
    public class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Press any key once the silo has started.");
            Console.ReadKey(intercept: true);
            GrainClient.Initialize("ClientConfiguration.xml");

            // TODO: our grains will go here later.

            Console.WriteLine("Press any key to exit.");
            Console.ReadKey(intercept: true);
        }
    }
}

 

让我们一步一步地进行一下。

Console.WriteLine("Press any key once the silo has started.");
Console.ReadKey(intercept: true);

 

由于启动ClientApp时OrleansHost可能未完全加载,因此我们希望在应用程序中引入一个暂停,以使其有机会启动。如果要尝试同时启动OrleansHost和ClientApp,则ClientApp可能会崩溃,因为在尝试连接主机时主机将无法存活(即两个进程之间的竞争状态)。

 
GrainClient.Initialize("ClientConfiguration.xml");
 

这应该是不言自明的。本质上,我们使用前面编写的ClientConfiguration.xml配置客户端。

其余的我们稍后再填写。

此时,如果您运行解决方案,则OrleansHost和ClientApp均应成功加载。他们实际上不会做任何事情。让我们为此做些事情!

第6步-为我们的筒仓创建一些谷物

现在,我们已经创建了项目,将其连接起来并进行了实际的交谈,现在让我们创建一个颗粒,以便我们可以做一些有意义的工作。在此示例中,我们将转到经常使用的计算器示例。请记住,第一轮将完全不使用DI。一旦到位,我们将重构项目以支持DI。

让我们从ICalculatorGrain开始。这在Grains.Interfaces中使用,应如下所示:

using System.Threading.Tasks;
using Orleans;

namespace Grains.Interfaces
{
    public interface ICalculatorGrain : IGrainWithGuidKey
    {
        Task<int> Add(int a, int b);
        Task<int> Sub(int a, int b);
        Task<int> Mul(int a, int b);
        Task<int> Div(int a, int b);
    }
}

 

谷物中同样无聊的CalculatorGrain应该看起来像这样:

using System.Threading.Tasks;
using Grains.Interfaces;
using Orleans;

namespace Grains
{
    public class CalculatorGrain : Grain, ICalculatorGrain
    {
        public Task<int> Add(int a, int b) => Task.FromResult(a + b);
        public Task<int> Sub(int a, int b) => Task.FromResult(a - b);
        public Task<int> Mul(int a, int b) => Task.FromResult(a * b);
        public Task<int> Div(int a, int b) => Task.FromResult(a / b);
    }
}

 

最后,让我们返回ClientApp中的Main()方法,并用行使此功能的少量示例代码替换// TODO部分。

var grain = GrainClient.GrainFactory.GetGrain<ICalculatorGrain>(Guid.NewGuid());

Console.WriteLine($"1 + 2 = {grain.Add(1, 2).Result}");
Console.WriteLine($"2 - 3 = {grain.Sub(2, 3).Result}");
Console.WriteLine($"3 * 4 = {grain.Mul(3, 4).Result}");
Console.WriteLine($"4 / 2 = {grain.Div(4, 2).Result}");

 

如果现在运行解决方案,我们将获得以下输出:

图片5

一切正常。太棒了!现在如何支持DI?

步骤7-将DI引入Grains项目

在继续之前,我们需要为Grains项目添加两个nuget引用。第一个是温莎城堡(或您喜欢的任何一个DI容器)。第二个是Microsoft的Extension Dependeny Injection框架。在powershell中执行以下命令:

Install-Package Castle.Windsor -ProjectName Grains
Install-Package Microsoft.Extensions.DependencyInjection -ProjectName Grains -Pre

注意Microsoft.Extensions.DependencyInjection的-Pre参数。在撰写本文时,该项目仍被视为预发布。

第8步-快速绕开Orleans DI的工作方式

在继续之前,最好先解释奥尔良在创建谷物时在幕后所做的事情。当奥尔良最初使粮食成为现实时,它使用IServiceProvider接口实际实例化该对象。这是一个非常简单的接口,只有一个方法:

object GetService(Type serviceType);

 

默认情况下(至少从1.2.0开始):Orleans使用的默认服务提供程序仅使用Activator类实例化对象。例如:

public class DefaultServiceProvider : IServiceProvider
{
    public object GetService(Type serviceType)
    {
        return Activator.CreateInstance(serviceType);
    }
}

 

仅使用此信息,应该很清楚,这里的第一步是创建一个IServiceProvider实现,该实现通过容器进行解析。让我们现在开始:

using System;
using Castle.MicroKernel;

namespace Grains
{
    public class CastleServiceProvider : IServiceProvider
    {
        private readonly IKernel kernel;

        public CastleServiceProvider(IKernel kernel)
        {
            this.kernel = kernel;
        }

        public object GetService(Type serviceType) => this.kernel.Resolve(serviceType);
    }
}

 

这很容易-但是我们实际上将其粘贴在哪里?深入研究奥尔良源代码,您会注意到Orleans.DependencyInjection项目中的类ConfigureStartupBuilder在不深入研究文件的情况下,它实际上是通过一个参数来启动的:startupTypeName(稍后会对此进行更多介绍)。启动生成器本质上将询问该类型,并在其中寻找一个称为“ ConfigureServices”的方法,该方法采用IServiceCollection类型的参数并返回IServiceProvider类型。如果您研究代码,则默认情况下,如果未配置startupTypeName,则它将仅返回前面提到的DefaultServiceProvider的实例。

使用此信息,我们可以创建一个实现此方法的类:

using System;
using Castle.MicroKernel.Registration;
using Castle.Windsor;
using Grains.Interfaces;
using Microsoft.Extensions.DependencyInjection;
using Orleans;

namespace Grains
{
    public class Startup
    {
        public IServiceProvider ConfigureServices(IServiceCollection services)
        {
            var container = new WindsorContainer();

            // Scan for all of the grains in this assembly.
            container.Register(Classes.FromThisAssembly().BasedOn<Grain>().LifestyleTransient());

            // TODO: Register our custom providers here.

            foreach (var service in services)
            {
                switch (service.Lifetime)
                {
                    case ServiceLifetime.Singleton:
                        container.Register(
                            Component
                                .For(service.ServiceType)
                                .ImplementedBy(service.ImplementationType)
                                .LifestyleSingleton());
                        break;
                    case ServiceLifetime.Transient:
                        container.Register(
                            Component
                                .For(service.ServiceType)
                                .ImplementedBy(service.ImplementationType)
                                .LifestyleTransient());
                        break;
                    case ServiceLifetime.Scoped:
                        var error = $"Scoped lifestyle not supported for '{service.ServiceType.Name}'.";
                        throw new InvalidOperationException(error);
                }
            }

            return new CastleServiceProvider(container.Kernel);
        }
    }
}

 

让我们逐行进行以下操作:

我们需要做的第一件事是创建我们的容器,然后针对此注册我们所有的谷物实现。Classes.FromThisAssembly()是在Castle Windsor中按质量注册类型的便捷方法。

var container = new WindsorContainer();

// Scan for all of the grains in this assembly.
container.Register(Classes.FromThisAssembly().BasedOn<Grain>().LifestyleTransient());

 

当Orleans调用我们的ConfigureServices()方法时,它将传入要自动注册的类型映射列表,因此我们需要将它们注册到容器中。我在'Scoped'块中放置了一个异常,因为我不确定如何处理(仍在学习中)。同样,在撰写本文时,奥尔良还没有以这种方式注册任何东西。实际上,它注册的所有内容都是瞬态的。

foreach (var service in services)
{
    switch (service.Lifetime)
    {
        case ServiceLifetime.Singleton:
            container.Register(
                Component
                    .For(service.ServiceType)
                    .ImplementedBy(service.ImplementationType)
                    .LifestyleSingleton());
            break;
        case ServiceLifetime.Transient:
            container.Register(
                Component
                    .For(service.ServiceType)
                    .ImplementedBy(service.ImplementationType)
                    .LifestyleTransient());
            break;
        case ServiceLifetime.Scoped:
            throw new InvalidOperationException($"Scoped lifestyle not supported '{service.ServiceType.Name}'.");
    }
}

 

最后,我们将容器的内核传递给CastleServiceProvider类的构造函数,然后将其返回。现在,每当奥尔良尝试创建某项内容时,它将使用我们的提供程序。

return new CastleServiceProvider(container.Kernel);

 

现在,那个startupTypeName参数呢?去哪儿了?在OrleansConfiguration.xml中!

让我们打开OrleansConfiguration.xml并将其添加到<Defaults />部分:

<?xml version="1.0" encoding="utf-8"?>
<OrleansConfiguration xmlns="urn:orleans">
  <Globals>
    <SeedNode Address="localhost" Port="10000" />
  </Globals>
  <Defaults>
    <Startup Type="Grains.Startup, Grains" />
    <Networking Address="localhost" Port="10000" />
    <ProxyingGateway Address="localhost" Port="30000" />
  </Defaults>
</OrleansConfiguration>

 

注意:您可能会在这里注意到“智能提示”警告,即“启动”不是有效的元素类型。忽略它。它不是OrleansConfiguration.xsd文件的一部分,但是Orleans本身可以识别它。

此时,您可以再次运行该解决方案。该行为应与以前相同。如果您好奇,请在Startup类中设置一个断点,以确认实际上是由奥尔良调用来创建您的谷物。

步骤9-向我们的谷物添加依赖关系(最后)

回顾一下:我们的解决方案中有三个项目,一个客户端应用程序,一个界面项目以及一个运行我们筒仓的谷歌实现。我们已经创建了一个简单的计算器纹理,并且已经将Castle Windsor连接到所有内容中,因此现在我们可以在实例化纹理(或属性注入时利用ctor注入本教程的最后一部分是熟悉DI的任何人的标准票价。

首先,让我们在Grains.Interfaces项目中为我们的“新”计算器提供者创建一个接口:

namespace Grains.Interfaces
{
    public interface ICalculatorProvider
    {
        int Add(int a, int b);
        int Sub(int a, int b);
        int Mul(int a, int b);
        int Div(int a, int b);
    }
}

 

其次,我们将在Grains项目中创建它的实现:

using Grains.Interfaces;

namespace Grains
{
    public class CalculatorProvider : ICalculatorProvider
    {
        public int Add(int a, int b) => a + b;
        public int Div(int a, int b) => a / b;
        public int Mul(int a, int b) => a * b;
        public int Sub(int a, int b) => a - b;
    }
}

 

第三,让我们重新编写CalculatorGrain,以使其将ICalculatorProvider作为依赖项并改为使用它:

using System.Threading.Tasks;
using Grains.Interfaces;
using Orleans;

namespace Grains
{
    public class CalculatorGrain : Grain, ICalculatorGrain
    {
        private readonly ICalculatorProvider calc;

        public CalculatorGrain(ICalculatorProvider calc)
        {
            this.calc = calc;
        }

        public Task<int> Add(int a, int b) => Task.FromResult(calc.Add(a, b));
        public Task<int> Div(int a, int b) => Task.FromResult(calc.Div(a, b));
        public Task<int> Mul(int a, int b) => Task.FromResult(calc.Mul(a, b));
        public Task<int> Sub(int a, int b) => Task.FromResult(calc.Sub(a, b));
    }
}

 

最后,我们需要在容器中注册我们的提供者。在普通的应用程序中,我们可能会使用IWindsorInstaller来执行此操作,但是为了简单起见,我们将只替换Startup.ConfigureServices()方法的// TODO部分来手动执行此操作:

container.Register(Component.For<ICalculatorProvider>().ImplementedBy<CalculatorProvider>().LifestyleTransient());

 

而且...就是这样!继续并运行解决方案,以确保其仍然有效。

请享用!

 

https://www.codeproject.com/Articles/1099750/How-to-implement-DI-support-in-your-Orleans-Silo

posted @ 2020-02-13 16:03  岭南春  阅读(170)  评论(0)    收藏  举报