Friday, October 16, 2009

Hi all

Today I discovered something I was not expecting while documenting something else, which I have just described here.

I had a solution that involved this input schema:

inputschema

and this output schema:

outputschema

Field1 from the input schema and Field3 form the output schema are promoted to the same property and Field2 form the input schema and Field4 from the output schema are also promoted to the same property. Note, that Field4 is an attribute, whereas Field1, Field2 and Field3 are elements.

I then have a map that does not map anything from the input to the output. It just creates empty elements/attributes in the output schema.

I added a receive port and receive location to read in the input and used the XMLReceive pipeline, because I needed the messagetype and I needed the property promotion. I added my map to the receive port. I then created a send port that basically just took everything that came in on the receive port and sent it out to a file. The send port uses the XMLTransmit pipeline.

The output from this was, as I expected. Given this input:

inputinstance

I got this output:

outputinstance

What happens is, that demotion is not supported for attributes, which really seems like a silly restriction, but that is just how it is.

When, however, I implemented my solution using an orchestration, it worked! The value of Field2 in the input was demoted into the value of Field4 in the output.

My orchestration is very simple:

orchestration

Basically, I receive the input, transform it using the same map as was on the receive port, copy over all the properties:

   1: OutputMessage(*) = InputMessage(*);
   2: OutputMessage(DemotionTest.Property1) = InputMessage(DemotionTest.Property1);
   3: OutputMessage(DemotionTest.Property2) = InputMessage(DemotionTest.Property2);

The reason that I copy over Property1 and Property2 manually is, that they are marked as MessageDataContextProperty in the property schema, and therefore, they are not automatically copied over using the OutputMessage(*) = InputMessage(*) statement.

After doing this, I just send out the message. The result is this:

outputinstance2

Now, this confused me… and what confused me more is, that I can actually do it with the passthrough pipeline on the send port. This means, that the demotion is happening as the orchestration publishes the message into the MessageBox for sending out the message.

So the upside to this is, that demotion for attributes DOES work – but only when the demotion occurs inside an orchestration upon sending out the message.

The downside is, that the product team have managed to do things differently depending on where in the process it happens, which really sounds like bad design. Hopefully they will fix this at some point in time.

Hope this helps someone

--
eliasen

Friday, October 16, 2009 1:52:43 PM (Romance Daylight Time, UTC+02:00)  #    Comments [0]  | 

Hi all

It is pretty common that developers want to assign one message to another inside and orchestration. And as we all know, this must happen inside a “Construct Message” shape.

Inside the “Construct Message” shape, you can have several shapes, but they must all be either a “Transform” shape or a “Message Assignment” shape. The “Transform” shape is used to execute a map, that will generate the message(s) that is/are to be constructed. The “Message Assignment” shape on the other hand uses the expression editor to let you specify how to assign a value to the message(s) that is/are to be constructed.

Often, there is a need to basically copy a message and then change just a couple of values in the copied message. This can’t be done by changing the values of the existing message, as messages in an orchestration are immutable.

Creating the copy is pretty simple; You just assign one message to another like this:

   1: OutputMessage = InputMessage;

Now, what many people realize after doing this and something does not work, like routing, correlation or other is, that this assignment only copies the content of InputMessage to OutputMessage. The context is not copied at all. So what you can do is to add another line of code to your “Message Assignment” shape like this:

   1: OutputMessage = InputMessage;
   2: OutputMessage(*) = InputMessage(*);

This will copy the context of a message from one message to another… but not the entire context, as it turns out.

I was trying out a demo for property demotion, and for this I let my orchestration receive an input message, transform it to an output message and send this out.

So what I did was that I created two schemas:

inputschema and outputschema

I let Field1 and Field3 be promoted into the same promoted property from a custom property schema, and I let Field2 and Field 4 be promoted into another property in a custom property schema.

and a map that does not map anything – it just created empty fields for Field3 and Field4.

In my “Construct Message” shape, I then added a “Transform” shape to do the transformation and a “Message Assignment” shape that would copy the properties. I then wanted to make sure the output of the send port had the demoted values inside it.

What I expected to happen was this:

  1. The XMLReceive pipeline would receive the input, promote the two properties and publish the message to the MessageBox.
  2. The Orchestration would pick it up, perform the transformation, copy the context and send the message out.
  3. The XMLTransmit pipeline would demote the two values from context (that I had copied from the input message) into Field3 and Field4

