Mariano@Blog!

.NET and other interesting stuff.

November 2007 - Posts

Creating a Workflow Foundation Custom Activity with Activity Derivation

Activity derivation is one of the two methods to create Custom Activities. Unlike the composition (the another method), where with several basic activities we can create a new large and complex activity, on this method we focus on creating one single activity, defining their properties and execution model. Let's look at an example of an activity created by this method, wich will be aiming to display a message on the console.

To do that we must create a new project of Workflow Activity Library type. We will name it “WFActivityDerivation”. and add a new class called ActivityDerivationExample.cs, which the following code:

public class ActivityDerivationExample : Activity

{

    string _text;

 

    public string Text

    {

        get { return _text; }

        set { _text = value; }

    }

 

    protected override ActivityExecutionStatus Execute(

    ActivityExecutionContext executionContext)

    {

        Console.WriteLine(Text);

        Console.ReadKey();

        return ActivityExecutionStatus.Closed;

    }

}

The class inherits from System.Workflow.ComponentModel.Activity, which is the base of all activities in WF. Our example activity exposes a property called “Text” whose value can be set from the workflow designer and which will be displayed on the console when the activity run.

The main feature of this class is the Execute method. When we override this method we are assuming full responsability for the behavior of the activity. When the time comes to execute our custom activity, WF runtime will invoke our Execute method and then show our message on the console. After that we need to tell the WF runtime that our activity execution has ended, returning the value Closed of the ActivityExecutionStatus.

At this point we can build our activity and create the assembly for distribution.

 

Activity Designers:

The Activity Designers are used to control the appearance of the activities at design time. To create a designer we need a new class that inherits from ActivityDesigner and override their virtual methods. The following example shows how to override the method OnPaint, who draws our activity in the designer's workflow:

public class ActivityDerivationDesigner : ActivityDesigner

{

    ActivityDerivationExample _activity;

    protected override void Initialize(Activity activity)

    {

        _activity = activity as ActivityDerivationExample;

        base.Initialize(activity);

    }

 

    protected override void OnPaint(ActivityDesignerPaintEventArgs e)

    {

        e.Graphics.FillRectangle(Brushes.Black, Location.X, Location.Y, Size.Width, Size.Height);

        Rectangle rect = new Rectangle(Location.X, Location.Y, Size.Width, 15);

        Font font = new Font("Lucida Console", 8);

        e.Graphics.DrawString(@"C:\> " + _activity.Text, font, Brushes.White, rect.X, rect.Y + 10);

 

    }

}

The following syntax is used to associate the designer to our activity::

//ActivityDerivationExampe.cs

[Designer(typeof(ActivityDerivationDesigner))]

public class ActivityDerivationExample : Activity

{

    //

}

 

With this, our activity will looks like the following picture:

image

 

Activity Validators:

The Activity Validators run at design and build time, and are used to ensure that the activity has the correct settings to run. To create a validator need a new class that inherits from the class ActivityValidator and overwrite method Validate.
For example the following validator will ensure that our activity has a valid value in the Text property:

public class ActivityDerivationValidator : ActivityValidator

{

    public override ValidationErrorCollection Validate(ValidationManager manager, object obj)

    {

        ValidationErrorCollection errors = base.Validate(manager, obj);

        ActivityDerivationExample activity = obj as ActivityDerivationExample;

        if (activity.Parent != null && String.IsNullOrEmpty(activity.Text))

        {

            errors.Add(ValidationError.GetNotSetValidationError("Text"));

        }

        return errors;

    }

}

All validation errors are added to the ValidationErrorCollection collection and returned to the caller.

The following syntax is used to associate the validator to our activity: 

//ActivityDerivationExampe.cs

[ActivityValidator(typeof(ActivityDerivationValidator))]

public class ActivityDerivationExample : Activity

{

    //

}

 

Conclusion:

The Activity Derivation allow us to create new activities that inherits from the base class Activity. You can also inherits from other classes that implement Activity, to use more features. This method gives us the highest level of control and offers us a way to extend WF code itself.

