Archive

Archive for the ‘Open Source’ Category

A Façade for Simple and Framework-Independent Logging in .NET

April 23rd, 2009

Logging is an important aspect, but I don’t like to have dependencies on a specific logging framework all over the place. This is where a logging façade comes in handy. Basically, a façade just provides you with a common interface that decouples the used logging framework from your code:

 

//ILogger is the facade. Behind the scenes,
//a framework of your choice is used
ILogger logger = LoggerService.Logger;
logger.Log("hello world");

 

The idea of a logging façade isn’t exactly new, but I thought I’d share this one with you for a few reasons:

  • It is dead easy to use.
  • It provides quite a few overloads when it comes to logging.
  • The core library has no dependencies on other libraries at all.
  • There are two façades (separate DLLs) that log through the Enterprise Library Logging block or the BitFactory logger. And hopefully more to come!
  • Writing your own façade is as simple as overriding one single method.
  • It’s not part of another project (completely standalone), and there is no license attached to it. Do with it whatever you want.
  • It is dead easy to use.

 

image

(click on image to enlarge)

 

Here’s one way to create a file-based logger (using the BitFactory façade) and make it globally accessible. This only takes you a few lines of code:

//create a new logger instance
string file = @"C:logfile.txt";
ILogger logger = BitFactoryLogger.CreateSingleFileLogger(file);

//use the global LoggerService to store the logger
LoggerService.SetLogger(logger);


...


//this will store the info in the log file
LoggerService.Logger.Log("This is an information");

 

 

Logging via ILogger

The whole purpose of this project is to shield your libraries from the actually chosen logging framework. Accordingly, you are always logging through the ILogger instance. ILogger provides quite a few overloads of the Log method, here are a few of them:

 

public void LogData(ILogger logger)
{
  logger.Log("An information");
  logger.Log("Something Happened", TraceEventType.Warning);

  //LogItem is the most verbose version
  LogItem item = new LogItem();
  item.Message = "My Message";
  item.EventId = 999;
  item.Categories.Add("Foo");
  item.Priority = 10;
  logger.Log(item); 

  try
  {
    DivideByZero();
  }
  catch(Exception e)
  {
    logger.Log(e);
    logger.Log("Additional message.", e);
    logger.Log("Additional message.", e, TraceEventType.Critical);
  } 
}

 

 

Initializing an ILogger Implementation

During the initialization of your application, you will have to specify the the logger implementation that is supposed to be used. This might happen declaratively or directly in code. Here’s the initialization code from NetDrives, which makes a logger available through the AutoFac IOC container.

Note that I’m registering a ConsoleLogger for debug builds, while release builds write into a log file. These are completely different classes, but it doesn’t matter – they both implement the ILogger interface:

 

//init IOC container builder
var builder = new ContainerBuilder();

//register single logger instance
ILogger logger;

#if (DEBUG)
  logger = new ConsoleLogger();
#else
  logger = BitFactoryLogger.CreateSingleFileLogger(AppUtil.LogFile);
#endif

//register logger
builder.Register(logger).As<ILogger>();

 

 

Registration and Access through LoggerService

I prefer to initialize and access my logger through an IOC container, but you can do it however you like. If you’re lacking a place to make your ILogger globally accessible, you can use the static LoggerService class:

 image

 

public void InitApp()
{
  //create a file logger (use BitFactory facade)
  string logFile = @"C:MyLogFile.txt";
  ILogger logger = BitFactoryLogger.CreateSingleFileLogger(logFile);

  //register as globally used logger
  LoggerService.SetLogger(logger);
}



private void Foo()
{
  try
  {
    DoSomethingWrong();
  }
  catch(Exception e)
  {
    //get registered logger and log exception
    ILogger logger = LoggerService.Logger;
    logger.Log(e);
  }
}

 

A nice thing about LoggerService: It guarantees you always a valid ILogger instance. If no logger is set, it just falls back to a NullLogger implementation that does not create any output at all. Here’s the implementation:

 

namespace Hardcodet.Util.Logging
{
  /// <summary>
  /// Provides a global repository for a given <see cref="ILogger"/>
  /// instance. This class ensures that the <see cref="Logger"/>
  /// property is never nullo - in case no logger is defined, it
  /// automatically installs a <see cref="NullLogger"/>
  /// instance.
  /// </summary>
  public static class LoggerService
  {
    private static ILogger logger = new NullLogger();


