![]() |
Platforms, Frameworks & Libraries »
Windows Workflow Foundation »
General
Intermediate
Enhanced Document workflow approval v2By Refky WahibThis Article supporting these features: Logging to file, TransactionScopeActivity, WorkflowCommitBatchService, SqlTrackingService, fault Handler Activity and Bi-Directional communication using custom services |
SQL, C# 2.0, Windows, .NET 2.0, .NET 3.0, Visual Studio, ADO.NET, SQL 2000, DBA, Dev
|
||||||||
|
Advanced Search Add to IE Search |
|
|
|
||||||||||||||||
Demonstrating these extra features
� Moving all interfaces in 1 assembly and all other projects will reference this assembly.
� Registering the services "out of the box and custom services" in application configuration file for better maintenance.
� Using "out of the box" system diagnostics LogToFile utility .
� Using "out of the box" WorkflowCommitBatchService, internally with using TransactionScopeActivity
and preparing our document service to work with batches and transactions.
� Using Fault Handler Activities to handle workflow internal errors.
� Bi-Directional communication, workflow with the host, through the custom service.
� Using "out of the box" SqlTrackingService, for tracking the workflow
� Using SqlTrackingService utility for query the tracked workflow.
If you did not read, the first article Document approval workflow system
I encourage you to read the article, and to run it, because this article is enhanced version for better design,
and for more workflow services.
� You should install .Net 3 released version
� you should have Visual studio extension for windows workflow
� This version of the application using SQL Server 2000
� You should create a database for SQL persistence service and for SQL Tracking service These scripts, under
C:\$windows folder$\Microsoft.NET\Framework\v3.0\Windows Workflow Foundation\SQL\EN
� For SQLPersistenceService you may need to run the Distributed Transaction Coordinator service
from control panel -> Administrative tools->services choose Distributed Transaction Coordinator service and run it if it is not running
� For transaction service you should to configure your component service,
from control panel -> Administrative tools->Component service then choose computers -> MyComputer
then with the context menu choose properties and in default properties tab tick Enable distributed com
on this computer and from MSDTC tab choose tick use local coordinator If you run any workflow
samples using SQLPersistenceService with TransactionScopeActivity so you have no need to
do any of the previous settings , and you may to do only the next settings
� You should create the application database that is in download source file
� Then change the app.config file to point to the new name of database or leave as it is if you choose
workflowDB the default database name for persistence and tracking database and DocumentDB for application database
� You may need to populate the Document database with some data to
initialize the users and groups that will appear in Client and Administration application,
so read the readme.txt in the package (zip file)
Note, some cases you may use abstracted class rather than interface, in some situations using the
abstracted classes for your basic library is much better than interface, it is out of this article scope or
always use abstracted classes then wrap it with interfaces in separate assembly
"I do that very often" to be used in client classes,
But the concept is the same "the abstracted classes and interfaces should always be away
from the core library or business logic projects".
Figure 1 DocumentBaseLibrary has all application interfaces and abstracted classes and enumerable data types
Figure 2 Class diagram of interfaces in DocumentBaseLibrary
Figure 3 abstracted classes in DocumentBaseLibrary
<configuration> <configSections> <section name="WfRuntime" type="System.Workflow.Runtime.Configuration.WorkflowRuntimeSection, System.Workflow.Runtime, Version=3.0.00000.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/> </configSections> <connectionStrings> <add name="documentDB" connectionString="Data Source=(local);Initial Catalog=documentDB;Persist Security Info=True;User ID=sa;Password=sa" providerName="System.Data.SqlClient" /> <add name="workflowDB" connectionString="Data Source=(local);Initial Catalog=workflowDB;Persist Security Info=True;User ID=sa;Password=sa" providerName="System.Data.SqlClient" /> </connectionStrings> <system.diagnostics> <switches> <add name="System.Workflow.Runtime" value="All" /> <add name="System.Workflow.Runtime.Hosting" value="All" /> <add name="System.Workflow.Runtime.Tracking" value="Critical" /> <add name="System.Workflow.Activities" value="Warning" /> <add name="System.Workflow.Activities.Rules" value="Off" /> <add name="System.Workflow LogToFile" value="1" /> </switches> </system.diagnostics> <WfRuntime> <CommonParameters> <add name="ConnectionString" value="Data Source=(local);Initial Catalog=WorkflowDB; Integrated Security=true"/> </CommonParameters> <Services> <add type= "System.Workflow.Runtime.Hosting.DefaultWorkflowSchedulerService, System.Workflow.Runtime, Version=3.0.00000.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" maxSimultaneousWorkflows="3" /> <add type="System.Workflow.Runtime.Hosting.SqlWorkflowPersistenceService, System.Workflow.Runtime, Version=3.0.00000.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" UnloadOnIdle="true" /> <add type= "DocumentCore.Bll.DocumentTransactionalService, DocumentCore"/> <add type="System.Workflow.Runtime.Tracking.SqlTrackingService, System.Workflow.Runtime, Version=3.0.00000.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /> </Services> </WfRuntime> </configuration>
It is good Idea to create a WorkflowHelper Class that is singleton or static class has only static members and has different
WorkflowRuntime properties to simplify the workflow runtime initialization and to avoid running more
than workflow runtime instance, or to load the workflow from different configuration sections
This class should encapsulate all accessing to workflow sections, and responsible to initiate the workflow instance
LogToFlile is configurable diagnosis utility, under system.diagnostics section in app.config
After you run the application you will find a log file created called WorkflowTrace.log
beside the text file, and that is the sample of this file result
System.Workflow.Runtime Start: 0 :
Workflow Runtime tracer is alive!
System.Workflow.Runtime.Hosting Information: 0 :
WorkflowRuntime: Created WorkflowRuntime
7816a986-75e8-4383-81dc-033c17ce8055 System.Workflow.Runtime.Hosting
Information: 0 : WorkflowRuntime: Starting WorkflowRuntime
7816a986-75e8-4383-81dc-033c17ce8055 System.Workflow.Runtime.Hosting
Information: 0 :
SqlWorkflowPersistenceService(00000000-0000-0000-0000-000000000000): Starting,
LoadInternalSeconds=120 System.Workflow.Runtime.Hosting Information: 0 :
DefaultWorkflowCommitWorkBatchService:
Starting System.Workflow.Runtime.Hosting Information: 0 :
SqlWorkflowPersistenceService OpenConnection start: 03/20/2007 19:09:19
System.Workflow.Runtime.Hosting Information: 0 : SqlWorkflowPersistenceService.
OpenConnection end: 03/20/2007 19:09:20 System.Workflow.Runtime.Hosting
Information: 0 :
SqlWorkflowPersistenceService.RetrieveNonblockingInstanceStateIds ExecuteReader
start: 03/20/2007 19:09:20 It is not fair to compare workflow logging utilities
and workflow tracking services The Logging utility will help you in programming
time to debug the workflow (the track service will do that as well) the result
will be written in plain file, so it will be quite hard to extract any
information , or to run queries against this file, and this file should be very
large. You should purge this file regularly So logging is not reliable neither
customizable like tracking service, so It is good Idea if you enabled the
Logging utility in debugging mode only, and stop it in release mode using #if
DEBUG directive
public interface IPendingWork { void Commit(Transaction transaction, ICollection items); void Complete(bool succeeded, ICollection items); bool MustCommit(ICollection items); }Then all your method should do nothing but save all application request information (method name, parameters)
[Serializable]
public class DocumentTransactionalService : IDocumentService, IPendingWork
�.
public void CreateInWorkflowDocumentForUser(IDocument document, string approvalUser, Guid workflowID)
{
Request req = new Request();
req.RequestType = RequestType.CreateInWorkflowDocumentForUser;
req.Document = document;
req.ApprovalUser = approvalUser;
req.WorkflowID = workflowID;
WorkflowEnvironment.WorkBatch.Add(this, req);
}
public void ApproveDocumentWorkflow(IDocument document, string approvalUser, Guid workflowID)
{
Request req = new Request();
req.RequestType = RequestType.ApproveDocumentWorkflow;
req.Document = document;
req.ApprovalUser = approvalUser;
req.WorkflowID = workflowID;
WorkflowEnvironment.WorkBatch.Add(this, req);
}
public void Commit(System.Transactions.Transaction transaction, System.Collections.ICollection items)
{
foreach (Request request in items)
{
switch (request.RequestType)
{
case RequestType.ApproveDocumentWorkflow:
ApproveDocumentWorkflowInTransaction(request.Document, request.ApprovalUser, request.WorkflowID);
break;
case RequestType.CreateInWorkflowDocumentForGroup:
CreateInWorkflowDocumentForGroupInTransaction(request.Document, request.ApprovalGroup, request.WorkflowID);
break;
case RequestType.CreateInWorkflowDocumentForUser:
CreateInWorkflowDocumentForUserInTransaction(request.Document, request.ApprovalUser, request.WorkflowID);
break;
case RequestType.RejectDocumentWorkflow:
RejectDocumentWorkflowInTransaction(request.Document, request.ApprovalUser, request.WorkflowID);
break;
default: throw new NotSupportedException(request.RequestType.ToString());
}
}
}
public void Complete(bool succeeded, System.Collections.ICollection items)
{
// nothing to clean
}
public bool MustCommit(System.Collections.ICollection items)
{
return true;
}
try { } catch (OperationCanceledException oEx) { // code to handle this specific exception } catch (NotSupportedException nEx) { // code to handle this specific exception } // --- // --- catch (Exception ex) { // generic exception that all code will come here if it is not fell down in any other exceptions }
[ExternalDataExchange]
public interface IDocumentService
{
#region events to fire methods for workflow
event EventHandler<externalexternaldocumenteventargument __designer:dtid="562949953421586"> RaiseApproveDocumentWorkflowEvent;
event EventHandler<externalexternaldocumenteventargument> RaiseRejectDocumentWorkflowEvent;
#endregion
/// other code
}
</externalexternaldocumenteventargument></externalexternaldocumenteventargument>[Serializable]
public class ExternalExternalDocumentEventArgument:ExternalDataEventArgs
{
/// other code
}
[ExternalDataExchange]
public interface IDocumentService
{
// code
#region events to used in the host, to track what is happening in workflow
event EventHandler<documenteventarguments __designer:dtid="562949953421612"> OnCreated;
event EventHandler<documenteventarguments> OnUpdated;
event EventHandler<documenteventarguments> OnArchived;
event EventHandler<documenteventarguments> OnDeleted;
event EventHandler<documenteventarguments> OnSentToWorkflow;
event EventHandler<documenteventarguments> OnApproved;
event EventHandler<documenteventarguments> OnRejected;
event EventHandler<documenteventarguments> OnError;
#endregion
</documenteventarguments></documenteventarguments></documenteventarguments></documenteventarguments></documenteventarguments></documenteventarguments></documenteventarguments></documenteventarguments>CreateInWorkflowDocumentForUserInTransaction(IDocument document, string approvalUser, Guid workflowID) { DocumentDalc.CreateInWorkflowDocumentRecordForUser(document, workflowID, approvalUser); if (this.OnSentToWorkflow != null) OnSentToWorkflow.Invoke(this, new DocumentEventArguments(document, "InWorkflow")); }So you can let the client register this event to notify him, when the document will be sent to workflow,
_service = WorkflowHelper.DocumentService as DocumentTransactionalService;
_service.OnCreated += ReceiverEvents.ServiceEvents;
_service.OnSentToWorkflow += ReceiverEvents.ServiceEvents;
_service.OnError += new EventHandler<documenteventarguments __designer:dtid="562949953421622">(_service_OnError);
return wr;
</documenteventarguments><add type="System.Workflow.Runtime.Tracking.SqlTrackingService, System.Workflow.Runtime, Version=3.0.00000.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /> This service is using SQL Server database, by default it will use the database in common parameter inside the WorkflowRuntime section <CommonParameters> <add name="ConnectionString" value="Data Source=(local);Initial Catalog=WorkflowDB; Integrated Security=true"/> </CommonParameters>
private IList<UserTrackingRecord> GetUserEvents(Guid instanceID) { SqlTrackingQuery sqlTrackingQuery = new SqlTrackingQuery(WorkflowHelper.TrackingConnectionString); SqlTrackingQueryOptions ops = new SqlTrackingQueryOptions(); SqlTrackingWorkflowInstance wftrackInstance; sqlTrackingQuery.TryGetWorkflow(instanceID,out wftrackInstance); return wftrackInstance.UserEvents; } private IList<ActivityTrackingRecord> GetActivityEvents(Guid instanceID) { SqlTrackingQuery sqlTrackingQuery = new SqlTrackingQuery(WorkflowHelper.TrackingConnectionString); SqlTrackingQueryOptions ops = new SqlTrackingQueryOptions(); SqlTrackingWorkflowInstance wftrackInstance; sqlTrackingQuery.TryGetWorkflow(instanceID, out wftrackInstance); return wftrackInstance.ActivityEvents; } private IList<WorkflowTrackingRecord> GetWorkflowEvents(Guid instanceID) { SqlTrackingQuery sqlTrackingQuery = new SqlTrackingQuery(WorkflowHelper.TrackingConnectionString); SqlTrackingQueryOptions ops = new SqlTrackingQueryOptions(); SqlTrackingWorkflowInstance wftrackInstance; sqlTrackingQuery.TryGetWorkflow(instanceID, out wftrackInstance); return wftrackInstance.WorkflowEvents; }
General
News
Question
Answer
Joke
Rant
Admin
|
PermaLink |
Privacy |
Terms of Use
Last Updated: 24 Mar 2007 Editor: |
Copyright 2007 by Refky Wahib Everything else Copyright © CodeProject, 1999-2009 Web15 | Advertise on the Code Project |