TLD49

I've been able to successfully utilize side-by-side versioning with state-based workflows, in that WF 1.0 persisted instances rehyrdate using 1.0 WF, while new ones hyrdate, persist and complete with current version (e.g. 2.0). All assemblies are strong-named and are versioned, and works either with the GAC or the application's config runtime settings (e.g. codebase) for assembly resolving.

However, whenever a modification is made to a WF message contract (interfaces decorated with ExternalDataExchange attribute and eventargs inheriting from ExternalDataEventArgs), those WF instances that were created prior to the modifications fail to rehydrate.

My assemblies are organized as follows: (1) Workflow runtime and templates/logic; (2) Workflow activities, and (3) Workflow message contracts (e.g. ExternalDataExchange interfaces, eventargs). The chain of reference is that (1) has reference to (2), which in turn has a reference to (3). Again, assemblies are either in GAC or codebase-loaded, so Copy Local is false ("soft" reference).

When modifications are made to the first two assemblies (runtime and/or activities), persisted WF instances properly rehydrate to their original versions (1.0 -> 1.0, 2.0 -> 2.0, etc). Only modifying my third assembly (ExternalDataExchange interfaces/eventargs) causes rehydration to fail in all persisted instances except the latest version. I confirmed with fuslogvw that all assembly versions are being loaded, using either the GAC or codebase in local app's config runtime. The exception that is thrown is:

"Event Queue operations failed with MessageQueueErrorCode QueueNotFound for queue 'Message properties.." which then indicates one of my ExternalDataExchange interfaces being utilized during rehydration of older WF instance.

Any ideas on why rehydration would fail for older instances in this scenario I need to be able to modify the message contracts (e.g. eventargs) without losing ability to rehydrate previously persisted WF instances.



Re: Windows Workflow Foundation Rehydration of workflows after modifying ExternalDataExchange interfaces/eventargs

TLD49

While brainstorming with another developer, we discovered that it may be the type that is being added to the ExternalDataExchangeService is from the newest assembly only. For example...

ExternalDataExchangeService dataService = wfRuntime.GetService<ExternalDataExchangeService>();

dataService.AddService(obj); // where obj is an ExternalDataExchange-decorated type

The obj being added to the dataservice implemented an interface decorated with ExternalDataExchange, which is part of the separate assembly I mentioned in my last post. The issue is that the type being added comes from the most recent assembly (say 2.0), so therefore it cannot rehydrate non-2.0 instances (e.g. 1.x). What I need to do is find a way to add the same type from older-versioned assemblies. As all previous versioned assemblies are being resolved, this should hopefully make things a bit easier to find and add the older types.. Any further ideas are appreciated.

UPDATE: ensuring that the implemented ExternalDataExchange services are added (both 1.x and 2.0) did not work. Still cannot re-hydrate older workflows persisted prior to changes in interfaces/eventargs.





Re: Windows Workflow Foundation Rehydration of workflows after modifying ExternalDataExchange interfaces/eventargs

Vishal Kalra - MSFT

If your problem is not resolved, could you share a minimal repro application of the scenario with us






Re: Windows Workflow Foundation Rehydration of workflows after modifying ExternalDataExchange interfaces/eventargs

TLD49

It would be easier to email you a simple solution to try out. However, here's how it was setup:

1. The message contract assembly has an ExternalDataExchange interface and eventargs.

Code Snippet

[Serializable]

public class TestProcessingEventArgs : ExternalDataEventArgs

{

private string _testValue;

public string TestValue

{

get { return _testValue; }

set { _testValue = value; }

}

public TestProcessingEventArgs(Guid instanceId, string testValue) : base(instanceId)

{

_testValue = testValue;

}

}

Code Snippet

[ExternalDataExchange]

public interface ITestMediator

{

event EventHandler<TestProcessingEventArgs> TestApproved;

}

2. Another assembly has the Workflow activities. This assembly has a soft reference to the message contract assembly above.

3. Separate assembly with the state-based Workflow template and runtime, with reference to the above two assemblies. This workflow has three states (Initial, Middle, and Completed), running in that order. The event is being handled in the middle state. Note that the implemented ITestMediator class is located within this assembly and its is registered per the previous post (ExternalDataExchangeService.AddService()).

4. Web application project that acts as the host. (It can be a console app also). Reference to the Workflow assembly above.

Again, I can send you a compressed solution to take a look at. Otherwise, I can add more detail here but it'll be a long post.





Re: Windows Workflow Foundation Rehydration of workflows after modifying ExternalDataExchange interfaces/eventargs

TLD49

From lack of response, I assume that either this scenario is very rare, makes no sense at all, or that the questions are too abstract. I'll try to be more specific.

As mentioned earlier in this thread, we register our "mediators" (e.g. instances representing implementation of an ExternalDataExchange-decorated interfaces) by adding each one to the ExternalDataExchangeService (EDS) instance that we retrieved from the WF Runtime. This is done right after instantiating and starting a new workflowruntime. Here's the code sample again:

Code Snippet

// "runtime" represents a WorkflowRuntime that has been started

ExternalDataExchangeService dataSvc = runtime.GetService<ExternalDataExchangeService>();

dataSvc.AddService(obj); // where "obj" represents instance of a mediator

We already learned that the latest version of workflowruntime is used, and when rehydrating older passivated WF instances, the runtime inherently utilizes the appropriate WF version to rehydrate as long as no changes were made to EDS interfaces/eventargs. However, when adding our mediators to the EDS using addService, in reality its simply adding each mediator to the runtime itself. Found this code snippet in ExternalDataExchangeService.AddService (thru Reflector):

Code Snippet
base.Runtime.AddService(service);

This ends up calling WorkflowRuntime.AddServiceImpl(service) method, which appears to ensure that the same type isn't added twice (i.e. cannot add two instances of the same service).

Code Snippet
if (this.GetAllServices(service.GetType()).Contains(service))
{
throw new
InvalidOperationException(ExecutionStringManager.CantAddServiceTwice);
}
if (this.
_startedServices && this.IsCoreService(service))
{
throw new
InvalidOperationException(ExecutionStringManager.CantChangeImmutableContainer);
}
Type key = service.GetType();
foreach (
Type type2 in key.GetInterfaces())
{
List<object> list;
if (this.
_services.ContainsKey(type2))
{
list = this._services[type2];
}
else
{
list = new List<object>();
this.
_services.Add(type2, list);
}
list.Add(service);
}
while (
key != null)
{
List<object> list2 = null;
if (this.
_services.ContainsKey(key))
{
list2 = this._services[key];
}
else
{
list2 = new List<object>();
this.
_services.Add(key, list2);
}
list2.Add(service);
key = key.BaseType;
}

With the way AddServiceImpl was structured with hierarchial lists, it was possible to have multiple VERSIONS of the same service/type. So I simply looped through older versions of the WF assembly with my mediators and adding them to the EDS. Compiled fine, but still failed to hydrate older WF instances.

Upon further investigation, we discovered that GetService(serviceType)in WorkflowRuntime fails if you have more than one type of the service you're requesting. Therefore, if there are multiple versions of the same mediator added, retrieving the service fails.

public object GetService(Type serviceType)
{
  if (serviceType == null)
  {
    throw new ArgumentNullException("serviceType");
  }
  this.VerifyInternalState();
  lock (this._servicesLock)
  {
    object obj2 = null;
    if (this._services.ContainsKey(serviceType))
    {
      List<object> list = this._services[serviceType];
      if (list.Count > 1)
      {
        throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, 
ExecutionStringManager.MoreThanOneService, new object[] { serviceType.ToString() })); } if (list.Count == 1) { obj2 = list[0]; } } return obj2; } }
Therefore, we theorized that only the latest version (or single version) of each mediator is being used - regardless of whether all assembly versions are loaded and resolved in the domain. However, I may be completely incorrect or going in the wrong direction. Please feel free to point me the right way - I just want to be able to make changes to EDS interface/eventargs AND still rehydrate older WF instances.






Re: Windows Workflow Foundation Rehydration of workflows after modifying ExternalDataExchange interfaces/eventargs

Ruurd Boeke

I'm very sorry to bump such an old thread, but we are facing the same problem as you. It seems like a very reasonable thing to do, so if there is something of a solution, we would like to know about it!

Basically, we can instantiate old instances, but they will not fetch our objects from the queue (we use classes, strong signed), and will not communicate to the runtime, because no correctly typed EDS was provided.

Hitting a brick wall here, please help! ;-)






Re: Windows Workflow Foundation Rehydration of workflows after modifying ExternalDataExchange interfaces/eventargs

Vishal Kalra - MSFT

Sorry for dropping the ball on this one. Can you please log this issue in https://connect.microsoft.com/wf






Re: Windows Workflow Foundation Rehydration of workflows after modifying ExternalDataExchange interfaces/eventargs

Ruurd Boeke

I do not know if TLD is actively monitoring this thread, so I have gone ahead and logged the issue.

It is here:

https://connect.microsoft.com/wf/feedback/ViewFeedback.aspx FeedbackID=296226

It is turning into a showstopper for our architecture.

Please everybody vote on it ! ;-)






Re: Windows Workflow Foundation Rehydration of workflows after modifying ExternalDataExchange interfaces/eventargs

John Newell

I also am running into this issue.

From what I am seeing you cannot have multiple versions of a local workflow service loaded in the runtime at 1 time. More at http://forums.microsoft.com/MSDN/ShowPost.aspx PostID=1893863&SiteID=1.

When you say "vote on it"...what do you mean I will gladly vote that this is a bug and a showstopper...just not sure how to, or about how to use "connect". I went to your connect link...just didn't see a way to "vote".





