I am trying to host a long running workflow service on Azure but I am having problems with correlation.
I have got the timeToUnload and the timeToPersist set to 0 and I have
After decompiling and searching a lot, I finnaly found where the key is Generated.
In the class System.ServiceModel.Channels.CorrelationKey, System.ServiceModel
The method GenerateKeyString does the trick. Now I have to find a way to override this method and make my own generating key algorithm so the same instance can run in multiple web servers with different names.
private static Guid GenerateKey(string keyString)
{
return new Guid(HashHelper.ComputeHash(Encoding.Unicode.GetBytes(keyString)));
}
private static string GenerateKeyString(ReadOnlyDictionaryInternal<string, string> keyData, string scopeName, string provider)
{
if (string.IsNullOrEmpty(scopeName))
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("scopeName", System.ServiceModel.SR.GetString("ScopeNameMustBeSpecified"));
if (provider.Length == 0)
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("provider", System.ServiceModel.SR.GetString("ProviderCannotBeEmptyString"));
StringBuilder stringBuilder1 = new StringBuilder();
StringBuilder stringBuilder2 = new StringBuilder();
SortedList<string, string> sortedList = new SortedList<string, string>((IDictionary<string, string>) keyData, (IComparer<string>) StringComparer.Ordinal);
stringBuilder2.Append(sortedList.Count.ToString((IFormatProvider) NumberFormatInfo.InvariantInfo));
stringBuilder2.Append('.');
for (int index = 0; index < sortedList.Count; ++index)
{
if (index > 0)
stringBuilder1.Append('&');
stringBuilder1.Append(sortedList.Keys[index]);
stringBuilder1.Append('=');
stringBuilder1.Append(sortedList.Values[index]);
stringBuilder2.Append(sortedList.Keys[index].Length.ToString((IFormatProvider) NumberFormatInfo.InvariantInfo));
stringBuilder2.Append('.');
stringBuilder2.Append(sortedList.Values[index].Length.ToString((IFormatProvider) NumberFormatInfo.InvariantInfo));
stringBuilder2.Append('.');
}
if (sortedList.Count > 0)
stringBuilder1.Append(',');
stringBuilder1.Append(scopeName);
stringBuilder1.Append(',');
stringBuilder1.Append(provider);
stringBuilder2.Append(scopeName.Length.ToString((IFormatProvider) NumberFormatInfo.InvariantInfo));
stringBuilder2.Append('.');
stringBuilder2.Append(provider.Length.ToString((IFormatProvider) NumberFormatInfo.InvariantInfo));
stringBuilder1.Append('|');
stringBuilder1.Append((object) stringBuilder2);
return ((object) stringBuilder1).ToString();
}
A few months later and I have found a solution to this problem. The root problem is that Azure names the Web site something different on each role instance; Rather than "Default Web SIte", the web site is called something like NewOrbit.ExVerifier.Web_IN_0_Web
(given a namespace for your web project of NewOrbit.ExVerifier.Web). Workflow uses the website name as part of the algorithm used to calculate the instance key, hence the problem.
The solution is, quite simply, to rename the website during role startup so it is called the same thing on all instances. Fixing the root problem rather than handling the consequences and so obvious I never saw it the first time round.
Here is how you can do this (losely based on this: http://blogs.msdn.com/b/tomholl/archive/2011/06/28/hosting-services-with-was-and-iis-on-windows-azure.aspx)
In ServiceDefinition.csdef
add a startup task:
<ServiceDefinition name="WasInAzure" xmlns="http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceDefinition">
<WebRole name="WebRole1">
...
<Startup>
<Task commandLine="setup\startup.cmd" executionContext="elevated" />
</Startup>
</WebRole>
</ServiceDefinition>
Setup\Startup.cmd
should have this content:
powershell -command "set-executionpolicy Unrestricted" >> out.txt
In ServiceDefinition.csdef
add this:
<ServiceDefinition name="WasInAzure" xmlns="http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceDefinition">
<WebRole name="WebRole1">
...
<Runtime executionContext="elevated" />
</WebRole>
</ServiceDefinition>
Create a setup\RoleStart.ps1
file:
write-host "Begin RoleStart.ps1"
import-module WebAdministration
$siteName = "*" + $args[0] + "*"
Get-WebSite $siteName | Foreach-Object {
$site = $_;
$siteref = "IIS:/Sites/" + $site.Name;
try {
Rename-Item $siteref 'MyWebSite'
write-host $siteName + " was renamed"
}
catch
{
write-host "Failed to rename " + $siteName + " : " + $error[0]
}
}
write-host "End RoleStart.ps1"
(replace MyWebSite with whatever you want the website to be called on all the servers).
Create or Edit WebRole.cs in the root of your website project and add this code:
public class WebRole : RoleEntryPoint
{
public override bool OnStart()
{
var startInfo = new ProcessStartInfo()
{
FileName = "powershell.exe",
Arguments = @".\setup\rolestart.ps1",
RedirectStandardOutput = true,
UseShellExecute=false,
};
var writer = new StreamWriter("out.txt");
var process = Process.Start(startInfo);
process.WaitForExit();
writer.Write(process.StandardOutput.ReadToEnd());
writer.Close();
return base.OnStart();
}
}
And that should be it. If you spin up multiple web role instances and connect to them with RDP, you should now be able to see that the website is called the same on all the instances and workflow persistence therefore works.
It appears this is a problem with running workflow services in a web role. Looks like the workaround is to run you workflow services in a worker role which doesn't have the same problem.
I’m not familiar with Workflow persistence. But others have reported they’ve successfully made SQL Azure work with WF persistence, I would like to suggest you to check http://social.msdn.microsoft.com/Forums/en-US/ssdsgetstarted/thread/2dac9194-0067-4e16-8e95-c15a72cb0069/ and http://www.theworkflowelement.com/2011/05/wf-persistence-on-sql-azure.html to see if they help.
Best Regards,
Ming Xu.
We are new to WF and WCF but was wondering if you could build your own Instance Store.
This should give you the ability to override InstanceKey whereby you could calculate your own.
There are quite a few examples on the internet.