    /// <summary>
    /// Gets the installed <see cref="ILogger"/> implementation.
    /// </summary>
    /// <remarks>This property always returns a valid
    /// logger.</remarks>
    public static ILogger Logger
    {
      get { return logger; }
    }


    /// <summary>
    /// Installs a given logger or resets the <see cref="Logger"/>
    /// to a <see cref="NullLogger"/> instance if the
    /// <paramref name="loggerImplementation"/> is a null
    /// reference.
    /// </summary>
    /// <param name="loggerImplementation">The logger to be
    /// used globally, or a null reference in order to reset
    /// the service.</param>
    public static void SetLogger(ILogger loggerImplementation)
    {
      logger = loggerImplementation ?? new NullLogger();
    }
  }
}

 

 

Creating a new Logger Façade

In case you want to use another logging framework (e.g. NLog or Log4Net), creating a new façade is very easy. Basically, you create a new project, set a reference to the base library and write a class that either

  • implements ILogger directly
  • or, even simpler, derives from the abstract LoggerBase class.

 

Feel like sharing your own façade? Just contact me and I’ll happily include your implementation 🙂

 

As a sample, here’s the code of the ConsoleLogger (part of the core library) and the Enterprise Library façade:

 

using System;

namespace Hardcodet.Util.Logging
{
  /// <summary>
  /// A very simple implementation of <see cref="ILogger"/>
  /// that outputs all messages to the system console.
  /// </summary>
  public class ConsoleLogger : LoggerBase
  {
    /// <summary>
    /// Logs a given item to the console.
    /// </summary>
    /// <param name="item">The item to be logged.</param>
    /// <exception cref="ArgumentNullException">If <paramref name="item"/>
    /// is a null reference.</exception>
    public override void Log(LogItem item)
    {
      if (item == null) throw new ArgumentNullException("item");
      Console.Out.WriteLine(item.ToLogMessage());
    }

  }
}

 

 

 

using Microsoft.Practices.EnterpriseLibrary.Logging;


namespace Hardcodet.Util.Logging.EntLibFacade
{
  /// <summary>
  /// An implementation of the <see cref="ILogger"/>
  /// interface which outputs logged data using
  /// the <see cref="Logger"/> of the MS Enterprise
  /// Library.
  /// </summary>
  public class EnterpriseLibraryLogger : LoggerBase
  {

    /// <summary>
    /// Writes a log entry to the Enterprise Library's
    /// logging block. Output depends on the logging
    /// block's configuration.
    /// </summary>
    /// <param name="item">An log item which encapsulates
    /// information to be logged.</param>
    public override void Log(LogItem item)
    {
      LogEntry entry = ConvertLogItem(item);
      Logger.Write(entry);
    }
    
    
    
    /// <summary>
    /// Creates a <c>LogEntry</c> instance which can be processed
    /// by the Enterprise Library based on a given log item. 
    /// </summary>
    /// <param name="item">An log item which encapsulates information
    /// to be logged.</param>
    /// <returns>An Enterprise Library item which corresponds
    /// to the submitted <c>LogItem</c>.</returns>
    private static LogEntry ConvertLogItem(LogItem item)
    {
      //assign properties
      LogEntry entry = new LogEntry();
      entry.Message = item.Message;
      entry.Title = item.Title;
      entry.AddErrorMessage(item.ErrorMessage);
      entry.EventId = item.EventId;
      entry.Priority = item.Priority;
      entry.Severity = item.Severity;
      entry.TimeStamp = item.TimeStamp;

      foreach (string category in item.Categories)
      {
        item.Categories.Add(category);
      }
      
      return entry;
    }
 
  }
}

 

 

The download contains the core library, two external façades (BitFactory, Enterprise Library), and a sample project. Hope you’ll like it 🙂

hardcodet-logging.zip

Author: Categories: C#, Open Source Tags:

From Lambda Dependencies to Lambda Bindings

April 3rd, 2009

Lambda-based binding for the business layer or your View Model 🙂

I’ve had this on the shelf for quite a while, but Kent Boogaart’s article on POCOs vs. DependencyObjects finally got me to cleaning things up a little. Kent is coming up with a similar approach that looks very promising, so you should keep your eyes on his blog, too.

Lambda Bindings are built on top of the Lambda Dependencies project I published a while ago. The original Lambda Dependencies allow you to observe object graphs for changes using simple LINQ expressions. Lambda Bindings leverage this pattern by not just publishing a change event but synchronizing target properties or fields automatically.

This provides you with a generic binding framework that can be used wherever you want to synchronize objects. Let’s have a first example:

 