Posted: Nov 02 2007, 04:09 PM by MarianoS | with 1 comment(s) |
Filed under:
Certificate Access Error in a IIS hosted WCF Service

The problem appears when a WCF Service hosted in an IIS tries to load a certificate from the Windows Certificates Store with the account of the Application Pool where the service runs, and the account’s profile is not previously loaded. When a user logs on interactively, the system automatically loads the user's profile. If a service or an application impersonates a user, the system does not load the user's profile. Therefore, the service or application should load the user's profile with LoadUserProfile.

When this happens the operation throws the following exception:

System.Security.Cryptography.CryptographicException: The system cannot find the file specified.

See http://support.microsoft.com/kb/939761 Microsoft Knowledge Base Article for detailed information.

A workaround to this problem is to load the Application Pool Identity Account´s profile before the service call is executed. Placing the code in the Application_Start() method on the Global.asax of the IIS host will solve the problem (see http://msdn2.microsoft.com/en-us/library/aa374341.aspx for detailed information).

Here is the code:

private ProfileManager.PROFILEINFO profile;

 

protected void Application_Start(object sender, EventArgs e)

{

    bool retVal = false;

    // Need to duplicate the token. LoadUserProfile needs a token with

    // TOKEN_IMPERSONATE and TOKEN_DUPLICATE.

    const int SecurityImpersonation = 2;

    dupeTokenHandle = DupeToken(WindowsIdentity.GetCurrent().Token, SecurityImpersonation);

 

    if (IntPtr.Zero == dupeTokenHandle)

    {

        throw new Exception("Unable to duplicate token.");

    }

 

    // Load the profile.

    profile = new ProfileManager.PROFILEINFO();

    profile.dwSize = 32;

    //Domain\User

    profile.lpUserName = @"MyDomain\UserName";

    retVal = ProfileManager.LoadUserProfile(dupeTokenHandle, ref profile);

 

    if (!retVal)

    {

        throw new Exception("Error loading user profile. " + Marshal.GetLastWin32Error());

    }

}

 

protected void Application_End(object sender, EventArgs e)

{

    ProfileManager.UnloadUserProfile(WindowsIdentity.GetCurrent().Token, profile.hProfile);

    CloseHandle(dupeTokenHandle);

}

 

private IntPtr DupeToken(IntPtr token, int Level)

{

    IntPtr dupeTokenHandle = new IntPtr(0);

    bool retVal = DuplicateToken(token, Level, ref dupeTokenHandle);

   

    if (false == retVal)

    {

        return IntPtr.Zero;

    }

 

    return dupeTokenHandle;

}

 

internal class ProfileManager

{

    [DllImport("Userenv.dll", SetLastError = true, CharSet = System.Runtime.InteropServices.CharSet.Auto)]

    internal static extern bool LoadUserProfile(IntPtr hToken, ref PROFILEINFO lpProfileInfo);

    [DllImport("Userenv.dll", SetLastError = true, CharSet = System.Runtime.InteropServices.CharSet.Auto)]

    internal static extern bool UnloadUserProfile(IntPtr hToken, IntPtr hProfile);

 

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]

    public struct PROFILEINFO

    {

        public int dwSize;

        public int dwFlags;

        public String lpUserName;

        public String lpProfilePath;

        public String lpDefaultPath;

        public String lpServerName;

        public String lpPolicyPath;

        public IntPtr hProfile;

    }

}

An alternative workaround consists in creating a Windows Service account that loads at system start-up using the Application Pool Service Identity.

Thanks to JavierA for helping me to find the solution.

Posted: Nov 01 2007, 05:15 PM by MarianoS | with no comments |
Filed under: ,
The beginning

Hi, my name is Mariano, I'm from Argentina and I´m 25.
I have programmed computers since I was 15 and I really enjoy it.
I'm in the computer business since year 2000 and now I'm working as developer for Lagash Systems S.A. mainly with Microsoft technology.

This is my first blog, so I hope you find it interesting!! (I will try to keep it updated!!)

More Posts