Try   HackMD
tags: Akka Web DotNet Core

從零到有Akka整合Dotnet Core Web建置程控系統-啟用Akka系統

此文件會手把手教學,該如何在一個Web Server上啟用Akka系統,算偏向建置SOP,故不會在框架上多作解釋。閱讀時須對Dot Net Core與Akka.Net需有一定的了解程度。

此篇文章會以在Web Server內新增一個與外部PLC溝通的範例去實作。

新增專案

在Visual Studio 2019新增Asp Dot Net Core Web應用程式 => 選擇Web MVC應用程式(進階設定先不作設置,可以先取消勾選)

Step1:Log-新增LogSend類別庫

Log工具實作,系統建置前先設計Akka Log實體Class,在此我們宣告一個ILog介面可抽換Log實際紀錄的方法。目前此例Log實作只對Akka Log做設計。

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

實作檔案

  • ILog Class : Log介面
  • NLogSend Clsass : 紀錄Akka System Log使用

需安裝Package

  • 安裝 Akka.Logger.Nlog (Ver:1.3.3)

實作

  • 實作ILog Class
​ public interface ILog ​ { ​ void E(string title,string content); ​ void A(string title,string contetnt); ​ void I(string title,string content); ​ void D(string title,string content); ​ }
  • 實作NLogSend Class
​ public class NLogSend : ILog ​ { ​ private ILoggingAdapter _nlog; ​ public NLogSend(ILoggingAdapter nlog = null) ​ { ​ _nlog = nlog; ​ } ​ public void A(string title, string contetnt) ​ { ​ _nlog.Warning("【" + title + "】" + ":" + contetnt); ​ } ​ public void D(string title, string content) ​ { ​ _nlog.Debug("【" + title + "】" + ":" + content); ​ } ​ public void E(string title, string content) ​ { ​ _nlog.Error("【" + title + "】" + ":" + content); ​ } ​ public void I(string title, string content) ​ { ​ _nlog.Info("【" + title + "】" + ":" + content); ​ } ​ }

Step2 Log-在 Web Server 根目錄下新增NLog Config

在LogSender撰寫完後,接下來在Web Server下新建NLog Config檔案。此檔為Web Server裡Akka系統運作記錄Log的相關設定規則設置。

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

  • 新增NLog.config至Web Server專案根目錄下
​<?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" ​ autoReload="true" ​ throwConfigExceptions="true" ​ internalLogToConsole="true" ​ internalLogLevel="info"> ​ <targets async="true" > ​ ​ ​ </targets> ​ <rules> ​ ​ </rules> </nlog>
  • 注意:記得檔案複製到輸出目錄需選擇->有更新才複製選項
    Image Not Showing Possible Reasons
    • The image file may be corrupted
    • The server hosting the image is unavailable
    • The image path is incorrect
    • The image format is not supported
    Learn More →

Step3 AkkaSystem-新增AkkaSysBase類別庫

建至Akka基礎使用Base Class,這邊我們先跟據Akka Life Cycle紀錄Actor的生命週期去設計BaseActor。再根據Akka Socket功能額外設置BaseClient與BaseServer Actor。最後在設計一些基礎設置Model與Akka管理功能。

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

實作檔案

  • Base資料夾
    • BaseActor Class : 紀錄Actor LifeCycle
    • BaseClientActor Class : Socket Client Actor
    • BaseServerActor Class : Socket Server Actor
  • AkkaPara Class : Akka Config參數設置
  • AkkaSocketIP Class : Acotr Socket IP參數
  • ISysAkkaManger Class : Create Actor方法
  • SysAkkaManager Class : Create Actor方法實作

需安裝Package

  • 安裝Akka (Ver 1.3.14)
  • 安裝Akka.ID.Extensions.DependencyInjection (Ver 1.3.2)
  • 安裝Akka.Remote(Ver 1.3.14)

實作

  • 實作AkkaSocketIP Class
​​ public class AkkaSocketIP ​​ { ​​ public IPEndPoint LocalIpEndPoint { get; set; } ​​ public IPEndPoint RemoteIpEndPoint { get; set; } ​​ }
  • 實作BaseActor Class