public void TestBinding(Student student)
{
  string cityName = "";

  //synchronize the cityName field with the City property of the school's address
  var binding = LambdaBinding.BindOneWay(
      () => student.School.Address.City,
      () => cityName);

  //change the property on the School object
  student.School.Address.City = "Sin City";

  //the binding expression updated the local variable
  Assert.AreEqual("Sin City", cityName);
}

What’s happening in the snippet above is that I created a binding between a nested property of a referenced object and a local field. As soon as the binding source (the City property of a school’s address) is changed, the local cityName field is being updated as well.

 

However, the Lambda Dependencies not only cover the source properties but the whole object graph. Accordingly, exchanging the whole School (or the Student instance) also triggers an update. In the snippet below, the cityName variable is being updated twice:

 

[Test]
public void Updating_Intermediary_Object_Should_Update_Target(Student student)
{
  string cityName = "";

  //synchronize the cityName field with the City property of the school's address
  var binding = LambdaBinding.BindOneWay(
      () => student.School.Address.City,
      () => cityName);


  //change bound City property -> triggers update of the local variable
  student.School.Address.City = "Paris";
  Assert.AreEqual("Paris", cityName);

  //create a completely different school instance
  School englishSchool = new School();
  englishSchool.Address = new Address {City = "London"};

  //assign the new school to the student
  student.School = englishSchool;

  //setting the School property also triggered the binding
  Assert.AreEqual("London", cityName);
}

 

Value Conversion

You can do simple value conversion by just submitting a converter to the binding expression. This allows you to intercept the binding pipeline or bind objects of different types together. If you’re coming from WPF, this feels natural anyway, but the solution here does not require you to implement a value converter – a simple Func<TSource, TTarget> is sufficient.

Here’s a simple sample that performs a conversion of a boolean flag to into a corresponding Visibility enum value:

 

