Thursday, October 29, 2020

Cascaded ambient scopes: How to create your own

There are such situations when you want to provide something in background, but only as long as it's needed. This could be a resource or anything you want to keep togheter. The goal can be to ensure, that it is only alive as long as you need it and/or to have a maximum one single instance to provide over complete program live cycle.

Explained in other words: an outer method call could be the first place in code when the resource or connector is used, but cascaded called methods in that outer method want's also access to that resource.

One solution to solve such problem is the use of an ambient scope. For example, the TransactionScope is a good example for ambient scopes. But it does not give you possibility to open one scope over the other with the goal to access the same transaction, if a transaction scope was opened before. For transaction scope might this have its reasons, but in this article I want to show you how you can create your action scope which supports cascaded calls.

You might argue, that you can pass such a resource from one method to the other. Yes, that is true, but there are situations where you place your code where you cannot or don't want to change the method profile (in methods such as in interfaces). And that specific ressource might become that central, that you don't want to reply code to check and eventually re-fabric this resource over and over again.

Below you find an example how such a cascaded ambient scope type could look like. As an example you could imagine, that we want to share a DB connection through some program scopes. As you see, the DB connection construction is quite flawly, it's only to give you some idea about possiblities. We will see later on, how this cascaded ambient scope could be used with another example code.

using System;
using System.Collections.Generic;
using System.Data.Common;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CascadedAmbientScopes
{
   
public sealed class DbConnectionScope : IDisposable
    {
       
#region Internal diagnosys helpers
       
public static event EventHandler<DbConnectionScope> ScopeStarted;
       
public static event EventHandler<DbConnectionScope> ScopeEnded;

       
public static event EventHandler<DbConnectionScope> CascadeStarted;
        
public static event EventHandler<DbConnectionScope> CascadeEnded;

       
public static int CascadeLevel => stack.Count - 1;

       
public int StackLevel { get; private set; }

       
#endregion
       
static DbConnectionScope Current => stack.Any() ? stack.Peek() : null;


       
#region Cascade lifetime stuff
        [ThreadStatic]
       
static DbConnection dbConnection;

       
public DbConnection DbConnection => dbConnection;

       
static void CascadeStart()
        {
           
/*
             * Build the DbConnection
             */

            dbConnection = DbProviderFactories.GetFactory(
"f.ex. retrieved from configuration or use dependency injection or whatever").CreateConnection();
           
        }

       
static void CascadeEnd()
        {
           
/*
             * Destroy the DbConnection
             */

            dbConnection.Dispose();
        }


       
#endregion

       
#region Scope stack

        [ThreadStatic]
       
static readonly Stack<DbConnectionScope> stack = new Stack<DbConnectionScope>();

       
static void Add(DbConnectionScope scope)
        {
           
if (!stack.Any())
            {
                CascadeStart();
                CascadeStarted?.Invoke(
null, scope);
            }

            stack.Push(scope);

            ScopeStarted?.Invoke(
null, scope);

        }

       
static void Remove(DbConnectionScope scope)
        {

           
#region Optional extra check for lousy programmers
           
if (Current != scope)
            {
               
throw new Exception("How you dare? It's not your turn: probably you have forgotten to dispose a previous scope or you might dispose an object in a different thread than the thread when it was created.");
            }
           
#endregion

            ScopeEnded?.Invoke(
null, scope);

            _ = stack.Pop();

           
if (!stack.Any())
            {
                CascadeEnd();
                CascadeEnded?.Invoke(
null, scope);
            }
        }

       
#endregion

       
public DbConnectionScope()
        {
            StackLevel = stack.Count;

            Add(
this);
        }


       
bool disposedValue = false;
       
public void Dispose()
        {
           
if (!disposedValue)
            {
                Remove(
this);
           
}

            disposedValue =
true;
        }
    }
}

Some take-outs for this type:

  • It must be disposed correctly, because the disposing of a scope level is the trigger to give back control to parent level. Otherwise the most parent level is never disposed - and the resource is not freed.
  • The cascaded ambient scope works isolated in it's thread. That's a limitation because otherwise, we would not be able to find out, when a scope really ends since different threads will dispose and create the ambient type concurrent.

 