This didn’t happen, though. No values were demoted at all. Now, not demoting into Field4 was expected, since demotion doesn’t work for attributes (or does it? See my blog post coming up in a very short time :-) ), but I really expected something to turn up in Field3.

It turns out, that this statement from my code:

   1: OutputMessage(*) = InputMessage(*);

does not copy ALL the context, but only the promoted properties that are marked as MessageContextPropertyBase. The ones that are marked as MessageDataPropertyBase (which is the default) do not get copied over. Now, this actually makes sense, all though it baffled me at first, since properties marked as MessageDataPropertyBase are based on values inside the message, so they cannot just be copied to another message type, since BizTalk cannot guarantee that this property exists on this message type and that the element that points to the promoted property actually exists in the message. Well, I guess BizTalk COULD guarantee that, but Microsoft have chosen not to implement that.

Also, distinguished fields, which are also in the context of the message are not copied over – again, this makes sense, since these are tightly bound to the schema they come from (This doesn’t stop the mapping engine of copying them over in maps on receive ports, though, which is really silly – see here).

SO, in order to get ALL the context copied form my input schema to the output schema, I needed three lines of code:

   1: OutputMessage(*) = InputMessage(*);
   2: OutputMessage(DemotionTest.Property1) = InputMessage(DemotionTest.Property1);
   3: OutputMessage(DemotionTest.Property2) = InputMessage(DemotionTest.Property2);

or I could mark the properties as MessageContextPropertyBase.

Yes, indeed… So remember this from now on:

  1. Assigning one message to another does NOT copy over the context
  2. Copying over context using M2(*) = M1(*) does NOT copy over custom properties that are marked as the default (MessageDataContextBase)
  3. In order to get the rest of the properties copied over, do it manually or change the type of the properties, if applicable.

I hope this helps someone…

--
eliasen

Friday, October 16, 2009 1:38:00 PM (Romance Daylight Time, UTC+02:00)  #    Comments [2]  | 
Thursday, October 15, 2009

Hi all

When developing BizTalk 2009 solutions using Visual Studio .NET 2008 on Windows Server 2008, you run into issues when deploying from within Visual Studio .NET.

Now, first of all, as long as there are issues with BizTalk 2009 on Visual Studio .NET 2008, you should refrain from deploying from within Visual Studio .NET.

Secondly, when deploying on Windows Server 2008, you might run into this series of errors:

First error:

Access is denied. (Exception from HRESULT: 0x80070005 (E_ACCESSDENIED)), followed by

Second error:

at Microsoft.BizTalk.Gac.Fusion.IAssemblyCache.InstallAssembly(AssemblyCacheInstallFlag flags, String manifestFilePath, FusionInstallReference referenceData)
   at Microsoft.BizTalk.Gac.Gac.InstallAssembly(String assemblyPathname, Boolean force)
   at Microsoft.BizTalk.Deployment.BizTalkAssembly.GacInstall(String assemblyLocation)
   at Microsoft.BizTalk.Deployment.BizTalkAssembly.PrivateDeploy(String server, String database, String assemblyPathname, String applicationName)
   at Microsoft.BizTalk.Deployment.BizTalkAssembly.Deploy(Boolean redeploy, String server, String database, String assemblyPathname, String group, String applicationName, ApplicationLog log)

Third error:

Unspecified exception: "
Access is denied. (Exception from HRESULT: 0x80070005 (E_ACCESSDENIED))"

Access is denied. (Exception from HRESULT: 0x80070005 (E_ACCESSDENIED))

Fourth error:

Access is denied. (Exception from HRESULT: 0x80070005 (E_ACCESSDENIED))

Fifth error:

Failed to add resource(s). Change requests failed for some resources. BizTalkAssemblyResourceManager failed to complete end type change request. Access is denied. (Exception from HRESULT: 0x80070005 (E_ACCESSDENIED))

 

Now, usually, I must confess that my developer machines are usually virtual PCs, where I just login as the local administrator. If you do that, everything works fine. But in Windows Server 2008, Microsoft have introduced a new security system, where you need to approve it every time you start up some program that requires administrator rights – even if you are a member of the local administrators group.

So for instance, starting up BizTalk Server Administration console will cause this to appear:

user account control

And you have to click “Continue” to start it up.

The error above is because suddenly, even if you are a member of the local administrators group, you cannot add assemblies to the GAC.

You have three choices to fix it:

  1. Login with your username, but run Visual Studio .NET as an administrator. This is done by right clicking on the short cut for VS.NET and choosing “Run as administrator”.
  2. Login as administrator
  3. Turn off the “User Account Control”. This is done by entering “Control Panel” => “User Accounts”. Click on “Turn User Account Control on or off” – and in here you can turn it off. Now, when logged in as a user that i not the administrator, but who is a member of the local administrators group, you get the rights you usually have.

 

