Saturday, March 21, 2009

Add a FitlerBuilder Control to an ActivitySettingsPart - Part 2

Now, before we finish wiring the FilterBuilder into the ActivitySettingsPart, there's something we have to do in the Activity itself. We have to create the property to which we'll save the filter that we built.

Open the Activity by right-clicking on the EnsynchDiagnosticActivity.cs file in the Solution Explorer, and select View Code. Add a dependency property called "BuilderFilter" to the class:

public static DependencyProperty BuilderFilterProperty =
DependencyProperty.Register("BuilderFilter", typeof(System.String),
typeof(EnsynchDiagnosticActivity));

[DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Visible)]
[BrowsableAttribute(true)]
[Category("Properties")]
public String BuilderFilter
{
get
{
return ((string)(base.GetValue(EnsynchDiagnosticActivity.BuilderFilterProperty)));
}
set
{
base.SetValue(EnsynchDiagnosticActivity.BuilderFilterProperty, value);
}
}

Okay, that's it for the Activity. You may be asking the screen, "Do we need to create a dependency property?" Well, no; but dependency properties help you unlock the power of Windows Workflow Foundation. The discussion is out of scope of this blog post, but I suggest an internet search for, "What is a dependency property?"

Now let's return to our ActivitySettingsPart (EnsynchDiagnosticActivityUI.cs). There are several override methods in which we have to include our FilterBuilder. Here is a summary of those methods.

Override MethodPurpose
GenerateActivityOnWorkflowThis is essentially the "Save" operation. When you click the Save button, the web application calls this method, expecting you to create an instance of your Activity and save all the input that the user has supplied into that Activity.
LoadActivitySettingsThe counterpart to the "Save" operation, which is essentially a "Load" operation. When you reopen the workflow in the portal, the web application passes the Activity data (which you saved in the Generate method) back to you, and expects you to determine how it's rendered.
PersistSettingsThis is ILM's version of the ASP.NET SaveViewState method, which is necessitated by the stateless nature of HTTP. You can think of it as a (required) method to temporarily store the user's input without committing it to a full-blown Activity.
RestoreSettingsThe counterpart to the Persist method. This is where you load the temporarily saved data.
SwitchModeThis method is called when the interface switches between "Edit" and "View" modes. In Edit mode, your web controls should be enabled and fully editable. In View mode, they should be on their read-only settings. The web application calls this method to, once again, allow you to determine how your interface should be rendered for each mode.


As you may have picked up on, there seems to be some overlap between the "Generate" and "Persist" methods, as well as the "Load" and "Restore" methods. In the former two, we have to extract the filter from the FilterBuilder, and in the latter two, we have to load the filter back into the FilterBuilder. As you'll see, these operations are more than just a one-liner, so we'll write a couple of helper methods to take care of them and avoid code repetition.

The two methods below are how we translate between the FilterBuilder control and the filter string that we save/load.

private string getBuilderFilter()
{
FilterType filter;
bool success = filterBuilder.TryGetFilter(
"http://schemas.microsoft.com/2006/11/XPathFilterDialect", out filter);
if (success)
{
return filter.Text;
}
return "";
}

private void setBuilderFilter(string value)
{
if (!string.IsNullOrEmpty(value))
{
FilterType filter = new FilterType();
filter.Dialect = "http://schemas.microsoft.com/2006/11/XPathFilterDialect";
filter.Text = value;
filterBuilder.SetFilter(filter);
}
}

If Visual Studio fails to resolve the FilterType class, you'll have to add its namespace to the beginning of the class:

using Microsoft.ResourceManagement.WebServices.WSEnumeration;

All right, now we can call our helper methods from the override methods. In the code samples below, I've left out most of the existing code, and just added the calls in the appropriate places.

public override Activity GenerateActivityOnWorkflow(SequentialWorkflow workflow)
{
...

activity.BuilderFilter = getBuilderFilter();
...
}

public override void LoadActivitySettings(Activity activity)
{
OwnerRollupActivity activity2 = activity as OwnerRollupActivity;
if (activity2 != null)
{
...

setBuilderFilter(activity2.BuilderFilter);
...
}
}

public override ActivitySettingsPartData PersistSettings()
{
ActivitySettingsPartData data = new ActivitySettingsPartData();
...

data["BuilderFilter"] = getBuilderFilter();
...
return data;
}

public override void RestoreSettings(ActivitySettingsPartData data)
{
if (data != null)
{
...

setBuilderFilter("" + data["BuilderFilter"]);
...
}
}

Okay, the last thing to do is to add the FilterBuilder to the SwitchMode method. However, before I do that, I can't help but do a little code cleanup here. Here's why: just by looking at the following line of code, can you tell me what it does?

SetControlAccess("txtActivityName", flag);

You may be able to guess that "control access" means that we're switching between edit and read-only modes, but what are we setting it to? What the heck does "flag" represent?

Let's start by renaming the SetControlAccess method. Actually, while we're at it, let's add a little more error checking as well.

private void setControlReadOnly(string controlID, Boolean readOnly)
{
Control target = this.FindControl(controlID);
if (target.GetType() == typeof(TextBox))
{
TextBox oText = (TextBox)target;
if (oText != null)
{
oText.ReadOnly = readOnly;
}
}
}

Ahhh, much better! Now, let's update the SwitchMode method with the new method name, rename the mysterious "flag" variable, and add our FilterBuilder to the mix.

public override void SwitchMode(ActivitySettingsPartMode mode)
{
bool readOnly = mode == ActivitySettingsPartMode.View;
setControlReadOnly("txtActivityName", readOnly);
setControlReadOnly("txtFilePath", readOnly);
setControlReadOnly("txtFileName", readOnly);
filterBuilder.ReadOnly = readOnly;
}

Hey, look at that! We've demystified our code, and now adding the FilterBuilder was a snap!

That's it for adding the FilterBuilder to the ActivitySettingsPart. Now you can build & deploy the project, and here are a few things that you can do to test in the ILM 2 portal:

  1. Create a new Workflow and add the modified Diagnostic Activity. Manipulate the FilterBuilder and click Save.
  2. Expand the activity again. Is the FilterBuilder read-only?
  3. Click the Edit button. Can you now edit the filter?
  4. Click Save again, and then Finish and Submit the workflow.
  5. Reopen the workflow and expand the activity. Is this the same filter you saved?

Another test that you can do to show off a cool feature of the FilterBuilder is to edit the activity, click Add Statement, and then try to Save. The FilterBuilder has its own error messages; very nice.

Well, that's it for now. To be honest, I've sort of left you hanging. You're probably wondering, "Wait a minute, how do you use the filter that we've saved in the activity?" The short answer is: you have to use a web service client to ask ILM to resolve the filter. Now, don't panic, Joe Schulman has released a sample client on MSDN that you may be able to incorporate.

Still a bit worried? Well, I have good news for you! As Brad Turner mentions in his blog, we will be releasing the source code for a complete custom activity library, which includes the use of the Public Resource Mangement Client, as well as several custom activities, one of which uses the FilterBuilder in a clever way. In fact, Brad is at TEC 2009 right now, and he'll be instructing a lab that uses the custom activities. And if you're signed up for his workshop, I'm pretty sure he'll let you take the source code with you if you can't wait for us to post it.

Enjoy!

No comments:

Post a Comment