Now the example, how we can use the ambient type. Let's say we have a program which run's a report on sales revenue. It can either run for a complete week or for single day. We have two separate methods, to fulfill our functional requirements: DailyReport and WeeklyReport. As part of functionality, WeeklyReport is calling DailyReport, but DailyReport can be called isolated as well (yes, I know, you would not design a program like that, neither would I - but it's only for demonstration). Both methods needs same DB connection and we want to provide the DB connection as a single resource in order to increase performance.


using System;

namespace CascadedAmbientScopes
{
   
class Program
    {
       
static void Main(string[] args)
        {
           
if (args[0] == "FullAction")
            {
                WeeklyReport();
            }
           
else
            {
                DailyReport(DateTime.Now.DayOfWeek);
            }
        }

       
static void DailyReport(DayOfWeek dayOfWeek)
        {
            
using(var dbConnectinScope = new DbConnectionScope())
            {
               
/*
                 * do something with the dbConnectionScope
                 */

            }
        }

       
static void WeeklyReport()
        {
           
using (var dbConnectinScope = new DbConnectionScope())
            {
               
for (var day = DayOfWeek.Sunday; day <= DayOfWeek.Sunday; day++)
                {
                    DailyReport(day);
                }

               
/*
                 * do something additional with the dbConnectionScope
                
*/
            }
        }
    }
}

Look now the using directives with DbConnectionScope: In case of "FullAction", it's called in WeeklyReport once, and seven times implicitly with the call of DailyReport. However: The DB connection is exactly created once, namely in the second line of WeeklyReport (using (... new DbConnectionScope())) and the DB connection will be still the same for all code in DailyReport because there will be no new DB connection created as long as the using directive in WeeklyReport is open.

See the idea behind? Again, the business case with DB connection is just an example to demonstrate the mechanics and the benefits of an cascaded ambient scope.

An other Idea is the use for the possibility of prefixed logging: Each scope could add a prefix in background, so wen something should be logged, the prefixes are included.
When we take again our sales revenue example, our code could look like this:

using System;
using System.Diagnostics;

namespace CascadedAmbientScopes
{
   
class Program
    {
       
static void Main(string[] args)
        {
           
if (args[0] == "FullAction")
            {
                WeeklyReport();
            }
           
else
            {
                DailyReport(DateTime.Now.DayOfWeek);
            }
        }

       
static decimal DailyReport(DayOfWeek dayOfWeek)
        {
           
var moneyIndicator = default(decimal);
           
using(var logging = new PrefixedLoggingScope(dayOfWeek.ToString()))
            {
               
/*
                 * calculate the money indicator
                 */

                logging.Log(
$"Turnover at {moneyIndicator}");
            }

           
return moneyIndicator;
        }

       
static void WeeklyReport()
        {
           
using (var logging = new PrefixedLoggingScope("Weekly report"))
            {
               
var moneyIndicator = default(decimal);
               
for (var day = DayOfWeek.Sunday; day <= DayOfWeek.Sunday; day++)
                {
                    moneyIndicator += DailyReport(day);
                }
                logging.Log(
$"Turnover at {moneyIndicator}");
           
}
        }
    }
}

In case of "FullAction" the output could look like this:

Weekly Report/Monday/Turnover at 6000
Weekly Report/Tuesday/Turnover at 5400
Weekly Report/Wednesday/Turnover at 4400
....
Weekly Report/Turnover at 48800

 

And in case of not "FullAction" like this:

Monday/Turnover at 6000


The token "Weekly Report" would be taken from most outer scope, created with the using directive in WeeklyReport method. The Monday, Tuesday, a.s.o. is then taken from the level created by the DailyReport method (either as most outer scope or as sub level of its parent scope created by WeeklyReport).

Unit tests: "Must-Not-Throw" instead of "Throws"

Today a little article about testing. You've may experienced situations when you wanted to thest a piece of code and the goal was to make sure, that a certain exception wasn't thrown.

I had the same situation some time ago and I searched for something like Assert.MustNotThrow<...>(...), but there was nothing like that. In my case, I'd used the well known Component Test Project for .NET-Framework (also known as MSTest project for .NET-Framework).

For this blog entry, I shortly checked the some other test frameworks for C# and found out, that only NUnit provides an Assert.DoesNotThrow(...). Unfortunately you cannot specify which exception shall not be thrown. So this may make test useless - an explanation to that you can find later in this post.

However, so I decided to create an extension for my need. The code is written for MSTest project for .NET-Framework but with some small changes you can use it in other test frameworks as well. 

Here's the code, I will explain some specifics afterwards:

using System;
using System.Diagnostics;
using System.Linq;

// leave the namespace - this is required in order to suppress unwanted stack trace information when
namespace Microsoft.VisualStudio.TestTools.UnitTesting
{

   
/// <summary>
   
/// Contains assertion approaches for unit testing which are not covered by standard framework
   
/// </summary>
   
public static class AssertExtension
    {
       
/// <summary>
       
/// Tests whether the code specified by delegate action does not throws given exception
       
/// of type <c>T</c> (excluding derived types) and throws <c>AssertFailedException</c>
       
/// if code throws exception of type <c>T</c>.
       
/// </summary>
       
/// <typeparam name="T">
       
/// Type of exception expected not to be thrown.
       
/// </typeparam>
       
/// <param name="assert">
       
/// The pseudo singleton instance of <c>Assert</c>
       
/// </param>
       
/// <param name="action">
       
/// Delegate to code to be tested and which is expected not to throw specified exception.
       
/// </param>
       
/// <exception cref="Microsoft.VisualStudio.TestTools.UnitTesting.AssertFailedException">
       
/// Thrown if action does throws exception of type <c>T</c>.
       
/// </exception>
        [DebuggerHidden]
       
public static void MustNotThrow<T>(this Assert assert, Action action) where T : Exception
            => MustNotThrow(assert, action,
typeof(T));


       
/// <summary>
       
/// Tests whether the code specified by delegate action does not throws given exceptions
       
/// defined by <c>exceptionTypes</c> and throws <c>AssertFailedException</c>
       
/// if code throws exception of type given by <c>exceptionTypes</c>.
       
/// </summary>
       
/// <typeparam name="exceptionTypes">
       
/// Types of exceptions expected not to be thrown.
       
/// </typeparam>
       
/// <param name="assert">
       
/// The pseudo singleton instance of <c>Assert</c>
       
/// </param>
       
/// <param name="action">
       
/// Delegate to code to be tested and which is expected not to throw specified exception.
       
/// </param>
       
/// <exception cref="Microsoft.VisualStudio.TestTools.UnitTesting.AssertFailedException">
        
/// Thrown if action does throws exception of type specified with <c>exceptionTypes</c>.
       
/// </exception>
        [DebuggerHidden]
       
public static void MustNotThrow(this Assert assert, Action action, params Type[] exceptionTypes)
        {
           
try
            {
                action();
            }
           
catch (Exception ex)
            {
               
if (!(exceptionTypes
                    ??
throw new ArgumentNullException(nameof(exceptionTypes))).Any()
                        || exceptionTypes.Any(x => x == ex.GetType()))
                {
                   
throw new AssertFailedException($"{nameof(Assert)}.{nameof(MustNotThrow)} failed. Exception type of '{(ex ?? throw new ArgumentNullException(nameof(ex))).GetType().Name}' was thrown. This exception was not expected.", ex);
                }
               
else
                {
                   
throw; // seems the test cannot be run properly ...
                }
            }
        }
    }
}




There are two overloads, the only difference is, that one takes exactly one exception type to test on, while the other overload can take a bunch of exception types to test on. Mostly you will test on one exception type: Testing on several types of exception could be an indicator, that your test is way too general and you should create a test each exception you want to test on. However, if there is that circumstance that you need to test on multiple exception types - you have the ability (yes you have the same with NUnit when using DoestNotThrow).

You probably have seen that the class is in the same namespace as the Assert type itself. This has been done to ensure, that the test fail diagnose information does filter out our extension method, since this is not what we want to know in case a test fails. The pictures below shows the difference, in case you don't know what I'm talking about:


You see: The extension methods are listed in the stack trace, this is likeley unwanted


This is, what we want to see, in case the test failed.

I don't know if the namespace is also used as filter in other test framework, but try it! And if you have the ugly stack trace like in the first picture: It's not the worlds end, but yes ... it is confusing.

In MSTest project framework, in older versions, Assert type is static, so you cannot use extension methods: In such scenario, just change the extension methods into static methods, that way you can use it even in older framework versions (or rather consider to update the nuget ...).

The test only checks for the exception types you provided, so derived exceptions from the type you provide, will not be tested. This is done that way with the same consideration as you should test exceptions each type with its own test and along with the best practice that exception types shall be sealed, so there are not that much exception derivates expected (besides all derivates from Exception type, of course).

After all this explanations, you may ask yourself: Why do I even need to test for specific exception not to be thrown, if a test method run always fails when an exception is thrown?
Two reasons - One: You just cannot distinguish between the case that you're test have failed or the case your test was not run successfully, because both cases ends in the result, that the test method failes. And if that happens, you have to do further analyzis, what the really source reason was.
Two: When you see Assert.Whatever(...)/Assert.That.Whatever(...) in your test code, you clearly know which code fragement is subject to test with which mechanics.

And for last, a simple example how to use it. Below you can see a very simple test method which shall find out, if the nullReference.ToString() will not raise an exception type of NullReferenceException. As you can guess, the test will fail, because nullReference is null.

using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace UnitTestProject2
{
    [TestClass]
   
public class UnitTest1
    {
        [TestMethod]
       
public void TestMethod1()
        {
           
string nullReference = null;
            Assert.That.MustNotThrow<NullReferenceException>(() => nullReference.ToString());
       
}
    }
}

And that's the outcome:



Happy testing :)