Testing That Your Public APIs Have Not Changed Unexpectedly with PublicApiGenerator and Approval Tests

We can write automated tests to cover various aspects of the code we write. We can write unit/integration tests that test that the code is producing the expected outcomes. We can use ConventionTests to ensure internal code quality, for example that classes following a specified naming convention and exists in the correct namespace. We may even add the ability to create a business readable tests using tools such as SpecFlow or BDDfy.

Another aspect that we might want to ensure doesn’t change unexpectedly is the public API that our code exposes to callers.

Using PublicApiGenerator to Generate a Report of our Public API

The first step of ensuring our public API hasn’t changed is to be able to capture the public API in a readable way. The PublicApiGenerator NuGet package (from Jake Ginnivan) gives us this ability.

Suppose we have the following class defined:

public class Calculator
{
    public Calculator()
    {
        CurrentValue = 0;
    }

    public int CurrentValue { get; private set; }

    public void Clear()
    {
        CurrentValue = 0;
    }

    public void Add(int number)
    {
        CurrentValue += number;
    }
}

Notice here that this code defines the public API that consumers of the Calculator class can use. It’s this public API that we want to test to ensure it doesn’t change unexpectedly.

We might start with some unit tests as shown in the following code:

public class CalculatorTests
{
    [Fact]
    public void ShouldHaveInitialValue()
    {
        var sut = new Calculator();

        Assert.Equal(0, sut.CurrentValue);
    }

    [Fact]
    public void ShouldAdd()
    {
        var sut = new Calculator();

        sut.Add(1);

        Assert.Equal(1, sut.CurrentValue);
    }
}

These tests help us ensure the code is doing the right thing but do not offer any protection against the public API changing. We can now add a new test that uses PublicApiGenerator to generate a string “report” detailing the public members of our API. The following test code shows this in use:

[Fact]
public void ShouldHaveCorrectPublicApi()
{
    var sut = new Calculator();

    // Get the assembly that we want to generate the public API report for
    Assembly calculatorAssembly = sut.GetType().Assembly;

    // Use PublicApiGenerator to generate the API report
    string apiString = PublicApiGenerator.PublicApiGenerator.GetPublicApi(calculatorAssembly);

    // TODO: assert API has not changed
}

If we debug this test and look at the content of the apiString variable we’d see the following text:

[assembly: System.Runtime.InteropServices.ComVisibleAttribute(false)]
[assembly: System.Runtime.InteropServices.GuidAttribute("c2dc3732-a4a5-4baa-b4df-90f40aad1c6a")]
[assembly: System.Runtime.Versioning.TargetFrameworkAttribute(".NETFramework,Version=v4.5.1", FrameworkDisplayName=".NET Framework 4.5.1")]

namespace Demo
{
    
    public class Calculator
    {
        public Calculator() { }
        public int CurrentValue { get; }
        public void Add(int number) { }
        public void Clear() { }
    }
}

Using Approval Tests to Assert the API Is Correct

Now in our test we have a string that represents the public API. We can combine PublicApiGenerator with the Approval Tests library to check that this API text doesn’t change.

First off we go and install the Approval Tests NuGet Package. We can then modify the test as shown below:

public class CalculatorApiTests
{
    [Fact]
    public void ShouldHaveCorrectPublicApi()
    {
        var sut = new Calculator();

        // Get the assembly that we want to generate the public API report for
        Assembly calculatorAssembly = sut.GetType().Assembly;

        // Use PublicApiGenerator to generate the API report
        string apiString = PublicApiGenerator.PublicApiGenerator.GetPublicApi(calculatorAssembly);

        // Use Approval Tests to verify the API hasn't changed
        Approvals.Verify(apiString);
    }
}

The first time we run this it will fail with a message such as “Failed Approval: Approval File "c:\…\Demo.Tests\CalculatorApiTests.ShouldHaveCorrectPublicApi.approved.txt" Not Found”. It will also generate a file called CalculatorApiTests.ShouldHaveCorrectPublicApi.received.txt. We can rename this to CalculatorApiTests.ShouldHaveCorrectPublicApi.approved.txt, run the test again and it will pass.

