endpoint.tv – Workflow and Custom Activities – Best Practices (Part 2)
In this episode, Windows Workflow Foundation team Program Manager Leon Welicki drops in to show us the team’s guidelines for developing custom activities.
In this episode, Windows Workflow Foundation team Program Manager Leon Welicki drops in to show us the team’s guidelines for developing custom activities.
Here is a really simple way to handle a session expiration in asp.net MVC using a base controller. Having all controller inherit from a basecontoller and overriding the OnActionExecuting event allows for checking the session before all actions are executed.
Here is the code
public class BaseController : Controller
{
protected override void OnActionExecuting
[…]
In this episode, Windows Workflow Foundation team Program Manager Leon Welicki drops in to show us the team’s guidelines for developing custom activities.
For more information, see the Workflow Foundation developer center on MSDN.
As some of you know, I presented on Entity Framework 4 and WCF Data Services 4 at Hartford Code Camp back on June 19th. I have posted the material from the code camp here.
The focus of the AppFabric Web Farm guide is to assist you in properly installing and configuring Windows Server AppFabric on an existing IIS Web farm. Within a small Windows Server AppFabric installation or within a development environment, having a single AppFabric server is sufficient. However, in a larger production environment, a Web farm using multiple AppFabric servers is recommended to handle increased user load. Web farms not only increase performance by reducing the load on each server, but also increase availability. If one server in the Web farm is disabled (for example, taken offline for maintenance), other servers in the Web farm take over, so users are never aware of the offline server. Servers within a Web farm share a pool of resources and minimize chances for performance bottlenecks due to resource contention.
http://download.microsoft.com/download/3/1/C/31CED722-2E5F-48D6-96B1-E73AAFD9873F/AppFabricWebFarm.docx
Learned something new today. Maybe it’s well-known, but new to me, you can just run “C:\Program Files (x86)\Microsoft SQL Server\90\Tools\Publishing\1.4\SqlPubWiz.exe”. This nifty little tool can generate INSERT script very easily. Pictures tell the story 🙂
Here are the links to the material from the ASP.NET MVC 2 Primer presentation given at the 3rd Annual Hartford Code Camp on Saturday, June 19, 2010:
Powerpoint Slides: Primer.pptx
Source Code: PrimerDemoSource.zip
Disclaimer:
I have done some Very Bad Things%u2122 in this code in order to speed up the presentation that are always considered bad practice. The most […]
I case anyone has missed it, BizTalk User Group Sweden, is as active as ever. We’ve got two great events planned after the summer.
Date: 26/8 2010 – Evening event.
Location: Stockholm
Topic: Managing your BizTalk environment using System Center Operations Manager 2007 R2
Speaker: Kent Weare
Sign-up: Here
This is a great event not only for BizTalk Developers and Operations but also for IT-professionals working with System Center, especially if they have BizTalk in their environment.
Date: 8-9/9 2010 – Yes, that’s right, this is a TWO DAY event.
Location: Stockholm
Topic: Applied Architecture Patterns on the Microsoft Platform aka BizTalk Server 2010 – Release party
Speaker: Richard Seroter, Ewan Fairweather, Stephen W. Thomas
Sign-up: Here
These are both events you do not wan’t to miss, and it seems people agree. Slots are filling up fast so be sure to be quick about signing up.
I’ll be presenting at Microsoft Swedens “Sommarkollo”. I’ll be talking about BizTalk. Introducing it, showing of enhancements in 2010 and going through some (what I hope are) inspirational scenarios. The target audience are developers or architects. Even though I will spend some time in the beginning at an introductory level I believe there are things that will entertain even more seasoned developers. Level is 200-300.
More info about this and other presentations can be found at http://www.microsoft.com/sverige/sommarkollo/default.html
Also, signups for my presentations can be reached through the below links:
Date: 30/6 kl. 13-16
Location: G%u00f6teborg
Sign-up: Here
Datum: 1/7 kl. 9-12
Location: Malm%u00f6.
Sign-up: Here
Datum: 7/7 kl. 9-12
Location: Stockholm
Sign-up: Here
Datum: 24/8 kl. 9-12
Location: Stockholm
Sign-up: Here
Enjoy the summer! I know I will 🙂
PS. Just to make readers (especially those from other countries with less generous terms) jealous I’ll be on vacation/parental leave (and have been for two weeks) until the very last days of august.
This is the first in a series of articles that will introduce and explore a couple a patterns to transfer and process large messages using WCF-Adapters. Generally speaking, an end to end BizTalk solution adopts the FTP Adapter or one of its variations (SFTP/FTPS) when it needs to exchange large messages with external parties over the internet, while it uses the FILE adapter for moving large batch files over the intranet. Large input files can be debatched by a receive pipeline into smaller discrete messages. This technique removes the need to worry about a large message and enables processing to be parallelized. The main recommendation to bear in mind when dealing with large messages might seem obvious, but it is often overlooked by developers: you must avoid any operations that require the entire message to be loaded into memory and adopt a streaming approach, especially in those cases where the application can concurrently receive and process multiple large messages in the same time. BizTalk itself adopts a streaming approach whereby the message is read through the use of a stream, meaning that the entire message doesn’t have to be present in memory at any point in time. This streaming approach is used by all the built-in pipeline components such as the Flat File Disassembler, XML Disassembler, etc. Therefore, it’s extremely important that you adopt the same approach when designing and developing custom pipeline components, orchestrations or helper classes for processing large messages.
The problems associated with large messages can be divided into the following categories:
The original message size, message format, and type of message processing are the main factors that affect how BizTalk Server processes large messages.
For more information about large message transfer and processing with BizTalk Server, you can read the following topics:
In this article we’ll try to provide an answer to the following questions:
Besides, in this series of articles I’ll discuss the following topics:
When I started to design this demo, my will was comparing the performance of the various message encoders and transfers modes provided out of the box by WCF and find the best combination to adopt when transferring large message over the network. WCF includes three types of encoding for SOAP messages:
WCF transport channels support two modes for transferring messages in each direction:
The TransferMode property exposed by transport protocol channel (e.g.HttpTransportBindingElement, TcpTransportBindingElement, etc.) and bindings (BasicHttpBinding, NetTcpBinding, etc.) allows to indicate whether messages are sent buffered or streamed. Streamed transfers can improve the scalability of a service by eliminating the need for large memory buffers. Whether changing the transfer mode actually improves scalability in practice depends on the size of the messages being transferred. Improvements in scalability should be most evident when large messages use streamed instead of buffered transfers. Therefore we’ll exploit the streamed transfer mode in for transferring large messages using BizTalk and WCF Adapters.
Note: By default, the HTTP, TCP/IP and Named Pipe transports use buffered message transfers. If you plan to exploit the streamed transfer mode when receiving a large message through a WCF Receive Location, you have to use the WCF-Custom/WCF-CustomIsolated Adapter. In fact, these latter allow developer to select a certain binding (e.g. BasicHttpBinding, NetTcpBinding, NetNamedPipeBinding, etc.) and then change the value of its TransferMode property to Streamed. The other WCF Adapters (e.g. WCF-BasicHttp, WCF-NetTcp, etc.) do not provide this possibility.
For more information on the WCF Transfer Modes, you can read the following topic:
I created a WinForm application that is capable to upload multiple documents at the same time. The UI of the driver application allows to select a list of files to be sent in parallel to BizTalk and which service endpoint to use for this purpose. Six different WCF Receive Locations have been created, one for each possible combination of message encoders and transfer modes. Therefore, the configuration file of the client application contains a different service endpoint, one for each WCF Receive Location. When you push the Submit button on the main form, the click event handler method (btnSubmit_Click) creates a a list of calls and then using the ThreadPool it start an asynchronous thread for each file selected on the UI. At this point, if the selected service endpoint uses a transaction-enabled binding, the application will call the SubmitFileWithTxn method, otherwise the SubmitFileWithoutTxn method.
The code of the 2 functions is quite similar, the only difference between the two is that the SubmitFileWithTxn method initiates a transaction that will be flowed to the WCF Receive Location. Let’s analyze the code of this latter method. The SubmitFileWithTxn starts opening a FileStream to read the selected file from the disk and I wrap this latter with a custom stream called ProgressStream. As the name suggests, the main scope of this custom stream is to update a progress bar on the UI as the file is being transmitted. This feature is particularly interesting when we use a binding which TransferMode is equal to Streamed. The ProgressStream class exposes an event called ProgressChanged that can be used to invoke a method that updates the progress bar. Since the progress bar control is owned by the main thread, while the application uses a ThreadPool worker thread to transmit the message to BizTalk, in order to change the value of the progress bar is necessary to call the Invoke method exposed by the ProgressForm object with a delegate. This technique enables the worker thread to ask the main thread to execute the delegate.
Then, the method creates an UploadMessage. If you look at the code of the class, you will note that I decorated the class with the MessageContractAttribute. Then I decorated all the properties representing transmission metadata like the name of the sender or the file size with the MessageHeaderAttribute attribute. This way I declare that this data will be transmitted as custom headers in the SOAP envelope.
Instead, the property called Data that will contain the content of the source file stream has been decorated with the MessageBodyMember attribute. Hence data will be transmitted within the body of the SOAP envelope.
Finally, the method creates a ChannelFactory using the IUploadMessageTxn interface as service contract. If you look at the code, you will note that this interface has been decorated with the TransactionFlowAttribute. In particular, the TransactionFlowOption.Mandatory value indicates that the service endpoint requires the client application to start and flow a distributed transaction.
For this reason, the SubmitFileWithTxn method invoke the underlying WCF Receive Location within a transaction scope. The driver application allows to control the outcome of the transaction selecting one between the Commit or Abort option buttons on the UI. Selecting abort, you can simulate a transaction failure. This possibility is extremely useful when you configure the WCF receive location to use a transactional binding. In fact, in this case the Message Agent will use the transaction initiated by the client application to post a placeholder document to the BizTalkMsgBoxDb (as you will see in the remainder of the article, the original message will be staged in a folder or SQL database and replaced by a placeholder document which contains its location and metadata). Therefore, if the client application aborts the transaction, no message will be published to the BizTalkMsgBoxDb. Transferring a large message in the context of WS-AT or OleTransactions transaction greatly improves transmission reliability, but it has a severe impact on overall performance, so this technique is recommended only in those cases where resilience is the primary concern.
For sake of readability, I omitted most of the code and I reported below only the classes and methods that can be helpful to understand the application.
MainForm class
#region Copyright //------------------------------------------------- // Author: Paolo Salvatori // Email: [email protected] // History: 2009-01-14 Created //------------------------------------------------- #endregion #region Using Directives using System; using System.IO; using System.Collections.Generic; using System.Configuration; using System.ComponentModel; using System.Transactions; using System.Data; using System.Drawing; using System.Text; using System.Xml; using System.Xml.XPath; using System.Threading; using System.Windows.Forms; using System.ServiceModel; using System.ServiceModel.Channels; using System.ServiceModel.Configuration; #endregion namespace Microsoft.BizTalk.CAT.Samples.SSISIntegration.Client { public partial class MainForm : Form { |
ProgressStream class
#region Copyright //------------------------------------------------- // Author: Paolo Salvatori // Email: [email protected] // History: 2009-06-30 Created //------------------------------------------------- #endregion #region Using Directives using System; using System.IO; #endregion namespace Microsoft.BizTalk.CAT.Samples.SSISIntegration.Client { public class ProgressStream : Stream { #region Private Fields private Stream stream; private int index; private long bytesRead; private long totalLength; #endregion #region Public Handler public event EventHandler<ProgressChangedEventArgs> ProgressChanged; #endregion #region Public Constructor public ProgressStream(int index, Stream file) { this.index = index; this.stream = file; this.totalLength = file.Length; this.bytesRead = 0; } #endregion #region Public Properties public override bool CanRead { get { return this.stream.CanRead; } } public override bool CanSeek { get { return this.stream.CanSeek; } } public override bool CanWrite { get { return this.stream.CanWrite; } } public override void Flush() { this.stream.Flush(); } public override long Length { get { return this.stream.Length; } } public override long Position { get { return this.stream.Position; } set { this.stream.Position = value; } } #endregion #region Public Methods public override int Read(byte[] buffer, int offset, int count) { int result = stream.Read(buffer, offset, count); bytesRead += result; if (ProgressChanged != null) { try { ProgressChanged(this, new ProgressChangedEventArgs(index, bytesRead, totalLength)); } catch (Exception) { ProgressChanged = null; } } return result; } public override long Seek(long offset, SeekOrigin origin) { return this.stream.Seek(offset, origin); } public override void SetLength(long value) { this.stream.SetLength(value); } public override void Write(byte[] buffer, int offset, int count) { this.stream.Write(buffer, offset, count); } #endregion } public class ProgressChangedEventArgs : EventArgs { #region Private Fields private int index; private long bytesRead; private long totalLength; #endregion #region Public Constructor public ProgressChangedEventArgs(int index, long bytesRead, long totalLength) { this.index = index; this.bytesRead = bytesRead; this.totalLength = totalLength; } #endregion #region Public properties public int Index { get { return this.index; } set { this.index = value; } } public long BytesRead { get { return this.bytesRead; } set { this.bytesRead = value; } } public long TotalLength { get { return this.totalLength; } set { this.totalLength = value; } } #endregion } } |
IUploadMessageTxn interface
#region Copyright //------------------------------------------------- // Author: Paolo Salvatori // Email: [email protected] // History: 2009-01-14 Created //------------------------------------------------- #endregion #region Using Directives using System; using System.IO; using System.ServiceModel; #endregion namespace Microsoft.BizTalk.CAT.Samples.SSISIntegration.Client { [ServiceContract(Namespace = "http://microsoft.biztalk.cat/10/samples/ssisintegration/uploadservice")] public interface IUploadMessageTxn { #region Contract Operations [OperationContract(Action = "UploadMessageTxn", ReplyAction = "*")] [TransactionFlow(TransactionFlowOption.Mandatory)] [XmlSerializerFormat()] void UploadMessageTxn(UploadMessage message); #endregion } } |
UploadMessage class
#region Copyright //------------------------------------------------- // Author: Paolo Salvatori // Email: [email protected] // History: 2009-01-14 Created //------------------------------------------------- #endregion #region Using Directives using System; using System.IO; using System.ServiceModel; using System.Net.Security; #endregion namespace Microsoft.BizTalk.CAT.Samples.SSISIntegration.Client { [MessageContract] public class UploadMessage { #region Private Fields private string id; private string sender; private string filename; private DateTime dateTime; private long size; private Stream data; #endregion #region Public Constructors public UploadMessage() { this.id = null; this.sender = null; this.filename = null; this.dateTime = DateTime.Now; this.size= 0; this.data = null; } public UploadMessage(string id, string sender, string filename, DateTime dateTime, long size, Stream data) { this.id = id; this.sender = sender; this.filename = filename; this.dateTime = dateTime; this.size = size; this.data = data; } #endregion #region Public Properties [MessageHeader(Name = "id", Namespace = "http://microsoft.biztalk.cat/10/samples/ssisintegration/uploadservice", ProtectionLevel = ProtectionLevel.None)] public string Id { get { return this.id; } set { this.id = value; } } [MessageHeader(Name = "sender", Namespace = "http://microsoft.biztalk.cat/10/samples/ssisintegration/uploadservice", ProtectionLevel = ProtectionLevel.None)] public string Sender { get { return this.sender; } set { this.sender = value; } } [MessageHeader(Name = "filename", Namespace = "http://microsoft.biztalk.cat/10/samples/ssisintegration/uploadservice", ProtectionLevel = ProtectionLevel.None)] public string Filename { get { return this.filename; } set { this.filename = value; } } [MessageHeader(Name = "dateTime", Namespace = "http://microsoft.biztalk.cat/10/samples/ssisintegration/uploadservice", ProtectionLevel = ProtectionLevel.None)] public DateTime DateTime { get { return this.dateTime; } set { this.dateTime = value; } } [MessageHeader(Name = "size", Namespace = "http://microsoft.biztalk.cat/10/samples/ssisintegration/uploadservice", ProtectionLevel = ProtectionLevel.None)] public long Size { get { return this.size; } set { this.size = value; } } [MessageBodyMember(Name = "data", Namespace = "http://microsoft.biztalk.cat/10/samples/ssisintegration/uploadservice", ProtectionLevel = ProtectionLevel.None)] public Stream Data { get { return this.data; } set { this.data = value; } } #endregion } } |
App.Config
<?xml version="1.0" encoding="utf-8" ?> <configuration> <system.diagnostics> <sources> <source name="System.ServiceModel.MessageLogging" switchValue="Warning, ActivityTracing"> <listeners> <add type="System.Diagnostics.DefaultTraceListener" name="Default"> <filter type="" /> </add> <add name="ServiceModelMessageLoggingListener"> <filter type="" /> </add> </listeners> </source> </sources> <sharedListeners> <add initializeData="C:\ssisintegration_client.svclog" type="System.Diagnostics.XmlWriterTraceListener, System, Version=2.0.0.0, Culture=neutral, |
For more information on how using the streamed transfer mode along with WCF, you can read the following topic:
In order to compare the performance of the various message encoders and transfer modes when transferring large messages over the network, I created six different WCF-Custom Receive Locations, one for each possible combination of these properties:
Each of the above Receive Locations use a CustomBinding and the TcpTransportBindingElement. Let’s compare the Binding configuration of the Receive Locations #2 and #5. They both use the Binary message encoder but they are configured to use a different transfer mode, respectively streamed and buffered. If you look at the WCF-Custom Adapter configuration of the SSISIntegration.WCF-Custom.NetTcp.Binary.Streamed.ReceiveLocation receive location (see the picture below), you will note that:
By contrast, the SSISIntegration.WCF-Custom.NetTcp.Binary.Buffered.ReceiveLocation receive location has been configured as follows:
The Messaging Engine failed to add a receive location “<Receive Location Name>” with URL “<Receive Location URL>” to the adapter “WCF-Custom”. Reason: “System.ArgumentException: For TransferMode.Buffered, MaxReceivedMessageSize and MaxBufferSize must be the same value.
All the above Receive Locations have been configured to use the ServiceThrottlingBehavior. The default value of the 3 properties exposed by the service behavior have been increased from the default value to 200. Indeed, as I explained in my article called Customizing and Extending the BizTalk WCF Adapters, all incoming messages to a certain WCF Receive Location are received and processed by a singleton instance of the BizTalkServiceInstance class. Thus, the value you specify for the MaxConcurrentInstances property is irrelevant as a single service instance will be used for each WCF Receive Location. Also the value you assign to the MaxConcurrentSessions property is no relevant if you don’t use a session-aware binding. As a consequence, the only property that you should properly set is the MaxConcurrentCalls that specifies the maximum number of messages concurrently processed by the WCF Receive location.
If we enable WCF tracing on the client application and we try to send a file to BizTalk, we can note that the SOAP message sent by the client application to BizTalk has the following format (the namespace of custom headers have been removed for simplicity).
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://www.w3.org/2005/08/addressing"> <s:Header> <a:Action s:mustUnderstand="1">UploadMessage</a:Action> <h:dateTime>2010-05-17T17:14:47.0058023+02:00</h:dateTime> <h:filename>config.xml</h:filename> <h:id>93204315-aeee-48d0-8922-09ef521c27f2</h:id> <h:sender>Paolo Salvatori</h:sender> <h:size>3617</h:size> <a:MessageID>urn:uuid:d8f87456-cef5-4302-b760-f1765483e4f1</a:MessageID> <a:ReplyTo> <a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address> </a:ReplyTo> <a:To s:mustUnderstand="1">net.tcp://localhost:11553/ssisintegration</a:To> </s:Header> <s:Body> <UploadMessage xmlns="http://microsoft.biztalk.cat/10/samples/ssisintegration/uploadservice"> <data>...</data> </UploadMessage> </s:Body> </s:Envelope> |
As a consequence, in order to extract data from the Body element, it’s necessary to properly configure the Inbound BizTalk message body section of all the WCF Receive Locations:
To avoid publishing the incoming large message to the MessageBox, I created a custom receive pipeline and in particular a custom pipeline component called StageFilePipeComponent that receives and stage the incoming file a folder or to a custom SQL database using a streaming approach and finally replace the original file with a placeholder document which contains the location of the staged file and its metadata. When published to the MessageBox, the placeholder document is consumed by a Send Port and processed by a custom send pipeline that reads the location of the original message and substitutes the placeholder document with this latter. Besides, the custom pipeline running within the Receive Location promotes the AckRequired context property. This will cause the send port to generate ACK and NACK messages when the adapter reports a message as successfully delivered or in the case of a NACK, when the adapter has failed and a message will be suspended. These messages are special and will only be published to the MessageBox, if there is someone subscribing to them. In my case, ACK and NACK messages are consumed by a special orchestration which role is to remove the original message from the staging repository (Folder, SQL Server) once the original message has been successfully transmitted by the send port or to eventually resubmit a suspended resumable message when the send port runs out of retries. In the next article of the series, I’ll show the code of the pipeline components and orchestration. For now I will only describe the data flow of the demo and discuss the results in terms of performance:
The two tables below report the performance data in terms of latency and memory consumption that I measured during my tests. The size of messages used during tests was around 50 MB. The following findings emerge from the analysis of the figures:
This article introduced a powerful technique to transfer and handle large files and provided a comparison in terms of performance and memory consumption between the message encoders and transfer modes supplied by WCF. In the next article, I’ll provide more details on the solution implementation and I’ll explain how to configure a WCF-Custom Receive Location and the StageFileReceivePipeline to stage the incoming file to the file system (using the Transactional NTFS or TxF) or to SQL Server in a transactional way using the same transaction utilized by the Message Agent to post the placeholder document to the MessageBox. In the meantime, you can download the code from my site in 2 flavors:
As always, please provide feedbacks, problems and suggestions.