这一部分主要是结合自定义活动介绍一下在工作流如何使用跟踪Trace,在工作流中进行代码跟踪有两种方式:
1、使用System.Diagnostics.Trace类
2、使用System.Activities.Tracking.CustomTrackingRecord对象
我们先看看简单一点的,使用System.Diagnostics.Trace类进行跟踪,先上代码:
switch (Level)
{
//输出错误处理消息
case System.Diagnostics.TraceLevel.Error:
//使用指定的消息向 System.Diagnostics.Trace.Listeners 集合中的跟踪侦听器中写入错误消息
System.Diagnostics.Trace.TraceError(text);
break;
//输出信息性消息、警告和错误处理消息
case System.Diagnostics.TraceLevel.Info:
//使用指定的消息向 System.Diagnostics.Trace.Listeners 集合中的跟踪侦听器中写入信息性消息
Trace.TraceInformation(text);
break;
case System.Diagnostics.TraceLevel.Verbose:
//将类别名称和消息写入 System.Diagnostics.Trace.Listeners 集合中的跟踪侦听器。
Trace.WriteLine(text, Category);
break;
case System.Diagnostics.TraceLevel.Warning:
//使用指定的消息向 System.Diagnostics.Trace.Listeners 集合中的跟踪侦听器中写入警告消息
Trace.TraceWarning(text);
break;
}
这是DiagnosticTrace.cs中的一段代码,从中我们可以看到Trace类为我们提供了四个方法,分别是写入错误消息,警告消息,信息性消息或自定义消息(类别名称及消息本身),大家会注意到在代码中的注释我们四次提到了System.Diagnostics.Trace.Listeners 集合,这个Listeners集合就是跟踪消息监听器,它的主要功能就是为监视跟踪和调试输出。只要Trace写需要写入新消息,就会将信息写入Listeners集合中的侦听器,下面我们以WriteLine方法为例,看看Trace的执行过程
[Conditional("TRACE")]
public static void WriteLine(string message, string category)
{
TraceInternal.WriteLine(message, category);
}
public static void WriteLine(string message, string category)
{
if (UseGlobalLock)
{
lock (critSec)
{
foreach (TraceListener listener in Listeners)
{
listener.WriteLine(message, category);
if (AutoFlush)
{
listener.Flush();
}
}
return;
}
}
foreach (TraceListener listener2 in Listeners)
{
if (!listener2.IsThreadSafe)
{
lock (listener2)
{
listener2.WriteLine(message, category);
if (AutoFlush)
{
listener2.Flush();
}
continue;
}
}
listener2.WriteLine(message, category);
if (AutoFlush)
{
listener2.Flush();
}
}
}
从上面的代码可以看出Trace.WriteLine方法都是交由侦听器完成的。Trace的一般情况下,如果我们没有指定自己的Listener,.net会为我们提供一个默认的侦听器DefaultTraceListener,默认情况下,该侦听器的Write 方法和WriteLine方法会将消息发出到 Win32OutputDebugString 函数和 Debugger.Log 方法。具体内容参见此处。
而在本练习中,我们要的不是将跟踪信息写入日志或直接在控制台输出,而是希望按自己的要求来处理跟踪信息,那么我们要做的事情就是:一编写自己的侦听器,二将自己的侦听器加入到Trace的侦听器集合中。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
namespace HelloWorkflow.Activities.Tests
{
//TraceListener 为监视跟踪和调试输出的侦听器提供 abstract 基类。
public class TestTraceListener : TraceListener
{
List<string> tracesMessages = new List<string>();
public List<string> TracesMessages { get { return tracesMessages; } }
public override void Write(string message)
{
tracesMessages.Add(message);
}
public override void WriteLine(string message)
{
Write(message);
}
}
}
从上面的代码可以看到,当Trace一旦调用侦听器的WriteLine方法,我们就将信息保存到自己的TracesMessages属性中。下面的代码就是应用自己的侦听器了:
[TestMethod]
public void ShouldOutputVerboseTrace()
{
string traceCategory = "Test";
string traceText = "From SayHello";
string expectedTrace = string.Format("{0}: {1}", traceCategory, traceText);
var listener = new TestTraceListener();
Trace.Listeners.Add(listener);
......
var target = new SayHello()
{
UserName = "Test"
};
......
workflow.Invoke();
//Assert System.Diagnostics.Trace
Assert.AreEqual(1, listener.TracesMessages.Count);
Assert.AreEqual(expectedTrace, listener.TracesMessages[0]);
......
}
接下来我们看看如何使用System.Activities.Tracking.CustomTrackingRecord对象进行跟踪。
WF提供了一个类CustomTrackingRecord让我们来封装跟踪信息,这个类中有一个属性Data,其类型为IDictionary<string, object>,要写入的信息就可以以键值对的形式保存到这个属性中。首先,调用CodeActivityContext.Track方法将自定义跟踪记录CustomTrackingRecord发送到已注册的全部跟踪提供程序。
#region 使用工作流的CustomTrackingRecord进行跟踪
//not(不输出跟踪和调试消息)
if (Level != System.Diagnostics.TraceLevel.Off)
{
//CustomTrackingRecord包含由运行时跟踪基础结构在引发自定义跟踪记录时发送到跟踪参与者的数据。
//CustomTrackingRecord 参数1:自定义跟踪记录的名称
//CustomTrackingRecord 参数2:跟踪记录使用的跟踪级别
CustomTrackingRecord trackRecord = new System.Activities.Tracking.CustomTrackingRecord("ppp", Level);
//Data 与此跟踪记录关联的用户定义数据
trackRecord.Data.Add("Text1", text);
trackRecord.Data.Add("Category2", Category);
//将指定的自定义跟踪记录发送到已注册的全部跟踪提供程序
context.Track(trackRecord);
}
#endregion
context.Track方法会把跟踪记录发送到已经注册的跟踪提供程序,下面我们就创建自己的跟踪提供程序,然后注册。跟踪提供程序类的基类是TrackingParticipant,我们的派生类要重写此类中的Track方法,当工作流在运行过程中写入跟踪记录时,就会调用此重写方法,执行用户逻辑。我们的派生类如下:
using System;
using System.Activities.Tracking;
using System.Collections.Generic;
namespace HelloWorkflow.Activities.Tests
{
//TrackingParticipant 与工作流跟踪基础结构交互并访问跟踪记录的工作流扩展的基类。
public class TestTrackingParticipant : TrackingParticipant
{
List<TrackingRecord> records = new List<TrackingRecord>();
public List<TrackingRecord> Records { get { return records; } }
//用于以异步方式处理跟踪记录
protected override void Track(TrackingRecord record, TimeSpan timeout)
{
records.Add(record);
}
}
}
最后,实现注册。注册除了要将工作流扩展类加入到工作流扩展集合中之外,还需要配置此扩展类要跟踪的是哪个活动的什么信息。
[TestMethod]
public void ShouldOutputVerboseTrace()
{
string traceCategory = "Test";
string traceText = "From SayHello";
string expectedTrace = string.Format("{0}: {1}", traceCategory, traceText);
......
//与工作流跟踪基础结构交互并访问跟踪记录的工作流扩展
var trackParticipant = new TestTrackingParticipant();
//TrackingProfile跟踪配置,指定需要跟踪的活动及要跟踪的信息记录TrackingRecord的名称,本例中是跟踪任何活动中发出的名为ppp的信
//息记录
var profile = new TrackingProfile()
{
//跟踪配置文件的显示名称
Name = "TestTrackingProfile",
//TrackingQuery 对象集合,这些对象定义此跟踪配置文件订阅的记录。
Queries =
{
//Name发出的自定义跟踪记录的名称(Category),ActivityName生成跟踪记录的活动的名称
//这里的Name属性值是要和CustomTrackingRecord对象的Name属性值对应
new CustomTrackingQuery(){ Name = "ppp", ActivityName = "*"}
}
};
trackParticipant.TrackingProfile = profile;
var target = new SayHello()
{
UserName = "Test"
};
var workflow = new WorkflowInvoker(target);
workflow.Extensions.Add(trackParticipant);
workflow.Invoke();
......
//Assert Tracking Records
Assert.AreEqual(1, trackParticipant.Records.Count);
var customRecord = trackParticipant.Records[0] as CustomTrackingRecord;
Assert.AreEqual(traceText, customRecord.Data["Text1"]);
Assert.AreEqual(traceCategory, customRecord.Data["Category2"]);
}
在文章的最后,我们来验证一下 PrePostSequence,按照前面的方式,先写测试方法,再完成工作流设计。测试代码如下:
[TestMethod]
public void ShouldReturnPrePostMessages()
{
IDictionary<string, object> output;
output = WorkflowInvoker.Invoke(new SayHello()
{
UserName = "Test"
});
Assert.AreEqual("This is Pre-Sequence Ordinal:1", output["PreMessage"]);
Assert.AreEqual("PrePost is Cool Ordinal:2", output["PrePostBody"]);
Assert.AreEqual("This is Post-Sequence Ordinal:3", output["PostMessage"]);
}
要测试的内容很简单,就是看Pre,PrePostBody以及Post是否一次按顺序执行。工作流设计如下:
- 添加三个输出参数PreMessage, PrePostBody 和PostMessage和一个变量Ordinal,变量的使用范围在PrePostSequence内部
- 添加一个Assign到Pre-Activities,DisplayName="Set the PreMessage", To=PreMessage, Value="This is Pre-Sequence Ordinal:" + Ordinal.ToString
- 添加一个Assign到PrePostSequence,DisplayName="Increment the Ordinal", To=Ordinal, Value=Ordinal + 1
- 添加一个Assign到PrePostSequence,DisplayName="Set the", To=PrePostBody, Value="PrePost is Cool Ordinal:" + Ordinal.ToString
- 再次添加一个Assign到PrePostSequence,DisplayName="Increment the Ordinal", To=Ordinal, Value=Ordinal + 1
- 添加一个Assign到PrePostSequence,DisplayName="Set the PostMessage", To=PostMessage, Value="This is Post-Sequence Ordinal:" + Ordinal.ToString
完整代码:http://download.csdn.net/detail/mathieuxiao/5157021