[Test]
public void Boolean_Should_Be_Converted_To_Visibility()
{
  //create a hidden window
  Window window = new Window { Visibility = Visibiliy.Collapsed };
  
  //create a view model
  MyViewModel viewModel = new MyViewModel { IsVisible = false };
  
  //create binding that casts the Visibility into a boolean
  LambdaBinding.BindOneWay(
      () => viewModel.IsVisible,
      () => window.Visibility,
      b => b == true ? Visibility.Visible : Visibility.Collapsed;
  
  //a change in the ViewModel shows/hides the window
  viewModel.IsVisible = true;
  
  Assert.AreEqual(Visibility.Visible, window.Visibility);  
}

 

Two-Way-Binding

Two way binding works too, of course:

[Test]
public void Updates_Should_Work_Both_Ways()
{
  //create two-way binding
  var binding = LambdaBinding.BindTwoWay(
    () => FirstStudent.Name,
    () => SecondStudent.Name);

  //change property on source
  FirstStudent.Name = "Peter";
  Assert.AreEqual("Peter", SecondStudent.Name);

  //change property on target
  SecondStudent.Name = "Parker";
  Assert.AreEqual("Parker", FirstStudent.Name);
}

In case you need to perform type conversion, you need to supply two converter functions for forward / reverse conversion:

//bind a boolean property to a control's Visibility property
var binding = LambdaBinding.BindTwoWay(
    () => ModelItem.IsEnabled,
    () => MyControl.IsVisible,
    b => b == true ? Visibility.Visible : Visibiliy.Collapsed
    v => v == Visibility.Visible ? true : false);

 

Default Values

In case the object graph is being broken (e.g. because the School was set to null), the target node will be automatically set to its default value (null for an object, 0 for an int etc.). However, you can also specify a default value of your own:

//a local field to be updated
private string schoolCity;

[Test]
public void Breaking_The_Chain_Should_Assign_Default_Value_To_Target_If_Specified()
{
  var binding = LambdaBinding.BindOneWay(
      () => Student.School.Address.City,
      () => schoolCity,
      "[No City]");

  //break the source chain
  Student.School = null;

  //the default value was assigned to the target
  Assert.AreEqual("[No City]", schoolCity);
}

(btw: the above snippet also shows you that you can easily bind to a field rather than a property).

 

Weak References

The underlying Lambda Dependencies only use weak references so you’re not at risk of creating memory leaks. However, LambdaBinding implements IDisposable, so the proper way to clean things up would be to dispose your binding.

Things to Consider

Remember that that the underlying Lambda Dependencies rely on the INotifyPropertyChanged interface, so don’t expect source binding to fields (or properties that do not fire a PropertyChanged event) to magically update your targets.

 

Download: lambda-dependencies.zip

 

kick it on DotNetKicks.com

WPF NotifyIcon – Release Candidate

April 1st, 2009

This is an implementation of a NotifyIcon (aka system tray icon or taskbar icon) for the WPF platform.

image

Update:

The control has now its own project page. Please go to

http://www.hardcodet.net/projects/wpf-notifyicon

Microsoft WPF DataGrid vs. Commercial Solution: 1:0

December 30th, 2008

I’ve never been really happy with the commercial data grid I’ve been using so far – the whole API felt somewhat “winformish”, and required my to write a lot of XAML or even code for even the most basic tasks.

Today, I needed a simple grid on one of my current projects, and immediately got annoyed by the same issues that bug me every time. With the difference that this time, I had a new alternative to look at – Microsoft’s Data Grid that went V1 this October.

Well: This is an amazing control. It’s not amazingly powerful, it doesn’t have amazing animations, amazing views or anything like that. There is just one thing: It gets the job done.

Here’s my top 3 in comparison to my commercial product:

1: Data Binding

Finally I have data binding the way I always thought it should be. No more dealing with the internal data representation of the grid, and 2-way-data binding to selected items (not records or rows) out of the box. I didn’t even have to look up the API – it’s just as I expected it to be. Loving it:

 

<dg:DataGrid
  x:Name="platforms"
  ItemsSource="{Binding Path=Track.Platforms, ElementName=me}"
  SelectedItem="{Binding Path=ActivePlatform, ElementName=me}">

<!-- column definitions -->

</dg:DataGrid>

 

2: Liquid (Star-Sized) Columns

To my great surprise, I can easily star-size columns to take the full available horizontal space of the grid. This is a feature I need as good as every time I use a grid. With my commercial product, I was forced to write a whole layout template in order to get there. Not anymore:

 

<dg:DataGrid.Columns>
  <!-- fixed size column -->
  <dg:DataGridTextColumn
    Header="Name"
    Width="60" 
    Binding="{Binding Path=ItemName}" />
  <!-- takes 2/3 of the remaining space -->
  <dg:DataGridTextColumn
    Header="Description"
    Width="2*"
    Binding="{Binding Path=Description}" />
  <!-- takes 1/3 of the remaining space -->
  <dg:DataGridTextColumn
    Header="Position"
    Width="*"
    Binding="{Binding Path=StartPosition}" />
</dg:DataGrid.Columns>

 

3: Styling

I really do like some of the carefully crafted themes of my commercial grid. But on the other hand, customizing it was a major pain, so styling was one of my main concerns. However, the MS grid is amazingly flexible and easy to use for a v1.0 solution.

In the end, I’m happier with my custom-styled result than the predefined theme of my commercial grid – simply because the custom style blends in perfectly with the rest of the UI:

styledgrid

checklist

 

Conclusion

I’m pretty sure that the commercial – and definitely more powerful – solutions have their rightful place on the market, but Microsoft’s grid really fills a gap for me here – especially because of the API that just keeps things simple.

I can’t help but think that the vendors that were the first ones on the market may be last in the long run. At least the API of the grid I used so far just doesn’t cut it for me. We all had to get (are still getting) acquainted to “thinking in WPF” and some of the “mature” solutions just give me the impression that their basic concepts have “Windows Forms” written all over them. And with regards to compatibility, it might get pretty hard to get rid of that. That is, however, just my 0.02$.

Go get it @ CodePlex: http://www.codeplex.com/wpf

Author: Categories: DataGrid, Open Source, WPF, WPF Controls Tags: ,

IronPython: Reusing Import Symbols to Avoid Performance Hits

December 18th, 2008

I was struggling with IronPython today as I stumbled over a pretty annoying setback when it came to dynamic compilation of scripts that involved namespace imports.

Lets do an artificial sample: The snippet below just increments a variable by one. As expected, it executes blazingly fast – after compilation, it executes a few thousand times without getting over a single millisecond:

value = value + 1

 

Now look at the following example:

import clr
clr.AddReference('System.Xml')
from System.Xml import *
value = value + 1

 

Basically, this snippet performs the same logic (incrementing the ‘value’ variable), but it contains an import for the System.Xml namespace. It’s not necessary, but it still needs to be compiled. Executing this (compiled!) script 4000 times takes over 5 seconds!

 

However: If you are lucky (as me), you have the opportunity to separate namespace imports and business logic, so you basically end up with two scripts:

  • The import script, which is executed only once to get the imported symbols
  • A script that performs the actual work
 
//script 1
import clr
clr.AddReference('System.Xml')
from System.Xml import *

//script 2
value = value + 1

 

Of course, you still need the import from the first script to perform the second one – which is where symbol dictionaries come into play:

//a dictionary that receives the import simbols
var importSymbols = new SymbolDictionary();

//execute imports on our engine
ScriptSource importSource = engine.CreateScriptSourceFromString(imports,
    SourceCodeKind.Statements);
CompiledCode importCompiled = importSource.Compile();
//create a scop to populate the symbols with the imported types
ScriptScope importScope = engine.CreateScope(importSymbols);
importCompiled.Execute(importScope);

 

The statement above executes the import script with an empty SymbolDictionary. If you check this dictionary after execution, you can see that it now contains symbols for all types of the imported namespace:

importSymbols.Key = __builtins__
value = IronPython.Runtime.PythonDictionary

importSymbols.Key = clr
value = Microsoft.Scripting.Runtime.Scope

importSymbols.Key = IHasXmlNode
value = IronPython.Runtime.Types.PythonType

importSymbols.Key = IXmlLineInfo
value = IronPython.Runtime.Types.PythonType

importSymbols.Key = IXmlNamespaceResolver
value = IronPython.Runtime.Types.PythonType

... much more

 

What we can do now is reuse this retrieved symbols for our actual worker snippet  (but beware, there’s a catch):

//compile worker script
ScriptSource scriptSource = engine.CreateScriptSourceFromString(source,
  SourceCodeKind.Statements);
CompiledCode workerScript = scriptSource.Compile();

//creating our scope with the new symbols allows us to access
//the XML namespace
ScriptScope workerScope = engine.CreateScope(importSymbols);
workerScope.SetVariable("value", 10);
workerScript.Execute(workerScope);

 

However, this code is not thread-safe, because it causes different scopes to actually share not only the import symbols, but all variables through the symbol dictionary. Look at the code below, that creates to script scope instances which are initialized with individual values:

//create a new scope with the symbols of the import scope
ScriptScope run1 = engine.CreateScope(importSymbols);
//start with a value of 10
run1.SetVariable("value", 10);
workerScript.Execute(run1);

//create a second scope with the symbols of the import scope
ScriptScope run2 = engine.CreateScope(importSymbols);
//start with a value of 20
run2.SetVariable("value", 20);
workerScript.Execute(run2);

//both scopes actually share the same variable
Console.Out.WriteLine(run1.GetVariable<int>("value"));
Console.Out.WriteLine(run2.GetVariable<int>("value"));

 

The console outputs the same value both times – because both scopes operate on the same variable:

21
21

 

So basically, we have two requirements:

  • Store import symbols (or any other shared variables) in a reusable cache.
  • Store worker variables that belong to a given script scope in another dictionary.

 

We can do this by subclassing the CustomSymbolDictionary class of the Microsoft.Scripting.Runtime.BaseSymbolDictionary namespace. Took me ages to find a solution, but implementation was a breeze:

/// <summary>
/// A symbol dictionary that provides an fixed set of
/// symbols through a <see cref="SharedScope"/>. As new variables
/// are not added to the <see cref="SharedScope"/>, these cached
/// symbols can be reused across different scopes.
/// </summary>
public class SharedSymbolDictionary : CustomSymbolDictionary
{
  /// <summary>
  /// A script scope that provides a reusable set of symbols.
  /// Any variables that are being created by the <see cref="ScriptScope"/>
  /// that owns this cache are not stored within <see cref="SharedScope"/>,
  /// but the parent scope's own symbol dictionary.
  /// </summary>
  public ScriptScope SharedScope { get; private set; }


  /// <summary>
  /// Creates a new cache instance
  /// </summary>
  /// <param name="sharedScope">A reusable <see cref="ScriptScope"/> that provides
  /// a set of symbols that are supposed to be used across several scopes.</param>
  /// <exception cref="ArgumentNullException">If <paramref name="sharedScope"/>
  /// is a null reference.</exception>
  public SharedSymbolDictionary(ScriptScope sharedScope)
  {
    if (sharedScope == null) throw new ArgumentNullException("sharedScope");
    SharedScope = sharedScope;
  }



  /// <summary>
  /// Invoked if a given variable or symbol is being requested. This method
  /// tries to find the requested item in the underlying <see cref="SharedScope"/>.
  /// </summary>
  /// <param name="key"></param>
  /// <param name="value"></param>
  /// <returns>True if the <see cref="SharedScope"/> provides the requested
  /// symbol.</returns>
  protected override bool TryGetExtraValue(SymbolId key, out object value)
  {
    //return the key from the base scope, if possible.
    lock (SharedScope)
    {
      return SharedScope.TryGetVariable(SymbolTable.IdToString(key), out value);
    }
  }



  /// <summary>
  /// Gets a list of the extra keys that are cached by the the optimized
  /// implementation of the module.
  /// </summary>
  public override SymbolId[] GetExtraKeys()
  {
    lock (SharedScope)
    {
      return SharedScope.GetItems().Select(pair =>
        SymbolTable.StringToId(pair.Key)).ToArray();
    }
  }


  /// <summary>
  /// Tries to set the extra value and return true if the specified key
  /// was found in the list of extra values.<br/>
  /// Any attempts to store extra values are being denied, which causes
  /// them to be stored in the scope itself rather than the local
  /// <see cref="SharedScope"/>. This ensures that runtime variables are
  /// not shared between different instances of the cache.
  /// </summary>
  /// <param name="key">The key that is used to store the submitted
  /// value.</param>
  /// <param name="value">Value to be cached.</param>
  /// <returns>Always false because runtime symbols are not supposed
  /// to be stored within the cache. This causes the value to be stored
  /// within the internal dictionary of the base class.</returns>
  protected override bool TrySetExtraValue(SymbolId key, object value)
  {
    return false;
  }

}

 

 

With this implementation, we can change our snippet accordingly:

//create a new scope with the symbols of the import scope
SharedSymbolDictionary cache = new SharedSymbolDictionary(importScope);
ScriptScope run1 = engine.CreateScope(cache);
//start with a value of 10
run1.SetVariable("value", 10);
workerScript.Execute(run1);

//create a second scope with the symbols of the import scope
cache = new SharedSymbolDictionary(importScope);
ScriptScope run2 = engine.CreateScope(cache);
//start with a value of 20
run2.SetVariable("value", 20);
workerScript.Execute(run2);

//both scopes actually share the same variable
Console.Out.WriteLine(run1.GetVariable<int>("value"));
Console.Out.WriteLine(run2.GetVariable<int>("value"));
 
As the SharedSymbolDictionary class stores the value variable not in the shared importScope, the variables of the worker scope can be set independently, which produces the correct output:
 
11
21

 

And the performance gain is remarkable: Execution time is once again down to 17 milliseconds for 10’000 iterations 🙂

Download cache implementation and test script: symbolcache.zip

Author: Categories: C#, Open Source Tags:

A Debug Activity for Workflow Foundation

October 1st, 2008

After having worked mostly conceptually for a few months (a detour to enterprise messaging), I’m back to writing code and getting my hands dirty. And I finally managed to get the time to start playing with Windows Workflow 😀

To me, learning is all about playing around with the technology, and here’s a first result that I think might be useful for you, too. This WF activity allows you to display an optionally parameterized message via console, debug window, or an assertion:

  • Console: The message is displayed via Console.WriteLine()
  • Debug: The message is displayed via Debug.WriteLine()
  • Assert: The message is displayed via Debug.Assert()

 

The activity supports binding points for up to 5 parameters and an optional Condition property to suppress output through either declarative or code conditions. The currently selected output mode  is nicely reflected by the designer.

 Debug Activities

Basically, the activity enables you to quickly include debugging tasks into your workflow without leaving the designer. Below is a screenshot of the console output in the ifNegativeActivity branch:

Activity Properties

At runtime, this causes the following output on the console:

console

 

The library comes with the above sample, and pretty much demonstrates the different features. The only thing worth mentioning is the use of Conditions:

  • In case of Console or Debug output mode, a condition is not required. However, in case a condition was defined, the output message will only be displayed if the condition evaluates to true. Otherwise, the activity skips the output.
  • In case of Assert output mode, the Condition property is mandatory (validated by the designer). Furthermore, in compliance with Debug.Assert, the output message will only be displayed if the condition evaluates to false.

 

The activity itself is pretty simple, but it makes a nice addition to my toolbox, especially while I’m learning. Enjoy 🙂

Important: Once you opened the sample, you will have to restart VS2008 after the initial build, or the designer won’t work properly (apparently an issue with the IDE’s cache)!


Download (VS2008): debug-activity.zip

Author: Categories: Open Source, WF Tags: ,