​​ public class BaseActor : ReceiveActor ​​ { ​​ protected ILog _log; ​​ private string _actorName; ​​ public BaseActor(ILog log) ​​ { ​​ _log = log; ​​ _actorName = Context.Self.Path.Name; ​​ } ​​ ​​ protected override void PreStart() ​​ { ​​ _log.I("AThread生命週期", _actorName + "PreStart"); ​​ base.PreStart(); ​​ } ​​ protected override void PreRestart(Exception reason, object message) ​​ { ​​ _log.E("AThread生命週期", _actorName + " PreRestart"); ​​ _log.E("AThread生命週期", "Reason:" + reason.Message); ​​ base.PreRestart(reason, message); ​​ } ​​ protected override void PostStop() ​​ { ​​ _log.I("AThread生命週期", _actorName + " PostStop"); ​​ base.PostStop(); ​​ } ​​ protected override void PostRestart(Exception reason) ​​ { ​​ _log.E("AThread生命週期", _actorName + " PostRestart"); ​​ _log.E("AThread生命週期", "Reason:" + reason.Message); ​​ base.PostRestart(reason); ​​ } ​​ }
  • 實作BaseClientActor Class
​​public IActorRef SndActor; ​​ protected IActorRef _connection; ​​ protected string _actorName; ​​ private readonly AkkaSocketIP _AkkaSocketIP; ​​ public BaseClientActor(AkkaSocketIP socketIP, ILog log) : base(log) ​​ { ​​ _AkkaSocketIP = socketIP; ​​ _actorName = Context.Self.Path.Name; ​​ Connect(); ​​ Receive<Tcp.Connected>(message => TCPConnected(message)); ​​ Receive<Tcp.ConnectionClosed>(message => TcpConnectionClosed(message)); ​​ Receive<Tcp.CommandFailed>(message => TcpCommandFailed(message)); ​​ Receive<Tcp.Received>(message => TcpReceivedData(message)); ​​ } ​​ protected virtual void TcpReceivedData(Tcp.Received msg) ​​ { ​​ _log.I("TCP接收資料", "Handle_Tcp_Received. message=" + msg.ToString()); ​​ _log.I("TCP接收資料", "ByteString=" + msg.Data.ToString()); ​​ _log.I("TCP接收資料", "Count=" + msg.Data.Count.ToString()); ​​ } ​​ protected virtual void TCPConnected(Tcp.Connected message) ​​ { ​​ _log.I("TCP連線成功", " Connected. message=" + message.ToString()); ​​ _log.I("TCP連線成功", " LocalAddress=" + message.LocalAddress.ToString()); ​​ _log.I("TCP連線成功", " RemoteAddress=" + message.RemoteAddress.ToString()); ​​ _connection = Sender; ​​ _connection.Tell(new Tcp.Register(Self)); ​​ } ​​ protected virtual void TcpConnectionClosed(Tcp.ConnectionClosed message) ​​ { ​​ _log.I("TCP連線關閉", " Tcp.ConnectionClosed. message=" + message.ToString()); ​​ _log.I("TCP連線關閉", " Message.Cause=" + message.Cause); ​​ _log.I("TCP連線關閉", " Message.IsAborted=" + message.IsAborted.ToString()); ​​ _log.I("TCP連線關閉", " Message.IsConfirmed=" + message.IsConfirmed.ToString()); ​​ _log.I("TCP連線關閉", " Message.IsErrorClosed=" + message.IsErrorClosed.ToString()); ​​ _log.I("TCP連線關閉", " Message.IsPeerClosed=" + message.IsPeerClosed.ToString()); ​​ Connect(); ​​ } ​​ protected virtual void TcpCommandFailed(Tcp.CommandFailed message) ​​ { ​​ _log.E("TCP操作失敗", " Tcp.CommandFailed. message=" + message.ToString()); ​​ _log.E("TCP操作失敗", " Tcp.CommandFailed. message=" + message.ToString()); ​​ _log.E("TCP操作失敗", " Cmd=" + message.Cmd.ToString()); ​​ _log.E("TCP操作失敗", " Message=" + message.Cmd.FailureMessage); ​​ Connect(); ​​ } ​​ /// <summary> ​​ /// 連線 ​​ /// </summary> ​​ protected void Connect() ​​ { ​​ Context.System.Tcp().Tell(new Tcp.Connect(_AkkaSocketIP.RemoteIpEndPoint)); ​​ }
  • 實作BaseServerActor Class