If we now modify the public API by changing a method signature (e.g. to public void Clear(int someParam)) and run the test again it will fail with a message such as “Received file c:\...\Demo.Tests\CalculatorApiTests.ShouldHaveCorrectPublicApi.received.txt does not match approved file c:\...\Demo.Tests\CalculatorApiTests.ShouldHaveCorrectPublicApi.approved.txt”.

Modifying the test and adding an Approval Tests reporter attribute ([UseReporter(typeof(DiffReporter))]) and running the test will now gives us a visual diff identifying the changes to the public API as shown in the following screenshot.

Approval Tests Diff Screenshot

To learn more about the features of Approval Tests, check out my Approval Tests for .NET Pluralsight course.

You can start watching with a Pluralsight free trial.

SHARE:

Testing Akka.NET Actor Systems

My new Pluralsight course Akka.NET Testing Fundamentals is now available.

Testing actor system brings a number of additional considerations over and above the concepts of traditional object oriented unit/integration testing.

While some additional testing support is required, for example as provided by Akka.NET’s TestKit, concepts such as “unit” and “integration” do translate to the testing of actors. For example a “unit” test becomes a test of the behaviour of a single actor in isolation and an “integration” test may involve multiple actors being testing together.

Just as with traditional OO testing where object dependencies can be mocked, so to with actor tests. For example, TestKit provides a number of pre-built mock actors that can be used if the actor(s) being tested need to be isolated. The simplest of these pre-built mock actors is the BlackHoleActor that will simply and silently accept any message sent to it and “swallow” the message. The EchoActor is another pre-built mock actor that will echo any message sent to it back into the test actor system instance as well as optionally back to the sending actor. The TestProbe pre-built mock actor is a more generalised mock actor and can be used in a number of useful ways.

To get access to the benefits of the actor testing infrastructure, the test class inherits from the TestKit base class. By doing this we get access to an automatically created actor system instance into which we can instantiate the actor(s) that need testing.

When testing an actor, it can be treated as a black or white box. A black box test would involve sending a message to the actor and examining (and asserting on) response message(s). Utilising TestKit we can also write white box actor tests where we can get access to the “internal” actor state directly and make asserts on this internal state.

If you’re new to the Actor Model or Akka.NET, check out the docs or my introductory Pluralsight course: Building Concurrent Applications with the Actor Model in Akka.NET.

You can start watching with a Pluralsight free trial.

SHARE:

Painless .NET Windows Service Creation with Topshelf

Windows Services allow us to run code in the background, without needing (for example) a console application running continually. Windows Services can run as various system users or specific local/domain users.

To see what Windows Services are installed on your (Win 8+) PC, hit Win+x then hit G. In the Computer Management window that opens, click the Services option under Services and Applications as shown in the following screenshot.

Computer Management window showing installed Windows Services

As the preceding screenshot shows, a Service has a (Display) Name, a Description, a Status (e.g. Running or not), a Startup Type (Manual, Automatic, Disabled, etc), and a Log On As that specifies in which user context the Service executes.

We can create Windows Services to run arbitrary .NET code such as:

  • Self-hosted web server using Owin
  • File system watcher and batch processing (e.g. image file conversion, video encoding)
  • Host remote Akka.NET actor system (such as in my Pluralsight course)
  • Process messages from a message queue as they arrive (e.g. MSMQ)
  • Mail, FTP, etc. servers
  • Integration/gateway, e.g. receive data from external systems

Using Topshelf

Topshelf is an open source project that greatly simplifies the creation of Windows Services.

The overview of creating a Service using Topshelf is:

  1. Create a Console application project in Visual Studio
  2. Add the Topshelf NuGet package
  3. Create a class to represent your service logic
  4. Configure your Service using the Topshelf API
  5. Build your Console application
  6. Execute your Console application passing Topshelf parameters to install/uninstall your Service

 

So assuming we have a new Console project called “Time” and the following NuGet packages are installed:

<packages>
  <package id="Microsoft.AspNet.WebApi.Client" version="5.2.3" targetFramework="net451" />
  <package id="Microsoft.AspNet.WebApi.Core" version="5.2.3" targetFramework="net451" />
  <package id="Microsoft.AspNet.WebApi.Owin" version="5.2.3" targetFramework="net451" />
  <package id="Microsoft.AspNet.WebApi.OwinSelfHost" version="5.2.3" targetFramework="net451" />
  <package id="Microsoft.Owin" version="2.0.2" targetFramework="net451" />
  <package id="Microsoft.Owin.Host.HttpListener" version="2.0.2" targetFramework="net451" />
  <package id="Microsoft.Owin.Hosting" version="2.0.2" targetFramework="net451" />
  <package id="Newtonsoft.Json" version="6.0.4" targetFramework="net451" />
  <package id="Owin" version="1.0" targetFramework="net451" />
  <package id="Topshelf" version="3.2.0" targetFramework="net451" />
