Learning MVC 2, Workflow Foundation 4, and maybe Silverlight 3, Part III

Moving along with my example MVC/workflow application (see Part I and Part II), for this post I thought I'd share some of the architecture and show a first, rough-cut at the workflow.

The solution is broken down as you see here:

 

The Common library is shared by the presentation layer as well as the logic/data layers. From a project perspective, it looks like this:

 

Here you see I have interfaces for data access and services (although there is only one service for this example). I have a "data transfer object," which I'll use to pass employee information between the layers. And I have factories that will create instances of the data access layer (DAL) and services. This allows me to make changes to the DAL or services without breaking existing code/tests, assuming I don't modify existing interface method signatures (I can add methods, just not change existing ones). It also allows me to use a strategy pattern for loading the appropriate assembly.

For example, here is the employee service factory:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Configuration;
using System.Reflection;
using KoozKooz.Common.Interfaces;

namespace KoozKooz.Common.Services
{
    public sealed class EmployeeServiceFactory
    {
        private static IEmployeeService _instance = null;

        static EmployeeServiceFactory()
        {   
            // TODO: create custom section
            // TODO: refactor code to generic loader
            string asm = ConfigurationManager.AppSettings["EmpSvs-Assembly"];
            string cls = ConfigurationManager.AppSettings["EmpSvs-Type"];

            // Dynamically load the assembly and instantiate the type  
            Assembly a = Assembly.Load(asm);
            _instance = a.CreateInstance(cls) as IEmployeeService; 
        }
        
        public static IEmployeeService GetEmployeeService()
        {
            return _instance;
        }
    }
}

The assembly information is read from the configuration file:

<add key="EmpSvs-Assembly" value="KoozKooz.Logic"/>
<add key="EmpSvs-Type" value="KoozKooz.Logic.Services.EmployeeService"/>

A quick unit test would look like this:

///<summary>
/// A test for retrieving the employee service
///</summary>
[TestMethod()]
public void GetEmployeeServiceTest()
{
    IEmployeeService svc = EmployeeServiceFactory.GetEmployeeService();
    Assert.IsTrue(svc != null);
}

The DAL code is very similar.

I've not completed the service layer, but my unit tests for loading the DAL and requesting an employee are passing. I'm now turning to the workflow. WF 4 is a very different animal than WF 3.5, and I think once I understand the changes, I'm going to like them. Two things stand out--the flow of information to and from the workflow when executed within an application seems to have improved, and the persistence model appears greatly enhanced.

With WF 3.5, if you wanted to pass information into the workflow, you could do so when the workflow was initiated (and you had to pass in a Dictionary object). If you wanted to communicate with the hosting application, you had to add a service and something I called a "bridge," which was glorified "glue" code that married the particular activity with events the host application could monitor. With WF 4, the story has improved through the use of InArgument and OutArgument. I still have more to learn, but I'll be digging into this aspect soon for this learning application. As for persistence, WF 4 has the concept of a "bookmark." Instead of adding a Delay activity to kick off workflow persistence, you now add a bookmark to your (native) activity. When the bookmark is acted upon, WF will persist the workflow and wait for some external force to re-activate the workflow. Once it is re-activated, execution resumes in an event handler you provide.

There is a slight down-side with bookmarks at present, however. Bookmarks are available only to NativeActivity-based activities. NativeActivity is a form of CodeActivity, which is how you write custom workflow activities. If using CodeActivity as your activity's base class, you can still use the designer. However, when using NativeActivity, you lose all designer support. Your workflow is then entirely code-based. Ah well. It is a beta after all. Smile

My activity will not be very complex, and I reserve the right to refactor as required. This is my first rough-cut... The activity, which I named SendManagerVacationApprovalRequestActivity, merely sends an e-mail to the employee's manager:

namespace KoozKooz.Logic.Workflow
{
    internal sealed class SendManagerVacationApprovalRequestActivity :
                                                                 NativeActivity
    {
        // Manager's employee DTO
        public InArgument<EmployeeDTO> Manager { get; set; }

        // Employee's employee DTO
        public InArgument<EmployeeDTO> Requestor { get; set; }

        // Days requested...this should aleady be filtered against the
        // employee's available days, but we'd be remiss not to check
        // here too.
        public InArgument<Int32> RequestedDays { get; set; }

        // If your activity returns a value, derive from CodeActivity<TResult>
        // and return the value from the Execute method.
        protected override void Execute(NativeActivityContext context)
        {
            // Pull runtime values
            string fname = this.Requestor.Get(context).FirstName;
            string lname = this.Requestor.Get(context).LastName;
            Int32 requestedDays = this.RequestedDays.Get(context);
            Int32 availableDays = this.Requestor.Get(context).RemainingVacation;
            string callbackUrl = String.Format("{0}&InstanceId={1}",
                          ConfigurationManager.AppSettings"VacationCallback"],
                          context.WorkflowInstanceId.ToString());

            // Format the request message body:
            //
            // 1) Requesting employee first name
            // 2) Requesting employee last name
            // 3) Number of days requested
            // 4) Number of days available
            // 5) Callback URL, with workflow instance ID
            string messageBody =
                 String.Format(Properties.Resources.VacationRequestMessage,
                     fname, lname, requestedDays, availableDays, callbackUrl);

            // Pull manager's e-mail address
            string managerEMail = this.Manager.Get(context).EMail;

            // Mail request notification
            MailHelper mailer = new MailHelper();
            mailer.From =
                  ConfigurationManager.AppSettings["SystemEMailAccount"];
            mailer.To = managerEMail;
            mailer.Subject = Properties.Resources.VacationRequestSubject;
            mailer.SendMail(messageBody, true);

            // Create named bookmark
            context.CreateBookmark("VacationApproval",
                     new BookmarkCallback(this.OnApproval),
                     BookmarkOptions.NonBlocking);
        }

        private void OnApproval(NativeActivityContext context,
                                Bookmark bookmark, object obj)
        {
            // Do nothing...allow completion
        }
    }
}

I may, for example, return the SMTP result code if the SendMail method fails rather than allow it to simply crash (which is what it'll do without any exception handling code...at least that's what would happen using WF 3.5). Something else to learn about WF 4.

In any case, the workflow will create an e-mail message and forward that to the manager. Inside the e-mail message is a hyperlink for a "callback page" I specify (in the configuration file for now, for lack of a better place to store a value I'd like to reconfigure dynamically). Once the message is sent, the workflow instance will suspend. The page the manager accesses will then re-activate the workflow, allowing it to run to completion.

Something else to note is when I created the bookmark, I used the BookmarkOptions.NonBlocking option. This allows the workflow execution engine to run past the bookmark. I'll probably play with this a little once I get the workflow hosted. Is it better to block or not block? I might find that the workflow isn't persisted if I don't block, in which case I'll revise the code. It needs to be persisted, of course, since the manager could take a long, long time to authorize the vacation, making this a long-running workflow.

Now, I realize this workflow isn't very complex, but the point of this exercise is to marry a workflow to ASP.NET MVC. The complexity of the workflow isn't the point, so I'm fine with my custom activity merely sending an e-mail. Yes, using a workflow for this at all is probably overkill. Undecided But, again, the point is to get WF 4 working with ASP.MVC 2... Once you get the plumbing in place, you can create whatever workflows you like.

The next post should be interesting as I'll have the workflow invocation code working (or so I hope...if a lot of time passes between posts, you'll know I either got swamped with work or couldn't figure it out!). I've not seen a lot regarding threading models with WF 4, so I'm curious to dig into how WF 4 handles threading, or if it parallels what we used to have to do with WF 3.5.


Tags: , ,
Categories: .NET General | ASP.NET | Workflow

5 Comments
Actions: E-mail | Permalink | Comment RSSRSS comment feed

Comments

February 23. 2010 21:21

Reverse Osmosis Filter

Thanks for sharing your MVC2 knowledge with us, very helpful to me. Looking forward to the workflow code in your next post. Also a great tutorial by the way.

Reverse Osmosis Filter

April 19. 2010 05:18

Rocky Hagenhoff

I have been surfingonline just above 3 hours today, yet I by no means found any interesting piece similar to yours about asp.net It really is pretty worth enough for me. Personally, if all webmasters and bloggers made good content as you did, the internet are going to be much more functional than ever before.

Rocky Hagenhoff

April 19. 2010 13:53

Love your blog I'm going to subscribe

April 21. 2010 10:16

Learning Centre

Thanks for explaining all this in such an easy way, i have been reading this blog for some time now and have found it extremely helpful.

Learning Centre

June 30. 2012 05:43

pingback

Pingback from cutebeachvacationideas.sims2dl.com

Blast hits Damascus, Turkey sends troops to border | Cute Beach Vacation Ideas

cutebeachvacationideas.sims2dl.com

Add comment




  Country flag

Click to change captcha
biuquote
  • Comment
  • Preview
Loading