Re: Windows Workflow Foundation Rehydration of workflows after modifying ExternalDataExchange interfaces/eventargs

Ruurd Boeke

I've logged the issue in connect, but they closed it. Their comments show that they haven't really read the issue (explaining side-by-side execution and giving a link to versioning.. duh).

So I guess I did not present the issue well enough, or they just do not have an answer!

We'll have to figure out how to work around this ourselves, it seems.






Re: Windows Workflow Foundation Rehydration of workflows after modifying ExternalDataExchange interfaces/eventargs

TLD49

It's been four months since I've been on this project (it was put on hold), and just happen to be browsing old WF forums for first time in a while. Yes, I did find a hacked-together solution - not pretty but it works. Here's a compilaton of my notes and what I could recall from memory. Time permitting, I'll see if i can get something out a bit more coherent than below.

Here¡¯s how our WF/application structure was built using the following separate assemblies:

  • WF Runtime
  • WF State-based Workflows (Templates, Activities, implemented ExternalDataExchange classes)
  • WF Interface Assembly (interfaces and eventargs built off ExternalDataExchange model)

Then, for testing, we have a facade service layer and an ¡°dumb¡± application to kick off events that move the state-based workflow forward:

  • Web Service Layer (simple facade between application and WF runtime for testing)
  • Web Application

We actually found a solution, due in part to how one would register services with the ExternalDataExchangeService (EDS). How I obtain the EDS instance is straight off a WorkflowRuntime instance in the WF Runtime assembly:

ExternalDataExchangeService dataSvc = runtime.GetService<ExternalDataExchangeService>();

When registering a service to the EDS through AddService(obj) method, it will inherently use the latest version referenced when you last compiled. So, let¡¯s say I have an existing WF instance passivated with version 1.0 in all assemblies. Then later I make changes to WF Interface Assembly (e.g. add a new eventhandler to my interface and create a new ExternalDataEventArgs), version it as 2.0, then apply the change in my WF Workflows assembly (e.g. add a new state and HandleExternalEvent with my new eventhandler and eventargs) and version it to 2.0 as well. These are deployed successfully.

Now let¡¯s say the runtime is restarted using the latest 2.0 version. Assume that all 1.0 and 2.0 assemblies are resolved into the domain (through GAC or config runtime codebase). New 2.0 workflows start, resume and complete without any problems. However, you need the passivated 1.0 workflow instance to rehydrate and resume. Based on what step the workflow was in, we resume it by getting the appropriate previously-registered ExternalDataExchange service we added through AddService(obj) earlier then firing the appropriate event.

While adding services to EDS allows for multiple types (or multiple version of the ¡°same¡± type), getting that service (by design) allows one and only one type arbitrarily, per my earlier post on GetService(type).

So I just ended up forcing-adding services from all previous versions of interface assembly/runtime assembly to the EDS (by assembly/reflection), then created our own ¡°custom¡± WorkflowRuntime and used method-hiding on GetService(type) methods to allow it to loop through and find the matching type/version.

Code Snippet

public class CustomWFRuntime : WorkflowRuntime

{

private object servicesLock;

private Dictionary<Type, List<object>> services;

public CustomWFRuntime(string section)

: base(section)

{

servicesLock = new object();

}

public Dictionary<Type, List<object>> ServicesDictionary

{

get

{

return services;

}

}

// method-hiding for base WorkflowRuntime's method

public new T GetService<T>()

{

System.Diagnostics.Trace.WriteLine("CustomWFRuntime GetService<T> called.");

return (T)this.GetService(typeof(T));

}

// method-hiding for base WorkflowRuntime's method

public new object GetService(Type serviceType)

{

System.Diagnostics.Trace.WriteLine("CustomWFRuntime GetService(Type) called.");

FieldInfo fi = typeof(WorkflowRuntime).GetField("_services", BindingFlags.Instance | BindingFlags.NonPublic);

services = fi.GetValue(this) as Dictionary<Type, List<object>>;

if (services == null)

{

throw new NullReferenceException("Could not get internal field '_services' value.");

}

if (serviceType == null)

{

throw new ArgumentNullException("serviceType");

}

lock (servicesLock)

{

object obj2 = null;

if (services.ContainsKey(serviceType))

{

List<object> list = services[serviceType];

foreach (object o in list)

{

if (o.GetType() == serviceType)

{

obj2 = o;

break;

}

}

}

return obj2;

}

}

}

Now the proper version type is used from EDS, and the correct implemented interface and events fire off just fine. Otherwise, the 1.0 workflow will attempt to use the 2.0 interface/events ¨C which leads to failure (as we want side-by-side and are not utilizing dynamic update).

I found that ¡°out of the box¡±, EDS cannot inherently utilize side-by-side versioning in changing interface assemblies without some creative coding.