</packages>

We can create a self-hosted OWIN service that tells us the time.

First off we create an API Controller to return the time and then create an OWIN startup configuration class:

public class TimeController : ApiController
{
    [HttpGet]
    public string Now()
    {
        return DateTime.Now.ToLongTimeString();
    }
}

public class Startup
{
    public void Configuration(IAppBuilder appBuilder)
    {
        var config = new HttpConfiguration();

        config.Routes.MapHttpRoute("DefaultApi", "api/{controller}/{time}");
        config.Formatters.Remove(config.Formatters.XmlFormatter);
        config.Formatters.Add(config.Formatters.JsonFormatter);

        appBuilder.UseWebApi(config);
    }
}

Next we create a class that represents our Topshelf Windows Service:

class TimeService
{
    private IDisposable _webServer;

    public void Start()
    {
        // code that runs when the Windows Service starts up
        _webServer = WebApp.Start<Startup>("http://localhost:8084");
    }

    public void Stop()
    {
        // code that runs when the Windows Service stops
        _webServer.Dispose();
    }
}

Now in the Console applications main method we can use Topshelf’s HostFactory to configure what our Windows Service will do (via the TimeService class) and how it will be configured in Windows:

class Program
{
    static void Main(string[] args)
    {
        HostFactory.Run(
            configuration =>
            {
                configuration.Service<TimeService>(
                    service =>
                    {
                        service.ConstructUsing(x => new TimeService());
                        service.WhenStarted(x => x.Start());
                        service.WhenStopped(x => x.Stop());
                    });

                configuration.RunAsLocalSystem();

                configuration.SetServiceName("ASimpleService");
                configuration.SetDisplayName("A Simple Service");
                configuration.SetDescription("Don't Code Tired Demo");
            });
    }
}

Once the solution is built, navigate to the bin/debug directory in a admin command prompt and type:

time.exe install

This will install the Service into Windows as the following screenshots show.

command prompt showing Topshelf service installation

 

Service installed in Windows

 

In the command prompt window type:

time.exe start

This will start our new service.

Now in a browser, navigate to http://localhost:8084/api/time/now and you’ll see the current time come back as JSON

screenshot showing browser getting time

To learn more about Topshelf, check out my Pluralsight course: Getting Started Building Windows Services with Topshelf.

You can start watching with a Pluralsight free trial.

SHARE:

30 Pluralsight Courses

My new Pluralsight course Stateful Reactive Concurrent SPAs with SignalR and Akka.NET was just published, this brings my total number of courses to 30.

I’ve always loved teaching and sharing and being a Pluralsight author is a great way to continue to do this.

I’d also like to take a moment to say a thankyou to everyone that’s watched my courses and for all the kind words. Thank you.

SHARE:

Improving Message Throughput in Akka.NET with Routers

One of the things I cover in my new Pluralsight course is the awesome feature of routers in Akka.NET.

Routers, with very little code/config change, allow us to spread messages across multiple instances of actors. This means we can process messages concurrently and thus improve the overall throughput of messages in the actor system.

Routers distribute message to a set of actor instance “routees”.

Router Types

Routers can be grouped into two categories: Group routers and Pool routers.

Group routers distribute messages to actors that have already been created in the actor system, this means that a Group router does not supervise its routees.

A Pool router on the other hand does create and supervise its routees.

Routing Strategies

Both Pool and Group routers use routing strategies to determine which routee(s) a message should be routed to. Not all routing strategies are available in both Group and Pool routers.

One of the routing strategies is the Round-Robin strategy. This strategy will route incoming messages in turn to each routee in order, then return back to the first routee and continue around all routees.

An Example

As an example, consider the following console application that simulates some work being done with a Thread.Sleep(200).

using System;
using System.Diagnostics;
using System.Threading;
using Akka.Actor;

namespace ConsoleApplication1
{
    public class CompletedSomeWorkMessage
    {
    }