​​​ public class BaseServerActor : BaseActor ​​​ { ​​​ public BaseServerActor(AkkaSocketIP akkaSysIp, ILog log) : base(log) ​​​ { ​​​ Context.System.Tcp().Tell(new Tcp.Bind(Self, akkaSysIp.LocalIpEndPoint)); ​​​ Receive<Tcp.Bound>(message => TcpBound(message)); ​​​ Receive<Tcp.Connected>(message => TCPConnected(message)); ​​​ Receive<Tcp.CommandFailed>(msg => TCPCommandFail(msg)); ​​​ Receive<Tcp.ConnectionClosed>(message => TcpConnectionClosed(message)); ​​​ Receive<Tcp.Received>(message => TcpReceivedData(message)); ​​​ } ​​​ /// <summary> ​​​ /// Tcp監聽事件觸發 ​​​ /// </summary> ​​​ private void TcpBound(Tcp.Bound msg) ​​​ { ​​​ _log.I("TCP監聽", "Tcp.Bound Success. Listening on " + msg.LocalAddress); ​​​ } ​​​ protected virtual void TcpReceivedData(Tcp.Received msg) ​​​ { ​​​ _log.I("TCP接收資料", "Handle_Tcp_Received. message=" + msg.ToString()); ​​​ _log.I("TCP接收資料", "ByteString=" + msg.Data.ToString()); ​​​ _log.I("TCP接收資料", "Count=" + msg.Data.Count.ToString()); ​​​ } ​​​ protected virtual void TCPConnected(Tcp.Connected msg) ​​​ { ​​​ _log.I("TCP已被連線", " Tcp.Connected. message=" + msg.ToString()); ​​​ _log.I("TCP已被連線", " message.LocalAddress=" + msg.LocalAddress.ToString()); ​​​ _log.I("TCP已被連線", " message.RemoteAddress=" + msg.RemoteAddress.ToString()); ​​​ Sender.Tell(new Tcp.Register(Self)); ​​​ } ​​​ protected virtual void TCPCommandFail(Tcp.CommandFailed msg) ​​​ { ​​​ Console.WriteLine($"[Error] Tcp Command Failed. {msg.Cmd}"); ​​​ } ​​​ protected virtual void TcpConnectionClosed(Tcp.ConnectionClosed msg) ​​​ { ​​​ _log.I("TCP連線關閉", " Tcp.ConnectionClosed. message=" + msg.ToString()); ​​​ _log.I("TCP連線關閉", " Message.Cause=" + msg.Cause); ​​​ _log.I("TCP連線關閉", " Message.IsAborted=" + msg.IsAborted.ToString()); ​​​ _log.I("TCP連線關閉", " Message.IsConfirmed=" + msg.IsConfirmed.ToString()); ​​​ _log.I("TCP連線關閉", " Message.IsErrorClosed=" + msg.IsErrorClosed.ToString()); ​​​ _log.I("TCP連線關閉", " Message.IsPeerClosed=" + msg.IsPeerClosed.ToString()); ​​​ } ​​​ protected virtual void TcpCommandFailed(Tcp.CommandFailed msg) ​​​ { ​​​ _log.E("TCP操作失敗", " Tcp.CommandFailed. message=" + msg.ToString()); ​​​ _log.E("TCP操作失敗", " Tcp.CommandFailed. message=" + msg.ToString()); ​​​ _log.E("TCP操作失敗", " Cmd=" + msg.Cmd.ToString()); ​​​ _log.E("TCP操作失敗", " Message=" + msg.Cmd.FailureMessage); ​​​ } ​​​ }
  • 實作AkkaPara Class
​​​public static class AkkaPara ​​​ { ​​​ /// <summary> ​​​ /// 建立 config ​​​ /// </summary> ​​​ /// <param name="port"> 本地端接口埠號 </param> ​​​ public static Config Config(string port, string publicHostName) ​​​ { ​​​ var strConfig = @" ​​​ akka ​​​ { ​​​ loglevel = DEBUG ​​​ loggers = [""Akka.Logger.NLog.NLogLogger, Akka.Logger.NLog""] ​​​ actor ​​​ { ​​​ provider = remote ​​​ debug ​​​ { ​​​ receive = on # log any received message ​​​ autoreceive = on # log automatically received messages, e.g. PoisonPill ​​​ lifecycle = on # log actor lifecycle changes ​​​ event-stream = on # log subscription changes for Akka.NET event stream ​​​ unhandled = on # log unhandled messages sent to actors ​​​ } ​​​ } ​​​ remote ​​​ { ​​​ dot-netty.tcp ​​​ { ​​​ port = {port} ​​​ hostname = 0.0.0.0 ​​​ public-hostname = {publicHostName} ​​​ } ​​​ } ​​​ io ​​​ { ​​​ tcp ​​​ { ​​​ direct-buffer-pool ​​​ { ​​​ buffer-size = 1024 ​​​ } ​​​ max-received-message-size = unlimited ​​​ } ​​​ } ​​​ }"; ​​​ strConfig = strConfig.Replace("{port}", port); ​​​ return ConfigurationFactory.ParseString(strConfig); ​​​ } ​​​ }
  • 實作ISysAkkaManager Class
​​​ public interface ISysAkkaManager ​​​ { ​​​ ActorSystem ActorSystem { get; } ​​​ IActorRef CreateActor<T>() where T : ActorBase; ​​​ IActorRef CreateChildActor<T>(IUntypedActorContext context) where T : ActorBase; ​​​ IActorRef GetActor(string actName); ​​​ ActorSelection GetActorSelection(string actorPath); ​​​ }
  • 實作SysAkkaManager Class
​​​public class SysAkkaManager : ISysAkkaManager ​​​ { ​​​ public ActorSystem ActorSystem { get; } ​​​ private readonly Dictionary<string, IActorRef> _actorDics = new Dictionary<string, IActorRef>(); ​​​ public SysAkkaManager(ActorSystem actorSystem) ​​​ { ​​​ ActorSystem = actorSystem; ​​​ } ​​​ public IActorRef CreateActor<T>() where T : ActorBase ​​​ { ​​​ return CreateActor<T>(() => ActorSystem); ​​​ } ​​​ public IActorRef CreateChildActor<T>(IUntypedActorContext context) where T : ActorBase ​​​ { ​​​ return CreateActor<T>(() => context); ​​​ } ​​​ private IActorRef CreateActor<T>(Func<IActorRefFactory> func) where T : ActorBase ​​​ { ​​​ var actName = typeof(T).Name; ​​​ if (_actorDics.ContainsKey(actName)) return _actorDics[actName]; ​​​ return RegisterActor(actName, func().ActorOf(ActorSystem.DI().Props<T>(), typeof(T).Name)); ​​​ } ​​​ private IActorRef RegisterActor(string actName, IActorRef actor) ​​​ { ​​​ if (_actorDics.ContainsKey(actName)) throw new ArgumentException($"You have been register Action {actName}"); ​​​ _actorDics.Add(actName, actor); ​​​ return actor; ​​​ } ​​​ public IActorRef GetActor(string actName) ​​​ { ​​​ if (!_actorDics.ContainsKey(actName)) throw new ArgumentException($"It't doesn't has register Action {actName}"); ​​​ return _actorDics[actName]; ​​​ } ​​​ public ActorSelection GetActorSelection(string actorPath) ​​​ { ​​​ return ActorSystem.ActorSelection(actorPath); ​​​ } ​​​ }

AkkaSystem-新增ExternalSys類別庫專案實作Akka PLC

上述Akka Base Template敲好後,接著實作與外部連線的PLC Akka System

實作檔案

  • PLC資料夾:
    • PlcRcv Class : PLC Server Actor,與外部PLC系統連線即接收外部PLC發送資料。
    • PlcRcvEdit Class : PlcRcv驗證完接收資料後,傳至Plc將資料解析轉成ORM,並將資料傳送給WebServer。
    • PlcSndEdit Class : 組成發送資料,組成完畢傳送給PlcSnd發到外部系統。
    • PlcSnd Class : PCL Client Actor 連接外部PLC系統,並發送資料給外部PLC系統。
    • PlcMgr Class : Akka PLC管理者角色,管理與建置上述Actor。

實作

  • PlcRcv Class
​public class PlcRcv : BaseServerActor ​ { ​ private readonly ISysAkkaManager _akkaManager; ​ private readonly ILog _log; ​ private readonly IActorRef _plcRcvEditActor; ​ public PlcRcv(ISysAkkaManager akkaManager, AkkaSocketIP akkaSysIp, ILog log) : base(akkaSysIp, log) ​ { ​ _akkaManager = akkaManager; ​ _plcRcvEditActor = akkaManager.GetActor(nameof(PlcRcvEdit)); ​ _log = log; ​ } ​ protected override void TcpReceivedData(Tcp.Received msg) ​ { ​ _log.I("接收到訊息"," [Info] Handle_Tcp_Received. message=" + msg.ToString()); ​ _log.I("接收到訊息"," [Info] Count=" + msg.Data.Count.ToString()); ​ _plcRcvEditActor.Tell(msg.Data.ToArray()); ​ } ​ }
  • PlcRcvEdit Class
​​ public class PlcRcvEdit : BaseActor ​​ { ​​ private readonly ILog _log; ​​ public PlcRcvEdit(ILog log) : base(log){ ​​ _log = log; ​​ } ​​ }
  • PlcSnd Class
​​public class PlcSnd : BaseClientActor ​​ { ​​ private ILog _log; ​​ public PlcSnd(AkkaSocketIP akkaSysIp, ILog log) : base(akkaSysIp, log) ​​ { ​​ _log = log; ​​ } ​​ }
  • PlcSndEdit Class
​​ public class PlcSndEdit : BaseActor ​​ { ​​ private readonly IActorRef _plcSndActor; ​​ private readonly ILog _log; ​​ public PlcSndEdit(ISysAkkaManager akkaManager, ILog log) : base(log) ​​ { ​​ _plcSndActor = akkaManager.GetActor(nameof(PlcSnd)); ​​ _log = log; ​​ } ​​ }

Step4 : Log-設置Web Server 內的 NLog Config 規範內容

在ExternalSys Akka系統建置好後,目前使用的架構方法是使用Akka NLog去紀錄Akka系統的Log,設置方式則透過NLog Config設定準則去更改紀錄Log機制。

在剛剛建置的Akka Plc得知,除了Akka本身機制的Log設置紀錄,我們還須新增PlcRcv, PlcRcvEdit, PlcSnd, PlcSndEdit。

需設置參數區段

  • 1.variable 設定Log存放路近
  • 2.targets Log設置機制與名稱
  • 3.rules Log存取Level以及寫到哪個Target

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

實做檔案

  • 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" autoReload="true" throwConfigExceptions="true" internalLogToConsole="true" internalLogLevel="info"> <variable name="LogPath" value="C:\AkkaWebTemplate"/> <targets async="true" > <target name="LogControl" xsi:type="File" fileName="${LogPath}/AkkaLog.log" archiveFileName="${LogPath}/AkkaLog.{#}.log" archiveNumbering="DateAndSequence" archiveAboveSize="1000000" encoding="utf-8" maxArchiveFiles="5" archiveDateFormat="yyyyMMdd" archiveEvery="Day" /> <target name="PlcMgrLog" xsi:type="File" fileName="${LogPath}/PlcMgrLog.log" archiveFileName="${LogPath}/PlcMgrLog.{#}.log" archiveNumbering="DateAndSequence" archiveAboveSize="1000000" encoding="utf-8" maxArchiveFiles="5" archiveDateFormat="yyyyMMdd" archiveEvery="Day" /> <target name="PlcRcvLog" xsi:type="File" fileName="${LogPath}/PlcRcvLog.log" archiveFileName="${LogPath}/PlcRcvLog.{#}.log" archiveNumbering="DateAndSequence" archiveAboveSize="1000000" encoding="utf-8" maxArchiveFiles="5" archiveDateFormat="yyyyMMdd" archiveEvery="Day" /> <target name="PlcRcvEditLog" xsi:type="File" fileName="${LogPath}/PlcRcvEditLog.log" archiveFileName="${LogPath}/PlcRcvEditLog.{#}.log" archiveNumbering="DateAndSequence" archiveAboveSize="1000000" encoding="utf-8" maxArchiveFiles="5" archiveDateFormat="yyyyMMdd" archiveEvery="Day" /> <target name="PlcSndLog" xsi:type="File" fileName="${LogPath}/PlcSndLog.log" archiveFileName="${LogPath}/PlcSndLog.{#}.log" archiveNumbering="DateAndSequence" archiveAboveSize="1000000" encoding="utf-8" maxArchiveFiles="5" archiveDateFormat="yyyyMMdd" archiveEvery="Day" /> <target name="PlcSndEditLog" xsi:type="File" fileName="${LogPath}/PlcSndEditLog.log" archiveFileName="${LogPath}/PlcSndEditLog.{#}.log" archiveNumbering="DateAndSequence" archiveAboveSize="1000000" encoding="utf-8" maxArchiveFiles="5" archiveDateFormat="yyyyMMdd" archiveEvery="Day" /> </targets> <rules> <logger name="PlcMgrLog" minlevel="Debug" writeTo="PlcMgrLog"/> <logger name="PlcRcvLog" minlevel="Debug" writeTo="PlcRcvLog" /> <logger name="PlcRcvEditLog" minlevel="Debug" writeTo="PlcRcvEditLog"/> <logger name="PlcSndLog" minlevel="Debug" writeTo="PlcSndLog" /> <logger name="PlcSndEditLog" minlevel="Debug" writeTo="PlcSndEditLog" /> <logger name="*" minlevel="Debug" writeTo="LogControl" /> </rules> </nlog>

Step5 : AkkaSystem-設置Akka參數至Web Server appsetting.json

因為是在Web Server Service下啟用Akka,故需在appsetting.json內設置相關參數

    1. Akka System Name
    1. Akka System Port
    1. Akka System Public Host IP
    1. Akka Plc Soket IP Port設置

實做檔案:

  • appsetting.json

實做

"AkkaConfigure": { ​ "Name": "AkkaSystem", ​ "Port": "8200", ​ "PublicHostIP": "127.0.0.1", ​ "PLC": { ​ "RemoteIp": "127.0.0.1", ​ "RemotePort": "7791", ​ "LocalIp": "127.0.0.1", ​ "LocalPort": "9101" ​ } ​ }

Step6 : AkkaSystem-至Web Server新增Akka DI Service

Akka與外部連結系統建置完成後,接著要在Web Server啟用Akka系統。因為 Dot Net Core框架已全面使用Dependency Injection(DI)機制,故此部分在Akka建至需Align現有框架。所以需要將Akka註冊到DI Container裡

需註冊至Container項目

  • 註冊 Akka System
  • 註冊 Akka PLC
  • 註冊 Akka Server Engine

實做檔案

  • AkkaSys資料夾
    • AkkaSysDIService Class : 註冊Akka DI相關設置
    • AkkaServerEngine Class : 啟動Akka系統

實做

  • AkkaSysDIService Class
    • Inject : 註冊Akka Class
    • GetLog : Create Nlog Instance
    • NewIPPoint : 讀取appsetting.json Akka Socket Ip Port設定檔
​ public class AkkaDIService ​ { ​ private readonly IServiceCollection _service; ​ private readonly IConfiguration _configuration; ​ public AkkaDIService(IServiceCollection service, IConfiguration configuration) ​ { ​ _service = service; ​ _configuration = configuration; ​ } ​ public void Inject() ​ { ​ // Register Akka System ​ _service.AddSingleton<ISysAkkaManager>(provider => ​ { ​ var sysName = _configuration["AkkaConfigure:Name"]; ​ var sysPort = _configuration["AkkaConfigure:Port"]; ​ var sysPublicIP = _configuration["AkkaConfigure:PublicHostIP"]; ​ var actSystem = ActorSystem.Create(sysName, AkkaPara.Config(sysPort, sysPublicIP)); ​ actSystem.UseServiceProvider(provider); ​ return new SysAkkaManager(actSystem); ​ }); ​ #region Register Akka PLC ​ _service.AddScoped(p => ​ { ​ var akkaManager = p.GetService<ISysAkkaManager>(); ​ return new PlcMgr(akkaManager, GetLog<PlcMgr>(p, "PlcMgrLog")); ​ }); ​ _service.AddScoped(p => ​ { ​ var ipPoint = NewIPPoint("PLC"); ​ var akkaManager = p.GetService<ISysAkkaManager>(); ​ return new PlcRcv(akkaManager, ipPoint, GetLog<PlcRcv>(p, "PlcRcvLog")); ​ }); ​ _service.AddScoped(p => ​ { ​ return new PlcRcvEdit(GetLog<PlcRcvEdit>(p, "PlcRcvEditLog")); ​ }); ​ _service.AddScoped(p => ​ { ​ var ipPoint = NewIPPoint("PLC"); ​ return new PlcSnd(ipPoint, GetLog<PlcSnd>(p, "PlcSndLog")); ​ }); ​ _service.AddScoped(p => ​ { ​ var akkaManager = p.GetService<ISysAkkaManager>(); ​ return new PlcSndEdit(akkaManager,GetLog<PlcSndEdit>(p, "PlcSndEditLog")); ​ }); ​ #endregion ​ // Register Akka Server Engin ​ _service.AddScoped(provider => ​ { ​ var akkaManager = provider.GetService<ISysAkkaManager>(); ​ return new AkkaServerEngine(akkaManager); ​ }); ​ } ​ private ILog GetLog<T>(IServiceProvider context, string nlogName) ​ { ​ return new NLogSend(Logging.GetLogger(context.GetService<ISysAkkaManager>().ActorSystem, nlogName)); ​ } ​ private AkkaSocketIP NewIPPoint(string sysName) ​ { ​ var localIp = _configuration[$"AkkaConfigure:{sysName}:LocalIp"]; ​ var localPort = _configuration[$"AkkaConfigure:{sysName}:LocalPort"]; ​ var remoteIp = _configuration[$"AkkaConfigure:{sysName}:RemoteIp"]; ​ var remotePort = _configuration[$"AkkaConfigure:{sysName}:RemotePort"]; ​ var ipPoint = new AkkaSocketIP ​ { ​ LocalIpEndPoint = new IPEndPoint(IPAddress.Parse(localIp), Int32.Parse(localPort)), ​ RemoteIpEndPoint = new IPEndPoint(IPAddress.Parse(remoteIp), Int32.Parse(remotePort)) ​ }; ​ return ipPoint; ​ } ​ }
  • AkkaServerEngine Class
public class AkkaServerEngine { public AkkaServerEngine(ISysAkkaManager akkaManager) { akkaManager.CreateActor<PlcMgr>(); } }

Step7:Web Server-設置StarUp

Akka DI相關設置Class與啟動Engine建至完後,將Akka DI Class與Engine加入Starup。

在ConfigureServices新增AkkaSysDIService,並使用Inject() Method。接著在Configure啟用AkkaEngine。在Web Server啟動時,走Middleware Pipline時就會啟動Akka System。

修改StarUp

  • 1.在StarUp建構子新增IWebHostEnvironment,並宣告IWebHostEnvironment內部變數
​private readonly IConfiguration _configuration; ​private readonly IWebHostEnvironment _environment; ​public Startup(IConfiguration configuration,IWebHostEnvironment environment) ​ { ​ _configuration = configuration; ​ _environment = environment; ​ }
  • 2.Configure Method新增IServiceProvider
​public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IServiceProvider serviceProvider)
  • 3.在ConfigureServices Mehod new AkkaDIService
​ public void ConfigureServices(IServiceCollection services) ​ { ​ services.AddControllersWithViews(); ​ new AkkaDIService(services, _configuration).Inject(); ​ }
  • 4.在Configure 啟用Akka
​ // 啟用Akka ​ var serverEngine = serviceProvider.GetService<AkkaServerEngine>();

Demo

Web啟動後,可用第三方SocketTest3測試是否可以跟Web Service裡的Akka連線以及檢察Log路徑檔是否有成功產生Nlog檔案

Akka Log檔案產生

Sample Code