My very first action on this new post was to tweak the color scheme. Not because it provides any utilitarian purpose yet, but because I have a fondness for the dark theme and is my favorite Visual Studio theme. So I have spent fifteen minutes playing around with different color combinations as provided by MahApps.Metro.
Now that pretty colors are sufficient to continue, I’m actually going to make some functionality. Well, after I explain this post’s name. I have a bad tendency to think up an idea and just go start working on it, making it up as I go. This started out as one of those times before realizing that a couple of pieces of paper outlining the general flow is not a bad idea. So I have a lot of scrap paper scattered about my desk with scribblings so incomprehensible that future archeologists are going to frame them in a museum and claim the use of hieroglyphics didn’t end until a much later time period than previously believed.
Last post, I showed some pictures of a dialog but I didn’t actually show that code, so here
private void DialogConnect() { using(DataConnectionDialog dcd = new DataConnectionDialog()) { DataSource.AddStandardDataSources(dcd); if(DataConnectionDialog.Show(dcd) == DialogResult.OK) { try { var connection = new SqlConnection(dcd.ConnectionString); connection.Open(); //I'm gonna do some work } catch(Exception) { //I'm going to alert the user something went wrong. } } } } |
I don’t really care for this so I’m going to rip out the guts and throw it somewhere else. This is where Akka.NET is going to come in. I’ve created an actor which the ViewModel will be a wrapper around.
internal class ViewActor: ReceiveActor { #region Public Constructors public ViewActor() { } #endregion Public Constructors } |
I’ll figure out what to put in there in a bit. First of all, I’m going to create one. The AnalysisViewModel constructor now looks like this
public AnalysisViewModel(ActorSystem actorSystem) { _actorSystem = actorSystem; CmdConnect = new RelayCommand(DialogConnect); //private readonly IActorRef _viewActor; _viewActor = _actorSystem.ActorOf(Props.Create(() => new Actors.ViewActor()), Actors.ActorPaths.ViewActor); } |
The actor system is creating a new actor and we are keeping a reference to it.
namespace LeastCost.Actors { internal class ActorPaths { #region Public Fields public static readonly string ViewActor = "viewing"; #endregion Public Fields } } |
This class will be full of the paths to my actors. I’m not a fan of string literals scattered around.
Now for some logic. Actors communicate in messages. Messages are objects. Akka.NET seems to me to follow Alan Kay’s thoughts for object-oriented nicely, but that is a different topic in which I am not well-versed.
namespace LeastCost.Messages { internal class SqlConnectionMessage { #region Public Constructors public SqlConnectionMessage(string connectionString) { ConnectionString = connectionString; } #endregion Public Constructors #region Public Properties public string ConnectionString { get; private set; } #endregion Public Properties } } |
A message is created in the AnalysisViewModel by
private void ShowConnectionDialog() { using(DataConnectionDialog dcd = new DataConnectionDialog()) { DataSource.AddStandardDataSources(dcd); if(DataConnectionDialog.Show(dcd) == DialogResult.OK) { _viewActor.Tell(new Messages.SqlConnectionMessage(dcd.ConnectionString)); } } } |
And consumed by
public ViewActor() { Receive<Messages.SqlConnectionMessage>(x => System.Diagnostics.Debug.WriteLine(x.ConnectionString)); } |
Let’s test it out
By checking the debugger output I see
Data Source=localhost;Integrated Security=True
Tada! work offloaded. Now I’m going to offload even more. I think SQL connections are prone to errors so I’m going to put them in a child actor.
namespace LeastCost.Actors { public class SqlConnectionActor: ReceiveActor { #region Public Constructors public SqlConnectionActor() { Receive<Messages.SqlConnectionMessage>(x => DoDBThing(x)); } #endregion Public Constructors #region Private Methods private void DoDBThing(Messages.SqlConnectionMessage sqlConnectionString) { //Download schema and do something amazing. } #endregion Private Methods } } |
I also have this absurd notion that I’m going to have multiple simultaneous connections, probably won’t, but I want to. So this next part is definitely a “because I can” moment. I’m going to make a pool of these SqlConnectionActors to do my work.
actorSystem.ActorOf(Props.Create(() => new Actors.SqlConnectionActor()).WithRouter(new Akka.Routing.RoundRobinPool(5)), Actors.ActorPaths.SqlActorPool); |
But wait! There’s more!
I’m going to override the router with some cool options using HOCON configuration. I’ve changed App.config to look like this
<?xml version="1.0" encoding="utf-8" ?> <configuration> <configSections> <section name="akka" type="Akka.Configuration.Hocon.AkkaConfigurationSection, Akka" /> </configSections> <startup> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" /> </startup> <akka> <hocon> <![CDATA[ akka { actor{ deployment{ /sqlConnectRouter { router = round-robin-pool resizer { enabled = on lower-bound = 1 upper-bound = 5 } } } } } ]]> </hocon> </akka> </configuration> |
This is likely overkill, but fun overkill. The pool of routers should now resize as needed to fit the demand.
The ViewActor still handles those SqlConnectionMessages though, let’s pass that on.
internal class ViewActor: ReceiveActor { #region Private Fields private readonly ActorSelection _sqlWorkers; #endregion Private Fields #region Public Constructors public ViewActor() { _sqlWorkers = Context.ActorSelection("/user/" + Actors.ActorPaths.SqlActorPool); Receive<Messages.SqlConnectionMessage>(x => _sqlWorkers.Tell(x)); } #endregion Public Constructors } |
Let’s update the SqlConnectionActor
public class SqlConnectionActor: ReceiveActor { #region Public Constructors public SqlConnectionActor() { Receive<Messages.SqlConnectionMessage>(x => DownloadSchema(x)); } #endregion Public Constructors #region Private Methods private void DownloadSchema(Messages.SqlConnectionMessage sqlConnectionMsg) { try { using(var connection = new SqlConnection(sqlConnectionMsg.ConnectionString)) { connection.Open(); //Pull some info and give a success message } } catch(System.Exception) { Sender.Tell(new Messages.SqlConnectionFailureMessage()); } } #endregion Private Methods } |
Oh yeah, I made a new message type.
namespace LeastCost.Messages { internal class SqlConnectionFailureMessage { //Add some way to identify which connection failed. } } |
So the ViewActor needs to handle that, because the SqlConnectionActor will tell the sender that something failed by
catch(System.Exception) { Sender.Tell(new Messages.SqlConnectionFailureMessage()); } |
Right now I’ll just fill out the generic debugger text
public ViewActor() { _sqlWorkers = Context.ActorSelection("/user/" + Actors.ActorPaths.SqlActorPool); Receive<Messages.SqlConnectionMessage>(x => _sqlWorkers.Tell(x)); Receive<Messages.SqlConnectionFailureMessage>(x => System.Diagnostics.Debug.WriteLine("nooooo")); } |
I still haven’t done any actual work yet. That has to change next time. But a lot has been accomplished in this post.