    public class DoSomeWorkMessage
    {
        public DoSomeWorkMessage(int workItem)
        {
            WorkItem = workItem;
        }

        public int WorkItem { get; private set; }
    }

    public class WorkerActor : ReceiveActor
    {
        private readonly IActorRef _counterActorRef;

        public WorkerActor(IActorRef counterActorRef)
        {
            _counterActorRef = counterActorRef;

            Receive<DoSomeWorkMessage>(
                message =>
                {
                    Console.WriteLine("Working on {0}", message.WorkItem);

                    // simulate some work
                    Thread.Sleep(200);

                    _counterActorRef.Tell(new CompletedSomeWorkMessage());
                });
        }
    }


    public class WorkCounterActor : ReceiveActor
    {
        private int _workLeft;

        public WorkCounterActor(int workToBeDone)
        {
            _workLeft = workToBeDone;

            Receive<CompletedSomeWorkMessage>(
                message =>
                {
                    _workLeft--;

                    if (_workLeft == 0)
                    {
                        // all work is done so shutdown actor system
                        Context.System.Shutdown();
                    }
                });
        }
    }

    internal class Program
    {
        private static void Main(string[] args)
        {          
            const int totalWorkToBeDone = 20;

            var system = ActorSystem.Create("MySystem");
            var counter = system.ActorOf(Props.Create(() => new WorkCounterActor(totalWorkToBeDone)));
            var worker = system.ActorOf(Props.Create(() => new WorkerActor(counter)));


            var timeTaken = Stopwatch.StartNew();

            for (int i = 1; i <= totalWorkToBeDone; i++)
            {
                worker.Tell(new DoSomeWorkMessage(i));
            }

            system.AwaitTermination();

            timeTaken.Stop();


            Console.WriteLine("Took {0}ms", timeTaken.ElapsedMilliseconds);
            Console.ReadLine();
        }
    }
}

Running this application results in the following screenshot:

Akka.NET console application screenshot with no router

Notice in the preceding screenshot that to process 20 work items took about 4 seconds.

We can increase the concurrency in the system by adding a Round Robin Pool router.

The following modified code shows the creation of 5 worker actors as part of a pool.

var worker = system.ActorOf(Props.Create(() => new WorkerActor(counter)).WithRouter(new RoundRobinPool(5)));

Now when we execute worker.Tell(new DoSomeWorkMessage(i)) the message will be routed to one of five automatically created WorkerActor instances.

Running the program now results in an execution time of about 1.5 seconds rather than the non-router version that took about 4 seconds.

Akka.NET console application screenshot with a round robin router

To learn more about the different types of routers and other ways to improve message throughput check out my Pluralsight course: Improving Message Throughput in Akka.NET or check out the Akka.NET documentation.

You can start watching with a Pluralsight free trial.

SHARE:

Akka.NET Dispatchers and User Interface Thread Access

Akka.NET actors typically run in their own (non-UI) threads. This means by default accessing UI objects (for example setting some text in a text box) will cause a thread access exception.

For example the following code shows an actor trying to set the text of the button. The actor is not running in the UI context but the Button is so we’re trying to cross threads which causes the exception shown below.

Screenshot of Visual Stuid showing thread access exception in debugger

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <StackPanel>
        <Button Click="Button_OnClick">Say Hi</Button>
        <TextBox Name="GreetingTextBox"></TextBox>
    </StackPanel>
</Window>

public class HelloWorldActor : ReceiveActor
{
    public HelloWorldActor(TextBox textBox)
    {
        Receive<string>(s => textBox.Text = s);
    }
}
public partial class MainWindow : Window
{
    private ActorSystem _actorSystem;
    private IActorRef _actor;

    public MainWindow()
    {
        InitializeComponent();

        _actorSystem = ActorSystem.Create("MySystem");

        _actor = _actorSystem.ActorOf(Props.Create(() => new HelloWorldActor(GreetingTextBox)));
    }

    private void Button_OnClick(object sender, RoutedEventArgs e)
    {
        _actor.Tell("G'day");
    }
}

The thread on which an actor runs can be configured, either in code or in the app/web HOCON config.

We can modify the actor props in code as follows:

_actor = _actorSystem.ActorOf(Props.Create(() => new HelloWorldActor(GreetingTextBox))
                                   .WithDispatcher("akka.actor.synchronized-dispatcher"));