Hope this helps someone

--
eliasen

Thursday, October 15, 2009 10:41:16 PM (Romance Daylight Time, UTC+02:00)  #    Comments [2]  | 
Tuesday, October 13, 2009

Hi all

Just to let all you Danes know, Alan Smith (MVP) will be delivering the famous Quicklearn courses in BizTalk Deep Dive and BizTalk for Administrators in Copenhagen this fall.

You can see more about the courses at

BizTalk 2009 Developer Deep Dive – November 16’th

BizTalk 2009 for Administrators – November 30’th

Both courses are 5 days.

--
eliasen

Tuesday, October 13, 2009 9:12:12 PM (Romance Daylight Time, UTC+02:00)  #    Comments [0]  | 
Monday, October 12, 2009

Hi all

I just discovered a funny “feature” of the Bing Search on the Connect site:

BingSearch

 

So it is showing me results 1-0 of 0…

Funny, eh? No? Maybe? Oh, nevermind… :-)

--
eliasen

Monday, October 12, 2009 9:17:58 PM (Romance Daylight Time, UTC+02:00)  #    Comments [1]  | 
Saturday, October 10, 2009

Hi all

So, the other day I had this requirement for a BizTalk pipeline component:

Take an InfoPath formula and convert it into a PDF that is to be sent out via email. This seemed easy enough. I searched a bit, and found that three simple steps were needed:

  1. Install this: 2007 Microsoft Office Add-in: Microsoft Save as PDF 
  2. In my code, reference Microsoft.Office.InfoPath.dll and Microsoft.Office.InfoPath.FormControl.dll
  3. Write these lines of code:
   1: FormControl formControl = new FormControl();
   2: formControl.Open(pInMsg.Data);
   3: string output = Path.GetTempFileName();
   4: formControl.XmlForm.CurrentView.Export(output, Microsoft.Office.InfoPath.ExportFormat.Pdf);

Of course, this would also mean some code that would read the pdf file back in and then create the output message. But hey, that was just the price I had to pay.

