March 2008 - Posts
The new ReceiveActivity and SendActivity that marry Windows Workflow Foundation (WF) and Windows Communication Foundation (WCF) are really cool
. Getting started is easy because a new Sequential Workflow Service Library, found under WCF instead of Workflow in VS2008, uses nice defaults for everything. But sooner or later you need to change these defaults and you need to know what can be done and what can't.
When you want to use the new ReceiveActivity in a workflow you need to use a compatible WCF binding. The reason for this requirement is that the conversation context, see this blog post, is part of the message and needs to be retrieved and returned. The following code returns a list of all WCF binding and how they are composed:
Sub Main()
Dim assemblies As
New List(Of Assembly)()
' .NET 3.0
assemblies.Add(GetType(ServiceHost).Assembly)
' .NET 3.5
assemblies.Add(GetType(WorkflowServiceHost).Assembly)
assemblies.Add(GetType(WebServiceHost).Assembly)
Dim query = From assembly In assemblies _
From type In assembly.GetTypes() _
Where type.IsSubclassOf(GetType(Binding)) _
AndAlso
Not type.IsAbstract _
AndAlso type.IsPublic _
Order
By type.Name _
Select type
PrintBinding(query.ToList)
Console.ReadLine()
End
Sub
Private
Sub PrintBinding(ByVal types As List(Of Type))
For
Each type In types
Console.WriteLine(type.FullName)
Try
Dim binding As Binding = _
CType(Activator.CreateInstance(type), Binding)
Dim elements = binding.CreateBindingElements
For
Each element In elements
Console.WriteLine(vbTab + element.GetType().FullName)
Next
Catch ex As Exception
Console.WriteLine(ex.Message)
End
Try
Console.WriteLine()
Next
End
Sub
The classes responsible for inserting and removing these conversation tokens are ContextRequestChannel and ContextReplyChannel and they are instantiated by the ContextBindingElement. So seems we are restricted to using BasicHttpContextBinding, NetTcpContextBinding or WSHttpContextBinding.
So it seems we cannot use NetMsmqBinding which is a shame because one way reliable messaging is the perfect fit for workflow as far as I am concerned. Well not quite so fast because we still have the CustomBinding where we can configure the stack just the way we want right?
Yeah we do but there is a problem
. It turns out the ContextBinding requires a channel with an IReplyChannel interface and the NetMsmqBinding actually implement an IInputChannel or an IOutputChannel. Which one actually depends if you are the client or the service.
And thinking about how WF/WCF conversations works this restriction makes sense. After all a ReceiveActivity is called without a context in order to create a new workflow, assuming the CanCreateInstance property equals true, and returns the workflow instanceId in the context as part of the response. This design kind of rules out one-way messages and thereby NetMsmqBinding.
Now this sucks big time if you ask me
. I would much rather have seen that you could specify the instanceId of the workflow to be created, just as you can with the WorkflowRuntime.CreateWorkflow() where a number of the overloads let you specify the workflows instanceId. I suppose it is possible to create a different context binding but that would be quite some work and, I assume, duplicate a lot of code already written my Microsoft. So let's hope they see the light and add MSMQ/ReceiveActivity intergration.
Enjoy!
www.TheProblemSolver.nl
http://wiki.WindowsWorkflowFoundation.eu
In a previous blog post I showed how to use the ReceiveActivity with long running workflow and how to extract the workflow instanceId from the context using the IContextManager. I also showed how to rebuild this context at a later date when you use a new proxy object to call the same workflow instance.
But what happens when there are multiple ReceiveActivity instances waiting for the same request in a workflow. In the workflow below both indicated activities are in a parallel activity so they are waiting at the same time and for the same request.
The way to keep them apart is by specifying the ContextToken for the two ReceiveActivity objects. The picture below shows the ReceiveActivity to the right, the one to the left has a ContextToken named LeftBranch.
Now we know how to keep them apart in the workflow we still need a way to specify this at the clients end. This is done using a conversationId setting on the same context as we specified the instanceId. The only problem here is that this conversationId is a Guid and not something the client can determine by itself so the workflow needs to supply it somehow. For this example I just kept things simple and returned the conversationId from the first ReceiveActivity call. This is the code in the CodeActivity that does just that:
private
void codeActivity1_ExecuteCode(object sender, EventArgs e)
{
IDictionary<string, string> context = RightReceiveActivity.Context;
string temp;
if (context.TryGetValue("conversationId", out temp))
ReturnValue = temp;
}
All I need to do is het the Context from the ReceiveActivity I am interested in, the right one in this case, and retrieve the conversationId from it. Remember the context is an IDictionary<string, string> and it will also contain the instanceId for the workflow. In fact I could have just returned this context to the client for future use but as I was using the simple standard interface generated and the client already knew the workflow instanceId I decided to skip this step.
The client code to set the conversationId is simple:
Workflow1Client proxy = new
Workflow1Client();
IContextManager contextManager = proxy.InnerChannel.GetProperty<IContextManager>();
if (!string.IsNullOrEmpty(_conversationId))
_context.Add("conversationId", _conversationId);
contextManager.SetContext(_context);
DumpContext(proxy);
Console.WriteLine(proxy.GetData(2));
Where the _context field points to the context used when creating the workflow and the conversationId is returned from the first GetData() call.
Enjoy!
www.TheProblemSolver.nl
http://wiki.WindowsWorkflowFoundation.eu
Sometimes you need to the complete public key of an assembly instead of the public key token. For example when you want to use the InternalsVisibleTo attribute when the target assembly is signed. The easiest way of doing so is using the Strong Name Tool or sn.exe with the –Tp parameter. So from .NET 2.0 command prompt do:
sn -Tp <<your assembly>>
And if you want to add an Open .NET 2.0 SDK Command Prompt here shortcut menu option to the Windows Explorer check this tip.
Enjoy!
www.TheProblemSolver.nl
http://wiki.WindowsWorkflowFoundation.eu
In my last post I showed how to self host the workflow runtime and still be able to use a Windows Communication Foundation and ReceiveActivity.
Hosting the workflow runtime in a WorkflowServiceHost is nice but in all likelihood you will also need to configure the workflow runtime itself and add some WorkflowRuntimeService to it. So how to do this when you never actually create the workflow runtime yourself?
There are two possible ways to go about this depending if you prefer to use code or a configuration file. Lets first look at the code option as it is easier to see all the working parts. In the case of a WCF hosted workflow runtime the WorkflowRuntimeBehavior is the WCF ServiceBehavior that is actually responsible for creating the WorkflowRuntime. Fortunately getting a reference to it, and thereby the WorkflowRuntime, isn't hard.
// Create the host
WorkflowServiceHost host = new
WorkflowServiceHost(typeof(Workflow1));
// Retreive the WF behavior
WorkflowRuntimeBehavior workflowRuntimeBehaviour =
host.Description.Behaviors.Find<WorkflowRuntimeBehavior>();
// Retreive the WF runtime
WorkflowRuntime workflowRuntime = workflowRuntimeBehaviour.WorkflowRuntime;
// Create and add the SqlWorkflowPersistenceService
SqlWorkflowPersistenceService persistenceService =
new
SqlWorkflowPersistenceService(connectionString);
workflowRuntime.AddService(persistenceService);
// Start listening
host.Open();
Doing the same in a configuration file isn't hard either. The main problem is that a app.config isn't compiled so it's a bit easier to make a typo and not catch it until runtime.
<?xml
version="1.0"
encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<!-- Details ommited -->
<behaviors>
<serviceBehaviors>
<behavior
name="WorkflowConsoleApplication1.Workflow1Behavior" >
<!-- Details ommited -->
<workflowRuntime
name="WorkflowServiceHostRuntime"
validateOnCreate="true"
enablePerformanceCounters="true">
<services>
<add
type="System.Workflow.Runtime.Hosting.SqlWorkflowPersistenceService, System.Workflow.Runtime, Version=3.0.00000.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
connectionString="Data Source=localhost\sqlexpress;Initial Catalog=WorkflowPersistence;Integrated Security=True;Pooling=False"
LoadIntervalSeconds="1"
UnLoadOnIdle= "true" />
</services>
</workflowRuntime>
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
</configuration>
This configuration file is basically the same as in the previous post except that contains a workflowRuntime element inside of the service behavior. This workflowRuntime contains the services collection where you can add any services you need.
Enjoy!
www.TheProblemSolver.nl
http://wiki.WindowsWorkflowFoundation.eu
One of the new, and pretty cool, Windows Workflow Foundation features is the ReceiveActivity that unleashes the power of Windows Communication Foundation to Windows Workflow Foundation. Getting started with a ReceiveActivity is quite simple as long as you start with a sequential Workflow Service Library.
The new service host for Windows Communication Foundation services makes life good as it means you can test a workflow without creating a host application or resorting to IIS.
But sometimes you just want to host the workflow runtime yourself and still use the ReceiveActivity. So how to go about and do that?
For a normal WCF host you would use an instance of ServiceHost but with a ReceiveActivity that isn't quite going to cut it as the host needs some awareness of WF and ServiceHost is very generic. So instead add a reference to System.WorkflowServices and create an instance of WorkflowServiceHost. The syntax is the same so no surprises there:
WorkflowServiceHost host = new
WorkflowServiceHost(typeof(Workflow1));
host.Open();
Console.WriteLine("Press enter to stop.");
Console.ReadLine();
The app.config contains the runtime configuration but all of it is pretty standard WCF stuff so no surprises:
<?xml
version="1.0"
encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<services>
<service
name="WorkflowConsoleApplication1.Workflow2"
behaviorConfiguration="WorkflowConsoleApplication1.Workflow2Behavior">
<host>
<baseAddresses>
<add
baseAddress="http://localhost:8731/Design_Time_Addresses/WorkflowConsoleApplication1/Workflow2/" />
</baseAddresses>
</host>
<endpoint
address=""
binding="wsHttpContextBinding"
contract="WorkflowConsoleApplication1.IMyContract">
<identity>
<dns
value="localhost"/>
</identity>
</endpoint>
<endpoint
address="mex"
binding="mexHttpBinding"
contract="IMetadataExchange" />
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior
name="WorkflowConsoleApplication1.Workflow2Behavior" >
<serviceMetadata
httpGetEnabled="true" />
<serviceDebug
includeExceptionDetailInFaults="false" />
<serviceCredentials>
<windowsAuthentication
allowAnonymousLogons="false"
includeWindowsGroups="true" />
</serviceCredentials>
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
</configuration>
Enjoy!
www.TheProblemSolver.nl
http://wiki.WindowsWorkflowFoundation.eu
The first of a two part article I wrote on developing custom workflow activities just appeared on the MSDN Visual Basic Developer Center. You can find the article named "The Power of Custom Workflow Activities"
here. A second article with more details is scheduled to appear here as well.

For everyone new to Workflow Foundation I would suggest reading some intro level material first. For example my "Workflow Foundation 101" article or "Hello, Workflow" by K. Scott Allen.
Enjoy!
www.TheProblemSolver.nl
http://wiki.WindowsWorkflowFoundation.eu
The team building WF is looking for some input on what to do for the future of Workflow Foundation. To find out what you want they created a Windows Workflow and Rules Designer Rehosting Survey. Go ahead and fill it out over here: https://live.datstat.com/MSCSD-Collector/Survey.ashx?Name=WF_Rules_Designer_Rehosting_Blogs
Enjoy!
www.TheProblemSolver.nl
http://wiki.WindowsWorkflowFoundation.eu

I guess MIX-08 was a pretty cool event
Unfortunately all I can do was guess because I wasn't there
.
So I guess I missed a lot of good sessions, right? Well wrong actually because the team that run MIX have already put the sessions up on the web! So go to http://sessions.visitmix.com/Default.htm and view all the sessions you want!
I just watched Brad Abrams
session about Ajax and VS2008 and its excellent!
And best of all: they are also published in Zune and IPod format so you can take them on the road
Enjoy!
www.TheProblemSolver.nl
http://wiki.WindowsWorkflowFoundation.eu
Op vrijdag 28 maart organiseert Software Developer Network (SDN) in samenwerking met de .NET gebruikersgroep (dotNed) en VBcentral de community launch van Visual Studio 2008, SQL Server 2008 en Windows Server 2008. Dit evenement loopt parallel aan een reguliere Software Developer Event. Hierdoor zijn de drie Nederlandse gebruikersgroepen in staat om jou een indrukwekkend programma voor te schotelen. Deze dag bestaat uit ruim vijfentwintig sessies, verdeeld over maar liefst zeven paralleltracks. Door het brede aanbod van onderwerpen en de aanwezigheid van veel bekende en gerenommeerde sprekers, mag je dit unieke evenement niet missen!
Alle leden van de drie Nederlandse communities kunnen zich aanmelden en hebben toegang tot alle sessies van die dag. De afsluitende Algemene Ledenvergadering van de SDN is alleen toegankelijk voor SDN leden. SDN leden kunnen zich aanmelden via de gebruikelijke weg en niet SDN leden via de website van Microsoft Nederland. Het evenement vindt plaats in De Reehorst, Ede en de kosten voor niet betalende SDN leden bedragen € 50,00 inclusief BTW.
Bezoek voor meer informatie en het volledige programma de website van de SDN
By default Edit and Continue (EnC) is disabled when you create a new ASP.NET Web Application using Visual Studio 2005. Not sure why this is because EnC is a really nice feature to have and can be a big time saver.
You will notice this when you try to edit the source while debugging and receive the following error message:
Edit and Continue
Changes are not allowed when the debugger has been attached to an already running process or the code being debugged was optimized at build or run time.
Fixing it is easy. Just double click on "My Project", select the Web tab and check the "Enable Edit and Continue" check. Next time when you run you are all set for EnC
Enjoy!
www.TheProblemSolver.nl
http://wiki.WindowsWorkflowFoundation.eu
I am proud to announce that I have been added to the European INETA Speakers Bureau
I am really passionate about Workflow Foundation and like to speak about that as well as other .NET and architecture related subjects. So if you are a user group leader and INETA member and would like to have me speak then feel free to contact me or Andre who runs the INETA speakers bureau.
You can find me here on the list.
Hope to meet you at some conference
www.TheProblemSolver.nl
http://wiki.WindowsWorkflowFoundation.eu
The default behavior with LINQ to SQL is to use deferred loading and works just great most of the time. But sometimes you might just want to load all related data at the same time because you know you are going to need it anyway and it saves a number of round trips to the database.
Using the LINQ DataContext this is quite easy to do. In fact all you need to do is configure a DataLoadOptions object and assign it to the LoadOptions property of the data context. The code looks something like this:
Dim context As
New AdventureWorksDataContext
Dim loadOptions As
New DataLoadOptions
loadOptions.LoadWith( _
Function(cust As Customer) cust.CustomerAddresses)
loadOptions.LoadWith( _
Function(custAddress As CustomerAddress) custAddress.Address)
context.LoadOptions = loadOptions
context.Log = Console.Out
Dim query = From cust In context.Customers _
Where cust.CompanyName.Contains("bike") _
Select cust
CustomerBindingSource.DataSource = query
BTW setting the DataContext Log property Console.Out means you can watch the SQL queries in the Output window of Visual Studio.
Speaking about SQL queries the load options used here actually reduce the three entity collections used in the form, Customer, CustomerAddress and Address, just need a single query to load. This is the query generated:
SELECT [t0].[CustomerID], [t0].[NameStyle], [t0].[Title], [t0].[FirstName], [t0].[MiddleName], [t0].[LastName], [t0].[Suffix], [t0].[CompanyName], [t0].[SalesPerson], [t0].[EmailAddress], [t0].[Phone], [t0].[PasswordHash], [t0].[PasswordSalt], [t0].[rowguid], [t0].[ModifiedDate], [t1].[CustomerID] AS [CustomerID2], [t1].[AddressID], [t1].[AddressType], [t1].[rowguid] AS [rowguid2], [t1].[ModifiedDate] AS [ModifiedDate2], [t2].[AddressID] AS [AddressID2], [t2].[AddressLine1], [t2].[AddressLine2], [t2].[City], [t2].[StateProvince], [t2].[CountryRegion], [t2].[PostalCode], [t2].[rowguid] AS [rowguid3], [t2].[ModifiedDate] AS [ModifiedDate3], (
SELECT COUNT(*)
FROM [SalesLT].[CustomerAddress] AS [t3]
INNER JOIN [SalesLT].[Address] AS [t4] ON [t4].[AddressID] = [t3].[AddressID]
WHERE [t3].[CustomerID] = [t0].[CustomerID]
) AS [value]
FROM [SalesLT].[Customer] AS [t0]
LEFT OUTER JOIN ([SalesLT].[CustomerAddress] AS [t1]
INNER JOIN [SalesLT].[Address] AS [t2] ON [t2].[AddressID] = [t1].[AddressID]) ON [t1].[CustomerID] = [t0].[CustomerID]
WHERE [t0].[CompanyName] LIKE @p0
ORDER BY [t0].[CustomerID], [t1].[AddressID]
-- @p0: Input NVarChar (Size = 6; Prec = 0; Scale = 0) [%bike%]
-- Context: SqlProvider(Sql2005) Model: AttributedMetaModel Build: 3.5.21022.8
Compare that to the original three separate queries and don't forget that the second and third are executed for each customer:
SELECT [t0].[CustomerID], [t0].[NameStyle], [t0].[Title], [t0].[FirstName], [t0].[MiddleName], [t0].[LastName], [t0].[Suffix], [t0].[CompanyName], [t0].[SalesPerson], [t0].[EmailAddress], [t0].[Phone], [t0].[PasswordHash], [t0].[PasswordSalt], [t0].[rowguid], [t0].[ModifiedDate]
FROM [SalesLT].[Customer] AS [t0]
WHERE [t0].[CompanyName] LIKE @p0
-- @p0: Input NVarChar (Size = 6; Prec = 0; Scale = 0) [%bike%]
-- Context: SqlProvider(Sql2005) Model: AttributedMetaModel Build: 3.5.21022.8
'WindowsApplication1.vshost.exe' (Managed): Loaded 'Anonymously Hosted DynamicMethods Assembly'
SELECT [t0].[CustomerID], [t0].[AddressID], [t0].[AddressType], [t0].[rowguid], [t0].[ModifiedDate]
FROM [SalesLT].[CustomerAddress] AS [t0]
WHERE [t0].[CustomerID] = @p0
-- @p0: Input Int (Size = 0; Prec = 0; Scale = 0) [1]
-- Context: SqlProvider(Sql2005) Model: AttributedMetaModel Build: 3.5.21022.8
SELECT [t0].[AddressID], [t0].[AddressLine1], [t0].[AddressLine2], [t0].[City], [t0].[StateProvince], [t0].[CountryRegion], [t0].[PostalCode], [t0].[rowguid], [t0].[ModifiedDate]
FROM [SalesLT].[Address] AS [t0]
WHERE [t0].[AddressID] = @p0
-- @p0: Input Int (Size = 0; Prec = 0; Scale = 0) [832]
-- Context: SqlProvider(Sql2005) Model: AttributedMetaModel Build: 3.5.21022.8
One gotcha to keep in mind is that you need to configure the DataLoadOptions before assigning them. If you try the following code:
Dim context As
New AdventureWorksDataContext
context.LoadOptions = New DataLoadOptions
context.LoadOptions.LoadWith( _
Function(cust As Customer) cust.CustomerAddresses)
context.LoadOptions.LoadWith( _
Function(custAddress As CustomerAddress) custAddress.Address)
All you get will be an InvalidOperationException with the following message: "LoadWith is not allowed after freeze or attach to DataContext.". Fortunately the error message is clear enough
Enjoy LINQ to SQL!
www.TheProblemSolver.nl
http://wiki.WindowsWorkflowFoundation.eu
Next march 28 we are hosting another SDE event in Ede, the Netherlands. There is an impressive list of speakers and sessions, check it out here. I will be doing a session about the new Workflow Foundation features in the .NET framework 3.5. These new features, also known as Silver, enable workflows to work together with Windows Communication Foundation (WCF) in a real nice and intuitive manner
And just to get a taste here is a screen cast made during the last SDE held in December. The screen cast is in Dutch and for SDN members only!
Enjoy