Now the HelloWorldActor will run on the UI thread and be able to access the text box as the following screenshot shows:

screenshot of WPF application running and updating the TextBox from another thread

 

MVVM and INotifyPropertyChanged

When using MVVM and a view-model, rather that passing the UI element to the actor, the view-model can instead be passed. The actor now updates the view-model which the textbox is databound to. In this situation the data-binding mechanism/INotifyPropertyChanged will take care of marshalling changes to the UI thread. This means that the actor no longer needs to be configured to run on the UI thread. The following code shows these changes:

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <StackPanel>
        <Button Click="Button_OnClick">Say Hi</Button>
        <TextBox Text="{Binding Greeting}"></TextBox>
    </StackPanel>
</Window>

using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows;
using Akka.Actor;

namespace WpfApplication1
{    
    public partial class MainWindow : Window
    {
        private ActorSystem _actorSystem;
        private IActorRef _actor;
        private ViewModel _viewModel ;

        public MainWindow()
        {
            InitializeComponent();

            _viewModel = new ViewModel();

            _actorSystem = ActorSystem.Create("MySystem");

            _actor = _actorSystem.ActorOf(Props.Create(() => new HelloWorldActor(_viewModel)));
            
            DataContext = _viewModel;
        }

        private void Button_OnClick(object sender, RoutedEventArgs e)
        {
            _actor.Tell("G'day");
        }
    }

    public class HelloWorldActor : ReceiveActor
    {
        private readonly ViewModel _viewModel;

        public HelloWorldActor(ViewModel viewModel)
        {
            _viewModel = viewModel;

            Receive<string>(s => _viewModel.Greeting = s);
        }
    }

    public class ViewModel : INotifyPropertyChanged
    {
        private string _greeting;

