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

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

In my previous post, I mentioned designing a small database to support a learning application I'd like to write. I also mentioned I'd discern the differences in the two workflow persistence models I see that are available in Workflow Foundation 4. Let's start with the latter.

It turns out there isn't really much to say. WF 3.5 persisted workflows using a "persistence provider model." WF 4, on the other hand, uses persistence according to an "instance store." The two models are incompatible, and the persistence provider is shipped with WF 4 only for backwards compatibility. All new WF 4 workflows should use the instance store, so mystery solved. We'll use the instance store when we get to that point.

As for the database, my goal isn't to design the be-all, end-all of databases for HR (as you may recall, the task will be to authorize vacation/holiday time off, which is an HR function). I just need some basic information:

  • The empoloyee's name, both requestor and manager
  • E-mail addresses
  • Amount of remaining vacation
  • An employee ID, used to uniquely identify everyone in the database

For my sample application, this data should suffice. There is always more we could add, like amount of vacation authorized by the manager but not yet taken, or the amount of vacation rolled over from last year--that sort of thing. For my needs here, however, I'll keep it simple. I'm much more interested in the workflow for vacation authorization and connecting that to an MVC application.

With that information in mind, I created this database scheme:

It's somewhat normalized, but I tend to design database that way. I say "somewhat" because the RemainingVacation column is stuck in the EmployeeData table, which isn't really where I'd probably put it in a true HR database design, but it's better than adding a third table just for this little sample application. So I'll live with that bit of denormalization. Smile I created some test data, which you see in this script (I'll swap out the e-mail addresses for valid ones later):

INSERT INTO [Koozkooz].[dbo].[Employees]
           ([EmployeeID])
     VALUES
           ('5001')
GO

INSERT INTO [Koozkooz].[dbo].[Employees]
           ([EmployeeID])
     VALUES
           ('10001')
GO

INSERT INTO [Koozkooz].[dbo].[EmployeeData]
           ([EmployeeDataID]
           ,[EmployeeID]
           ,[FName]
           ,[LName]
           ,[EMail]
           ,[RemainingVacation]
           ,[ManagerID])
     VALUES
           ('CEBB9D6F-9B50-4C8E-BAC7-B8E03377E5EB'
           ,'5001'
           ,'Tonya'
           ,'Manager'
           ,'tonya@koozkooz.com'
           ,20
           ,'0')
GO

INSERT INTO [Koozkooz].[dbo].[EmployeeData]
           ([EmployeeDataID]
           ,[EmployeeID]
           ,[FName]
           ,[LName]
           ,[EMail]
           ,[RemainingVacation]
           ,[ManagerID])
     VALUES
           ('A57152B2-D4F3-4A5E-8F74-C734C3B5C9A2'
           ,'10001'
           ,'Aimee'
           ,'Employee'
           ,'aimee@koozkooz.com'
           ,5
           ,'5001')
GO

The image depicting the database is from Visual Studio, and you've probably already noted I'm using LINQ to SQL. I've started framing the application in Visual Studio 2010 (Beta 2). I settled on the name "KoozKooz," if only because a quick Web search didn't turn up a lot of hits on the name:

Something in the beta I believe needs to be addressed is the ability to name things. When creating new projects, for example, you tell Visual Studio 2010 where you want the code located, but it names the project. At least the solution is named according to the location, as you'd expect. Anyway, in my case, I created an MVC 2 Web application, but Visual Studio named it "MvcApplication2" for me. I'll probably rename it to be "KoozKooz.Web," but you get the idea. Another limitation in the beta is deciding where Web apps should be hosted. With Visual Studio 2008, you can choose to use Cassini or IIS (locally or remotely). That functionality is not present in the current beta, but clearly it's needed. I'm sure it'll be in the next beta, or whatever release Microsoft intends.

In the next post, I'll start rigging up the data access and application layers, as well as their unit tests.

(Update: I found the KoozKooz domain was available, so I bought the rights. It's nice to be legal. Smile)


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

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

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

About a year ago I wrote some software for Microsoft that included a Web application that drove workflow to create a Word document filled with specific (and dynamic) information. It was interesting combining workflow with ASP.NET as there are things you have to do in addition to the normal workflow hosting. For one thing, workflow persistence is mandatory because the workflows would be considered long-running. For another, you can't use the typical thread management as you'd be tying up IIS threads. I didn't write about this in my workflow book since it wasn't part of the typical workflow thought process at the time, which perhaps was too limiting on my part. I liked the demos I created for that book, but prior to its release, the workflow community did see workflow more related to Windows Forms or WPF applications.

Now that we have ASP.NET MVC 2, WF 4, and a host of new technologies, it's time for me to dig in a little. I also edited Dino's terrific Web architecture book, Microsoft® .NET: Architecting Applications for the Enterprise, and am editing his awesome upcoming MVC book, tentatively entitled "Programming Microsoft® ASP.NET MVC". (If you already own every MVC book out there, trust me you'll want this one too...very "internals" oriented). I'd love to work out an example prescriptive site or two just to put some of this stuff into practice.

So, open kimono time... The only way to learn this stuff is to DO this stuff. So I came up with an idea. I'd wondered about combining workflow in a Web application using SMTP to authorize things. That is, imagine an enterprise-class Web application that forwards authorization requests to a manager for approval. The request could be anything, but I'll pick....oh...vacation authorization. So you go to the HR site, place a demand for vacation time, and the application forwards your manager the request. The e-mail has an embedded link that fires up the app in the manager's browser, they click yes or no, and the system notifies you of their decision. I'm sure everyone but me has written this, but I thought it made for a good scenario to work with MVC 2 and WF 4. We'll see about working in the Silverlight once the foundational components are in place. I say that only because SL can do some really powerful UI's, and this scenario ain't it. But it might make a good seque into RIA services. (!)

What I write here is raw stuff. Maybe it's well architected and maybe things should be refactored. That's fine...I'm playing with concepts more than anything. If you see glaring omissions or errors, by all means comment! We all learn more that way.

The first thing I see I'm going to need to do is determine the difference between traditional WF persistence and something I'm seeing called "workflow instance store." Is one perferred for Web apps? I honestly don't know yet, but I'll research it a bit and report back. It'll matter because I'll create a SQL Server 2008 database and need to run the appropriate scripts (both sets of which are provided with Visual Studio 2010). I'm starting here because, to me, the app is there to serve the data, not the other way around. I'll design a rudimentary database, just enough to support my scenario, and then layer over that LINQ code to interface with the application service layer. That, then, drives the user interface, at least for this scenario.

So, next time I'll describe the differences in WF persistence and design a small database to support this application scenario. If anyone reading this has questions or comments, all are welcome. :)


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

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