The Problem Solver

Tell me and I will forget
Show me and I will remember
Involve me and I will understand
- Confucius -

Google Ads

This Blog

Syndication

Search

Tags

News





  • View Maurice De Beijer's profile on LinkedIn

Community

Email Notifications

Explore

Archives

March 2008 - Posts

The new ReceiveActivity and SendActivity that marry Windows Workflow Foundation (WF) and Windows Communication Foundation (WCF) are really cool Smile. 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 Sad. 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 Sad. 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

Posted by Maurice | 3 comment(s)
Filed under: , , , , , ,

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

Posted by Maurice | 9 comment(s)
Filed under: , , , ,

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

Posted by Maurice | 1 comment(s)
Filed under: , ,

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

Posted by Maurice | with no comments
Filed under: , , , ,

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

Posted by Maurice | 2 comment(s)
Filed under: , , , ,

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

 

Posted by Maurice | 3 comment(s)
Filed under: , , , ,

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

Posted by Maurice | 4 comment(s)
Filed under: , , ,

Posted by Maurice | with no comments

I guess MIX-08 was a pretty cool event Smile Unfortunately all I can do was guess because I wasn't there Sad.

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 Smile

Enjoy!

www.TheProblemSolver.nl
http://wiki.WindowsWorkflowFoundation.eu

Posted by Maurice | 1 comment(s)
Filed under: , , ,

Op 6 maart heb ik een presentatie geven bij de Nationale Office Dag in Ede. Hier zijn de slides en voorbeelden te downloaden.

Enjoy!

www.TheProblemSolver.nl
http://wiki.WindowsWorkflowFoundation.eu

 

Posted by Maurice | with no comments
Filed under: , , ,

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

 

Posted by Maurice | with no comments
Filed under: ,

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 Smile

 

Enjoy!

www.TheProblemSolver.nl
http://wiki.WindowsWorkflowFoundation.eu

Posted by Maurice | 12 comment(s)
Filed under: , ,

I am proud to announce that I have been added to the European INETA Speakers Bureau Smile

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 Smile

www.TheProblemSolver.nl
http://wiki.WindowsWorkflowFoundation.eu

Posted by Maurice | 1 comment(s)
Filed under: , , , ,

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 Smile

 

Enjoy LINQ to SQL!

www.TheProblemSolver.nl

http://wiki.WindowsWorkflowFoundation.eu

Posted by Maurice | with no comments
Filed under: , , ,

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 Smile

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

Posted by Maurice | with no comments