        public string Greeting
        {
            get { return _greeting; }
            set
            {
                _greeting = value;
                OnPropertyChanged();
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            var handler = PropertyChanged;
            if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

You can read more about dispatchers in the Akka.NET docs and learn more about using Akka.NET with WPF in my Pluralsight course: Building Reactive Concurrent WPF Applications with Akka.NET.

You can start watching with a Pluralsight free trial.

SHARE:

Using Predicates in Akka.NET Receive Actors

When using the Receive actor API in Akka.NET, we can take advantage of overloads of the Receive method that allow us to specify predicates.

A message will now be handled if it is of the correct type and the predicate is met.

Take the following actor that sends emails with a different message if a customer is “high value”:

class WelcomeEmailSender : ReceiveActor
{
    public WelcomeEmailSender()
    {
        Receive<SendNewCustomerWelcomeEmail>(message => HandleMessage(message));
    }

    private void HandleMessage(SendNewCustomerWelcomeEmail message)
    {            
        if (message.IsHighValueCustomer)
        {
            SendEmail("Dear high value customer...", message.EmailAddress);
        }
        else
        {
            SendEmail("Yo! ...", message.EmailAddress);
        }
    }

    private void SendEmail(string greeting, string emailAddress)
    {
        Console.WriteLine("{0} to {1}", greeting, emailAddress);
    }
}

In the preceding code, the if statement is deciding what action is taken – the welcome message to be sent.

Using an overload of the receive method, we can add a predicate that replaces this if statement:

class WelcomeEmailSender2 : ReceiveActor
{
    public WelcomeEmailSender2()
    {
        Receive<SendNewCustomerWelcomeEmail>(
            message =>
            {
                SendEmail("Dear high value customer...", message.EmailAddress);
            },
            handleIf => handleIf.IsHighValueCustomer);

        Receive<SendNewCustomerWelcomeEmail>(
            message =>
            {
                SendEmail("Yo! ...", message.EmailAddress);
            }, 
            handleIf => handleIf.IsHighValueCustomer == false);
    }

    private void SendEmail(string greeting, string emailAddress)
    {
        Console.WriteLine("{0} to {1}", greeting, emailAddress);
    }
}

In the preceding code the predicate (represented by the lambda: handleIf => handleIf.IsHighValueCustomer) replaces the if.

In these example we only have two possible predicates because we are looking at the boolean “IsHighValueCustomer”. The following code shows predicates being defined for non-boolean (string) values:

class SomeActor : ReceiveActor
{
    public SomeActor()
    {
        Receive<string>(message => Console.WriteLine("Knock knock..."), handleIfMessage => handleIfMessage.Equals("Tell me a joke"));
        Receive<string>(message => Console.WriteLine("Hello There!"), handleIfMessage => handleIfMessage.Equals("Say Hi"));
        Receive<string>(message => Console.WriteLine("Woof"), handleIfMessage => handleIfMessage.Equals("Bark like a dog"));
    }
}

If we were to send this actor the following messages they would be received and handled:

IActorRef someActor = actorSystem.ActorOf<SomeActor>("Some");

someActor.Tell("Tell me a joke");
someActor.Tell("Say Hi");
someActor.Tell("Bark like a dog");

However, the following would not be received as it has no matching predicate (even though the message is still of type string):

someActor.Tell("Say bye");

To learn more about Akka.NET, check out the docs or my Pluralsight courses: Building Concurrent Applications with the Actor Model in Akka.NET and Implementing Logging and Dependency Injection in Akka.NET.

You can start watching with a Pluralsight free trial.

SHARE:

New Pluralsight Course: Implementing Logging and Dependency Injection in Akka.NET

If you’ve already watched my Akka.NET fundamentals Pluralsight course and want to learn more about using DI and logging, check out my new Implementing Logging and Dependency Injection in Akka.NET course.

You can start watching with a Pluralsight free trial.

Course Description

Akka.NET makes building concurrent and distributed applications easier. As with other architectures, Akka.NET based systems need effective logging for the monitoring and diagnosing of systems when deployed to production. Just because we use Akka.NET to get the benefits of the Actor Model, it doesn’t mean that best practices for object construction and dependencies such as dependency injection should be ignored. By the end of the course, you’ll understand how to implement effective logging in your Akka.NET system and how to use dependency injection to ensure the services your actors depend on are still provided in a loosely coupled and configurable way.

SHARE:

Switchable Actor Behaviour in Akka.NET

One of the things that actors can do is change their behaviour in response to an incoming message. This doesn’t however mean that we end up with a single “god actor” that has too many responsibilities. Whilst this may also seem like a potentially dangerous thing to do it is actually a very poweful concept. Because an actor instance only processes one message at a time, we don’t have to worry about this behaviour switch affecting the currently processing message, in fact the change in behaviour applies to the next message processed.

There’s a number of benefits to using switchable behaviours including the potential reduction/simplification of the amount of code we need to write and the ability for our brains to conceptualize what the the actor does more readily.

As an example, consider the following code that represents an actor that can respond in one of two ways to an incoming message depending on its current (boolean) state:

class LightSwitchActor : ReceiveActor
{
    private bool _isTurnedOn;
    public LightSwitchActor()
    {
        Receive<FlipTheSwitchMessage>(message => HandleMessage(message));
    }

    private void HandleMessage(FlipTheSwitchMessage message)
    {
        if (_isTurnedOn)
        {
            // do some processing when currently turned on and message received

            // flip the switch
            _isTurnedOn = false;
        }
        else
        {
            // do some processing when currently turned off and message received

            // flip the switch
            _isTurnedOn = true;
        }
    }
}

In the preceding code, the state of the boolean field _isTurnedOn determines how the actor behaves in response to an incoming FlipTheSwitchMessage.

This actor could be refactored so that the isTurnedOn field is removed and the different behavioural states become more explicit:

class LightSwitchBehaviourActor : ReceiveActor
{
    public LightSwitchBehaviourActor()
    {
        TurnedOff();   
    }


    private void TurnedOn()
    {
        Receive<FlipTheSwitchMessage>(
            message =>
            {
                // do some processing when currently turned on and message received                            

                // flip the switch
                Become(TurnedOff);
            });
    }

    private void TurnedOff()
    {
        Receive<FlipTheSwitchMessage>(
            message =>
            {
                // do some processing when currently turned off and message received                                  

                // flip the switch
                Become(TurnedOn);
            });
    }
}

Note the use of the Become() method which allows us to change how we respond to the next message that gets processed.

Whilst in these simple examples there are only two states (TurnedOn and TurnedOff) an actor could describe a greater number of potentially more complex behaviours.

For more information check out the Akka.NET docs or my Pluralsight course: Building Concurrent Applications with the Actor Model in Akka.NET.

You can start watching with a Pluralsight free trial.

SHARE: