The purpose of the article
Demonstrate, a quick view of workflow foundation, and highlight the basic knowledge that
we should know before create first useful workflow application,
and highlight a design guide lines to create reusable workflow applications.
Finally, to taste the workflow through a sample project that is generic and useful for any
document workflow system, from the client application to administration application using
database.
Workflow foundation, in brief:
We can see the workflow foundation, simply as a collection of activities that can work
together through conditions and rules for specific purpose.
Workflow foundation is not if else blocks with loop blocks, and if it was implemented as
this simple algorithm, it will cost us a lot for no benefits. The bottom line of using
workflow foundation is, simplifying a
complex algorithm to simple visual editable, maintainable workflow,
Replaced my old design, with workflow:
I used the command patterns a lot in my work, as an encapsulation of the request in
single object called command, that is initialized with parameters ,
then execute method will be invoked to retrieve the result, or to do the specific tasks,
But I always have a problem in sharing the properties, information and resources, or the
result(s) of the other commands,
Some commands were waiting until other commands finished,and others will begins from
the ending point of others,
To solve this problem I always use a mediator which in most cases is CommandManager
class that has a collection of a commands, and knows the order of execution of all
commands,and share all parameters, through the container scope, and each command has
a reference to the command manager,
so it can access all the properties of all other command very easy, or setting any
property for other command or the out client application.
Finally
to make the design good, I always inherit all the commands from CommandBase
abstracted class to give me the contract of all commands,so the CommandManager can
execute and initialize all childs commands.
The funny thing is,
that each time I try to create the command objects, I found myself made the same
generic pattern,and wondering if there any way to visualize these library for better use?
Once I saw the sequential workflow activities first time, I said "Aha"
That is exactly what I want, commands (Activities ), has a container command manager
(SequentialWorkflowActivity),and all commands has the same interface or base class
(Activity), and all commands can get the data through contacting the command manager
Sequential workflow
Finally visualizing every thing for quick development, and for better maintainability it is a
great stuff.
What impressed me in workflow foundation?
Sure every thing in workflow foundation impressed me, But the Idea of Adding service(s)
in runtime, then using this service in the workflow through referencing the service
using the public method GetService of WorkflowRuntime object, or through special
activities like CallExternalMethodActivity, HandleExternalEventActivity, it is
fantastic Idea,
because simply you can separate all the abstracted classes and interfaces to a
separate assembly, then make the workflow and the core application reference that
assembly, so you increased the level of reusability of workflow, because the workflow was
coded against the interface(s) but you can in runtime register any concrete class, that
implement this interface,
you can integrate the wonderful Provider pattern that introduced in .Net 2 with the
wonderful workflow foundation through registering any service in web.config, in workflow
section (this topic is beyond this simple project) Once you understand how to use, the
custom service with in the workflow You will understand all
other services, including "out of box" services like WorkflowPersistenceService,
WorkflowSchedulerService, TrackingService,..
Basics that you should know,
Before developing, the first practical application,
as I mentioned, using workflow as a bunch of "if else" statements, is not good idea at
all, and you should know the minimum information about workflow You should know
• How to pass parameters to workflow and to retrieve parameters, after the workflow
completed.
• How to create a custom activity, to work for specific task.
• How to use custom service, and consuming this service inside the workflow.
• How to pass data from host application to workflow or from workflow to application
through workflow processing. I think that is the minimum that you should know to create
a useful workflow application For advanced work with the workflow you may need to now,
• Using WorkflowPersistenceService, and Load and Unload to database,
• Using Transaction scope for data consistency.
• Create fault handler, in case of errors happened inside the workflow. Sue that is a lot to
mention about, the workflow specially if you, use the workflow in Asp.NET applications or
in webservice,
Or you think to update simple windows form after completing the workflow
or in specific events. But that is not the purpose of this article.
Design guide lines to work with workflow foundation
I tried to apply the workflow with objects that I already wrote before using my skills In
object oriented programming, using the workflow but I found it is quite difficult to use the
workflow, without any preparation to my code, exactly like first time I tried to use Unit
test to test "not testable " applications
I found that I have to make my code testable through dependency injection and
constructor injection and mockobjects,
Now To use the workflow you have to change your way in developing, So instead using
100% object oriented programming you should move object behaviors from concrete
classes to services,
so your code will be more service oriented
Steps for preparing your code, before using workflow:
Basic 3 steps
1) Separate all object behaviors to Services.
2) Extract the Interface for these services (user refactoring in voisual stodio 5) , so all
your services should implement services interfaces
3) Separate these interfaces to separate assembly
Consider this simple example
Assume you always use a class called
Customer, has these properties
ID, Name, Address,
and has these methods
Add(), Update(), Delete(), Attach(), Register(), Unregister(),..
To use the workflow , you should separate this object state and Identitylike like ID, Name,
Address And move the behavior (public methods, events,..) to CustomerService that
Implement ICustomerService.
In this case you can use the workflow very easy with your prepared code.
In source sample,
you will see the Document Object is inherited from the IDocumentService, and both of
them used for describing the whole document process
The Sample project in Brief
The Solution sample is 3 basic projects and other 2 projects for common objects and
interfaces.
The Idea of the Application is
The user will create a document with his client application,
the user still can update his document as long as is in Open sate.
Then the user will kick the document to workflow, then depending on the document
properties , the workflow will choose the Administrator (s), that is delegated to approve
this document, and will save the data to database and will wait until the Administrator to
complete the cycle.
The Administrator after the login screen (sample will work without password) will see all
documents that are waiting for him,
and then he will approve or Reject the document,
so the document will be flagged as approved or to open state again to the user that can
update the document or delete it or to re submit again to the workflow,
Client Application Sample
Create document inside the main form
private void btnCreateDocument_Click(object sender, EventArgs e)
{
if (ValidateForm())
{
Document _document = new Document();
_document.Category = (Category)this.cmbCategory.SelectedItem;
_document.Title = this.txtTitle.Text.Trim();
_document.Description = this.txtDescription.Text.Trim();
_document.Depositor = User.CurrentUser.UserName;
_service.CreateDocument(_document);
ConfirmSuccess();
}
else {
MessageBox.Show("Please complete, the form's fields", "Validation, Error", MessageBoxButtons.OK
, MessageBoxIcon.Error);
}
}
IDocument services class diagram
document Database Diagram
Document services class diagram
[Serializable]
public class DocumentService : IDocumentService
{
public Document GetDocumentByID(int id)
{
return DocumentDalc.GetDocumentByID(id);
}
public Documents GetCreatedDocuments(string depositor, Status status)
{
return DocumentDalc.GetAllCreatedDocuments(depositor, status);
}
public Documents GetCreatedDocuments(string depositor)
{
return DocumentDalc.GetAllCreatedDocuments(depositor);
}
public Documents GetAllNeedToApproveDocuments(string userName)
{
return DocumentDalc.GetAllNeedToApproveDocuments(userName);
}
public DocumentWorkflowItem GetWorkflowItem(Document document)
{
DocumentWorkflowItem wfItem = new DocumentWorkflowItem(document);
return wfItem;
}
#region IDocumentService Members
public void CreateDocument(Document document)
{
DocumentDalc.CreateDocumentRecord(document);
if (this.Created != null) Created.Invoke(this, new DocumentEventArguments(document));
}
public void UpdateDocument(Document document)
{
DocumentDalc.UpdateDocumentRecord(document);
if (this.Updated != null) Updated.Invoke(this, new DocumentEventArguments(document));
}
public void ArchiveDocument(Document document)
{
document.Status = Status.Archived;
DocumentDalc.UpdateDocumentRecord(document);
if (this.Archived != null) Archived.Invoke(this, new DocumentEventArguments(document));
}
public void DeleteDocument(Document document)
{
document.Status = Status.Deleted;
DocumentDalc.UpdateDocumentRecord(document);
if (this.Deleted != null) Deleted.Invoke(this, new DocumentEventArguments(document));
}
public void CreateInWorkflowDocumentForUser(Document document, string approvalUser, Guid workflowID)
{
DocumentDalc.CreateInWorkflowDocumentRecordForUser(document, workflowID, approvalUser);
if (this.SentToWorkflow != null) SentToWorkflow.Invoke(this, new DocumentEventArguments(document));
}
public void CreateInWorkflowDocumentForGroup(Document document, string approvalGroup, Guid workflowID)
{
DocumentDalc.CreateInWorkflowDocumentRecordForGroup(document, workflowID, approvalGroup);
if (this.SentToWorkflow != null) SentToWorkflow.Invoke(this, new DocumentEventArguments(document));
}
public void ApproveDocumentWorkflow(Document document, string approvalUser, Guid workflowID)
{
DocumentDalc.CreateApprovedDocumentRecordForUser(document, workflowID, approvalUser);
if (this.Approved != null) Approved.Invoke(this, new DocumentEventArguments(document));
}
public void RejectDocumentWorkflow(Document document, string approvalUser, Guid workflowID)
{
DocumentDalc.CreateRejectedDocumentRecordForUser(document, workflowID, approvalUser);
if (this.Rejected != null) Rejected.Invoke(this, new DocumentEventArguments(document));
}
#endregion
#region IDocumentService Members
public event EventHandler<externalexternaldocumenteventargument __designer:dtid="1688849860263990" /> RaiseApproveDocumentWorkflowEvent;
public event EventHandler<externalexternaldocumenteventargument /> RaiseRejectDocumentWorkflowEvent;
#endregion
public void RaiseApproveDocumentWorkflow(DocumentWorkflowItem wfItem)
{
ExternalExternalDocumentEventArgument arg = new ExternalExternalDocumentEventArgument(wfItem.WorkflowID, wfItem.Document, User.CurrentUser);
if (RaiseApproveDocumentWorkflowEvent != null)
{
RaiseApproveDocumentWorkflowEvent.Invoke(this, arg);
}
}
public void RaiseRejectDocumentWorkflow(DocumentWorkflowItem wfItem)
{
ExternalExternalDocumentEventArgument arg = new ExternalExternalDocumentEventArgument(wfItem.WorkflowID, wfItem.Document, User.CurrentUser);
if (this.RaiseRejectDocumentWorkflowEvent != null)
{
RaiseRejectDocumentWorkflowEvent.Invoke(this, arg);
}
}
#region IDocumentService Members
public event EventHandler<documenteventarguments /> Created;
public event EventHandler<documenteventarguments /> Updated;
public event EventHandler<documenteventarguments /> Archived;
public event EventHandler<documenteventarguments /> Deleted;
public event EventHandler<documenteventarguments /> SentToWorkflow;
public event EventHandler<documenteventarguments /> Approved;
public event EventHandler<documenteventarguments /> Rejected;
#endregion
}
Administration Application Program
private void approveToolStripMenuItem_Click(object sender, EventArgs e)
{
_documentID = int.Parse(dgrdDocuments.CurrentRow.Cells["ID"].Value.ToString());
DialogResult result = MessageBox.Show("Are you sure that you want to Approve this document", "confirmation", MessageBoxButtons.YesNo, MessageBoxIcon.Question);
Document doc = null;
if (result == DialogResult.Yes)
{
doc = _service.GetDocumentByID(this._documentID);
if (doc.Status != DocumentBaseLibrary.Status.InWorkflow)
{
MessageBox.Show(string.Format("This document {0} is not in Workflow state", doc), "Error", MessageBoxButtons.OK
, MessageBoxIcon.Error);
return;
}
Approve(doc);
}
}
private void Approve(Document doc)
{
DocumentWorkflowItem wfItem=_service.GetWorkflowItem(doc);
Type type = typeof(DocumentWorkflow.DocumentWorkflow);
Guid id = wfItem.WorkflowID;
try
{
WorkflowInstance inst = theWorkflowRuntime.GetWorkflow(id);
inst.Load();
this._service.RaiseApproveDocumentWorkflow(wfItem);
}
catch(Exception ex)
{
MessageBox.Show("The Document cannot be loaded. " + ex.ToString(),
"Administration", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
}
}
Conclusion
Workflow foundation, it take off completely the burden of all complexity of the
Application rules and decisions
But, to achieve the maximum reusability, you have to
prepare your code by creating services , and separate abstracted services in separate
assemblies