BUT… I was being naive… As the more clever of my readers have probably all ready realized, if something is called FORMcontrol, then it is for programs that have a UI. The code crashed big time at runtime with some ActiveX exception :-(

Then I remembered that I have a colleague who had previously told me that she had done this at some point, so I emailed her for her code.

Unfortunately, her code involved taking the form, extracting the XSL from the XSN file, perform a transformation on the XML using the XSL which will generate HTML and then using some utility to convert this into PDF. This was more complex than I had hoped, but I saw no other way. Unfortunately, her code had this line in it:

   1: StreamReader stream = new StreamReader(XmlFormView.XmlForm.Template.OpenFileFromPackage("View1.xsl"));
which, as you might have guessed also requires a UI, in this case it is used in a web application. So no go.

So, it seems that I will have to do a lot of dirty work myself :-(

This turned into quite a list of subtasks:

  • Take the XML document that comes through the pipeline component
  • Take the value of the processing instruction called “mso-infoPathSolution” This processing instruction is always present in an InfoPath form and it looks something like this:
    <?mso-infoPathSolution solutionVersion="1.0.0.2" productVersion="12.0.0" PIVersion="1.0.0.0" href="http://path.to/form.xsn" name="urn:schemas-microsoft-com:office:infopath:MyForm:-myXSD-2009-09-21T15-43-10" ?>
  • Take the value of the href “attribute” that is in the value of the processing instruction. The href is a URI that points to the XSN that this XML is an instance of, you see.
  • Get the XSN file that is located at the URI.
  • Extract the XSL file that matches the view of the form you want to convert into PDF.
  • Perform the transformation
  • Convert into PDF

 

So I am now going from the few lines of code I was hoping for to a more complex solution… so lets look at the code:

First of all, I need the value of the processing instruction. This is easily done:

   1: private static string GetHrefFromXml(XmlDocument infoPathForm)
   2: {
   3:     XmlNode piNode = infoPathForm.SelectSingleNode("/processing-instruction(\"mso-infoPathSolution\")");
   4:     if (piNode != null && piNode is XmlProcessingInstruction)
   5:     {
   6:         var pi = (XmlProcessingInstruction)piNode;
   7:         string href = pi.Value;
   8:         int location = href.IndexOf(Href);
   9:         if (location != -1)
  10:         {
  11:             href = href.Substring(location + Href.Length);
  12:             href = href.Substring(0, href.IndexOf("\""));
  13:             return href;
  14:         }
  15:         throw new ApplicationException("No href attribute was found in the procesing instruction (mso-infoPathSolution). Without this, the location of the form cannot be detected and without the form no PDF can be generated.");
  16:     }
  17:     throw new ApplicationException("Required XML processing instruction (mso-infoPathSolution) not found. Without this, the location of the form cannot be detected and without the form no PDF can be generated.");
  18: }

The most annoying part is, that the value of a processing instruction can be anything. In this case, it appears to be a list of attributes like “normal” XML, but since this is not guaranteed, there is no language support for getting the value of the href “attribute”. So I chose to use string manipulation to get the value.

After getting the href, I need to get the XSN file from SharePoint Server, where the form is published. This turned out to be a challenge also.

My first approach was quite simple:

   1: private static byte[] GetFormByUrl(string href)
   2: {
   3:     var wc = new WebClient
   4:     {
   5:         Credentials = CredentialCache.DefaultCredentials
   6:     };
   7:     return wc.DownloadData(href);
   8: }

This turned out to be something silly, though. What happens when SharePoint and Forms Server get a request for the XSN file, it assumes some one is trying to fill out the form. So what I got back was the HTML that the Forms Server was sending a user that wanted to fill out the form. Then I thought I’d try to do this:

   1: private static byte[] GetFormByUrl(string href)
   2: {
   3:     HttpWebRequest wr = (HttpWebRequest)HttpWebRequest.Create(href);
   4:     wr.AllowAutoRedirect = false;
   5:     WebResponse resp = wr.GetResponse();
   6:     Stream stream = resp.GetResponseStream();
   7:     using (MemoryStream ms = new MemoryStream())
   8:     {
   9:         byte[] buffer = new byte[1024];
  10:         int bytes = 0;
  11:         while ((bytes = stream.Read(buffer,0, buffer.Length)) != -1)
  12:             ms.Write(buffer,0,bytes);
  13:         return ms.ToArray();
  14:     }
  15: }

Basically, using an HttpWebRequest I could ask it to not redirect. This didn’t work either, since what I then got back was some HTML that basically just said that the page has moved. Bummer.

But then another colleague who apparently is better at searching than I am found out that I can add a noredirect parameter to my request that will instruct SharePoint to not redirect. This is different from my current approach because my current approach instructs .NET to not follow redirects, whereas this new approach instructs SharePoint to not ask me to redirect.

So I ended up with something as simple as this:

   1: private static byte[] GetFormByUrl(string href)
   2: {
   3:     string url = href + "?noredirect=true";
   4:     var wc = new WebClient
   5:     {
   6:         Credentials = CredentialCache.DefaultCredentials
   7:     };
   8:     return wc.DownloadData(url);
   9: }

Simple and beautiful :-)

Now I have the XSN file and the next issue pops up, naturally; How do I get the XSL extracted from the XSN file. The XSN file is just a cabinet file with another extension, so I thought this must be easy. I found out it is not. I searched and searched and ended up finding all sorts of weird stuff where people used p/invoke to do stuff and what not. I am confused that Microsoft have not added at least extraction functionality to the .NET framework, but they haven’t.

I ended up doing this:

   1: private static string ExtractCabFile(string cabFile)
   2: {
   3:     string destDir = CreateTmp(true, "");
   4:  
   5:     var sh = new Shell();
   6:     Folder fldr = sh.NameSpace(destDir);
   7:     foreach (FolderItem f in sh.NameSpace(cabFile).Items())
   8:         fldr.CopyHere(f, 0);
   9:     return destDir;
  10: }

This code assumes that the XSN file has been written to a temporary file with the extension .CAB – this is very important, since the shell command will open up the .CAB file with the default program, which is then the explorer. After that, all files in the cabinet file is copied to “destDir” which is just a directory created in the users Temp directory.

I am quite annoyed to have to go through all this, but that’s how things go sometimes.

So now I have found the href of the form, downloaded the form and extracted its files. Time for the transformation:

   1: private static MemoryStream PerformTransformation(XmlDocument xmldoc, string destDir, string view)
   2: {
   3:     var transform = new XslCompiledTransform();
   4:     var stream = new StreamReader(destDir + @"\" + view + ".xsl");
   5:     XmlReader xmlReader = XmlReader.Create(stream);
   6:     transform.Load(xmlReader);
   7:  
   8:     var outputMemStream = new MemoryStream();
   9:     transform.Transform(xmldoc, null, outputMemStream);
  10:     stream.Close();
  11:     xmlReader.Close();
  12:     outputMemStream.Seek(0, SeekOrigin.Begin);
  13:     return outputMemStream;
  14: }

So just a normal XSLT transformation, resulting in some HTML that is returned in a stream.

After this, I need to convert it into PDF, which is really simple using a tool we bought for this:

   1: private static byte[] GetPdfFromHtml(Parameters param)
   2: {
   3:     var pdfConverter = new PdfConverter
   4:     {
   5:         LicenseKey = "SomethingElse - You are not getting the correct License Key :-)"
   6:     };
   7:  
   8:     byte[] pdfBytes = pdfConverter.GetPdfBytesFromHtmlStream(param.HtmlStream, Encoding.UTF8, param.DestDir.EndsWith(@"\") ? param.DestDir : param.DestDir + @"\");
   9:     return pdfBytes;
  10: }

We are using the ExpertPDF library for this. The third parameter for the GetPdfBytesFromHtmlStream method call is the directory where the cabinet file was extracted to, since this is where all images used in the form are also kept and they are needed for the PDF to include them.

All in all; the component now works, but it turned out to be a lot more difficult than I had hoped.

As a last detail, I added a property to my pipeline component that the developer can use to decide which view to use for the transformation form XML to HTML.

The complete code for the pipeline component will not be available for download, since this was done for a customer, but I might do something a bit smaller and simpler and add it to my pipeline component collection later on.

--

eliasen

Saturday, October 10, 2009 4:32:03 PM (Romance Daylight Time, UTC+02:00)  #    Comments [1]  | 
Friday, October 9, 2009

Hi all

It has arrived – the first service pack for BizTalk 2006 R2.

Get it here: https://connect.microsoft.com/site/sitehome.aspx?SiteID=65

For a list of fixes, see here: http://support.microsoft.com/kb/974563

New features, see here: http://msdn.microsoft.com/en-us/library/ee532481(BTS.20).aspx

Good luck with it.

--
eliasen

Friday, October 9, 2009 8:22:28 PM (Romance Daylight Time, UTC+02:00)  #    Comments [0]  | 
Saturday, October 3, 2009

Hi all

Jon Skeet, who has been a C# MVP since 2003, as I understand it was to have his MVP renewed on October 1’st 2009, but his employer Google advised him not to be renewed, so he had to tell Microsoft to not consider him for renewal. You can see Jons blog entry here.

To be honest, I think it is very petty way Google is thinking.

So to Jon: Great work, keep it up, and hopefully either Google will come around or you will find a better job and get your MVP status back.

--
eliasen

Saturday, October 3, 2009 2:40:03 PM (Romance Daylight Time, UTC+02:00)  #    Comments [0]  | 

Hi all

Now, I am aware that people following my blog are probably very likely to follow Richard Seroters blog as well, but just in case you haven’t noticed, Richard tortured me with his 4 questions this time :-)

You can find his questions and my answers here.

Now, being interviewed by Richard and thus having a link to my blog appear on his blog entry is bound to generate some traffic to my blog from people who either didn’t know me or didn’t visit my blog… So naturally, the first about 12 hour after Richard posted his interview, my blog was down because the webhosting company that hosts eliasen.dk had changed something… or something else, who knows? :-)

As you can see, though, my blog is again up and running and will hopefully stay this way! :-)

--
eliasen

Saturday, October 3, 2009 2:22:35 PM (Romance Daylight Time, UTC+02:00)  #    Comments [0]  | 
Friday, September 18, 2009

Hi all

I am REALLY excited to announce, that I will be co-authoring a book on BizTalk. I will be a part of a terrific team consisting of

  • Anush Kumar
  • Brian Loesgen
  • Charles Young
  • Jon Flanders
  • Scot Colestock
  • Tom Canter
  • Me :-)

Together we will be writing “BizTalk Server 2009 Unleashed”, which is so new, that you cannot find it on the web page of the publisher or any other sites. It is so new, that we haven’t even signed our contracts with the publisher yet, which may cause someone to quit the project if they are not happy about the contract… so nothing promised yet.

But, needless to say, I am really excited, and giving the team, also feeling quite humble :-).

This will be my first book.

Wohooo

--
eliasen

Friday, September 18, 2009 10:06:38 PM (Romance Daylight Time, UTC+02:00)  #    Comments [11]  | 

Theme design by Jelle Druyts