Paul's Petrov Whiteboard

[BizTalk, Enterprise Application Integration, Business Process Automation, SOA, .NET]

  Home  |   Contact  |   Syndication    |   Login
  50 Posts | 1 Stories | 48 Comments | 47 Trackbacks

News

Archives

Post Categories

Image Galleries

BizTalk

Other

Monday, June 23, 2008 #

Below is an idea how to improve SOAP exception handling in BizTalk to WCF communication. Let’s consider approach recommended in BizTalk SDK documentation (Catch Typed Fault Exception sample):
 
  1. Add custom WCF fault to the service contract and add corresponding exception handler in the BizTalk orchestration.
  2. In BizTalk orchestration add hander that expects System.Web.Services.Protocols.SoapException to catch any general SOAP exceptions
This allows catching any custom WCF faults and handling them gracefully. It also handles general SOAP exceptions but the downside is that SOAP exception caught will not be original SOAP fault that came over the wire from the service. It’s going to be secondary SOAP exception thrown by the adapter. Receive pipeline will try to match incoming message type using XPath expressions defined in the send port configuration. Since custom WCF faults are embedded as content of Detail node of the soap:Fault message there will be XPath expression like this: /*[local-name()='Fault']/*[local-name()='Detail']/* | /*[local-name()='DivideResponse'], where the first part is matching any child of Detail element. In case if WCF service returns general SOAP fault it will put original exception in Detail element as ExceptionDetail node (QName: http://schemas.datacontract.org/2004/07/System.ServiceModel#ExceptionDetail). Since the schema for ExceptionDetail is unknown to pipeline it will throw SOAP exception with generic description and all original details will be lost. This secondary SOAP exception is the one that actually caught by our generic handler:
 
There was a failure executing the response(receive) pipeline: "Microsoft.BizTalk.DefaultPipelines.XMLReceive, Microsoft.BizTalk.DefaultPipelines, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" Source: "XML disassembler" Send Port: "WcfSendPort_Operator_WSHttpBinding_IOperation" URI: "http://localhost:8005/calculate" Reason: Finding the document specification by message type "http://schemas.datacontract.org/2004/07/System.ServiceModel#ExceptionDetail" failed. Verify the schema deployed properly.
 
One solution to this problem is to define http://schemas.datacontract.org/2004/07/System.ServiceModel#ExceptionDetail schema and include it as a fault contract for the WCF operation on the client side. That way all original SOAP fault details can be captured and propagated as ExceptionDetail fault. So that’s how I modified BizTalk orchestration in SDK sample:
 
  1. Declared new schema ExceptionDetail.
  2. Created new multi-part message type IOperation_Divide_SoapFault that has part of type ExceptionDetail
  3. Added to Divide operation new GenericSoapFault fault of type IOperation_Divide_SoapFault
  4. Added exception handler CatchSoapExceptionDetails for this new fault
Notice no changes required on the service side, all I had to do is on the client. But I did one thing in the service code to throw SOAP exception if numerator equal to 0 (just to be able to test such scenario):
 
           if (numerator == 0)
                throw new System.Web.Services.Protocols.SoapException("Test exception", new XmlQualifiedName("A100"));
 
Dropping input file with 0 as numerator (DivideGenerateSOAPExceptionInput.xml) will create following message in the Fault folder:
<?xml version="1.0" encoding="utf-8"?><MyOperationException xmlns="http://schemas.datacontract.org/2004/07/Service" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
  <Error>&lt;ExceptionDetail xmlns="http://schemas.datacontract.org/2004/07/System.ServiceModel" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"&gt;&lt;HelpLink i:nil="true" /&gt;&lt;InnerException i:nil="true" /&gt;&lt;Message&gt;Test exception&lt;/Message&gt;&lt;StackTrace&gt;   at Service.Operator.Divide(Int32 numerator, Int32 denominator) in C:\Projects\TestBed\Typed Fault Exception Handling\WcfService\Program.cs:line 37
   at SyncInvokeDivide(Object , Object[] , Object[] )
   at System.ServiceModel.Dispatcher.SyncMethodInvoker.Invoke(Object instance, Object[] inputs, Object[]&amp;amp; outputs)
   at System.ServiceModel.Dispatcher.DispatchOperationRuntime.InvokeBegin(MessageRpc&amp;amp; rpc)
   at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage5(MessageRpc&amp;amp; rpc)
   at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage4(MessageRpc&amp;amp; rpc)
   at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage3(MessageRpc&amp;amp; rpc)
   at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage2(MessageRpc&amp;amp; rpc)
   at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage1(MessageRpc&amp;amp; rpc)
   at System.ServiceModel.Dispatcher.MessageRpc.Process(Boolean isOperationContextSet)&lt;/StackTrace&gt;&lt;Type&gt;System.Web.Services.Protocols.SoapException&lt;/Type&gt;&lt;/ExceptionDetail&gt;</Error>
  <Operation>Divide</Operation>
</MyOperationException>

 Note it contains exception detail section with original "Test exception" message.

Another approach would be to use exclusively SOAP faults for all kind of custom and system faults, reducing number of exception handlers in orchestrations to one.  It can be achieved by implementing custom XML Disassembler pipeline component where incoming message would be inspected and if SOAP Fault is detected all properties can be copied to secondary SOAP fault thus preserving all original exception details.

Source code of modified SDK sample is available here.


Wednesday, June 11, 2008 #

Choice of configuration settings storage is an important topic when it comes to enterprise BizTalk application planning. One of the many options is to use regular .Net configuration files. Some prefer this way over the Enterprise SSO database option for reasons of simplicity and familiarity. I wanted to show how it can be done with Microsoft Enterprise Library configuration application block.

In this case Enterprise Library configuration section is placed in the BTNTSvc.exe.config file while application settings are stored in a separate configuration file. The problem here is how do we make EntLib to load required settings file at runtime.  Its done by simple helper class CustomSettings that looks up registry entry for the location and name of the application configuration file and creates FileConfigurationSource with it. The registry entry can be created  by MSI installation package.

The initialization method of this static helper class looks like this (thread synchronization code omited for brevity):

          RegistryKey regKey = null;

          try
          {
             regKey = Registry.LocalMachine.OpenSubKey(@"Software\MyCompany\MyApplication");
             configurationFile = Path.Combine(
             (string)regKey.GetValue("ConfigDir"),
             (string)regKey.GetValue("ConfigFile"));

              ExeConfigurationFileMap fileMap = new ExeConfigurationFileMap();
              fileMap.ExeConfigFilename = configurationFile;
              configuration = ConfigurationManager.OpenMappedExeConfiguration(fileMap, ConfigurationUserLevel.None);
              configurationSource = new FileConfigurationSource(configurationFile);

              //- store configPath in the settings
              configuration.AppSettings.Settings.Add(
              new KeyValueConfigurationElement("configDir", (string)regKey.GetValue("ConfigDir")));
           }
           catch (Exception ex)
           {
              Debug.WriteLine("Exception while initializing Settings:" + ex.ToString());
              throw ex;
           }
           finally
           {
              if (regKey != null) regKey.Close();
           }

 Then it has method to access properties by name:

        public static string GetValue(string name)
        {
            KeyValueConfigurationElement entry = Configuration.AppSettings.Settings[name];

            if (entry == null)
                throw new ConfigurationErrorsException("Key '" + name + "' is not found in the configuration file.");

            return Configuration.AppSettings.Settings[name].Value;
        }

 Which is used as in:

string propertyValue = CustomSettings.GetValue("propertyName");

 


Monday, June 09, 2008 #

If you store BizTalk application settings in Enterprise SSO database and adapt continuous integration you'll find this MS Build task useful. DeploySSOConfigStore task reads settings from XML configuration file and saves them to the SSO database. The XML can be created (exported) using Richard Seroter's SSO tool which I modified to support this operation. So, if you change your configuration settings, just update XML file in the source control and build process will pick it up and propagate changes to your target environment. Using this task is very simple, it accepts just one parameter - location of the XML settings file:

    <UsingTask AssemblyFile="CustomMSBuildTasks.BizTalk.dll" TaskName="CustomMSBuildTasks.BizTalk.DeploySSOConfigStore"/>

    <Target Name="DeploySSOConfigStore" DependsOnTargets="BizTalkDeploy">
        <CustomMSBuildTasks.BizTalk.DeploySSOConfigStore XmlConfigurationUrl="$(SolutionRoot)\$(BuildBranch)\Source\BizTalkSettings\BizTalk.Configuration.xml"/>
    </Target>

I also added another useful task for publishing WCF services: PublishBizTalkWcfServices. It's very simple but does all that stuff: setting up virtual directories (if needed), publishing contracts, creating receive locations. It has comprehensive logging that helps tracking down deployment issues quickly.

    public class PublishBizTalkWcfServices : Task
    {
        private string serviceDescriptionUrl;

        /// <summary>
        /// The URL to the description file.
        /// </summary>
        [Required]
        public string ServiceDescriptionUrl
        {
            get { return serviceDescriptionUrl; }
            set { serviceDescriptionUrl = value; }
        }

        /// <summary>
        /// Publishes BizTalk schemas as WCF services.
        /// </summary>
        /// <returns></returns>
        public override bool Execute()
        {
            WcfServiceDescription description = WcfServiceDescription.LoadXml(ServiceDescriptionUrl);

            Publisher publisher = new Publisher();
            publisher.BackgroundWorker = new System.ComponentModel.BackgroundWorker();
            publisher.BackgroundWorker.WorkerReportsProgress = true;
            publisher.BackgroundWorker.ProgressChanged += new System.ComponentModel.ProgressChangedEventHandler(BackgroundWorker_ProgressChanged);

            try
            {
                PublishingResults results = publisher.Publish(description);

                Log.LogMessage(results.Message);
            }
            catch (Exception ex)
            {
                Log.LogError(ex.ToString(), null);
                return false;
            }
            return true;
        }

        public void BackgroundWorker_ProgressChanged(object sender, System.ComponentModel.ProgressChangedEventArgs e)
        {
            Log.LogMessage(e.UserState.ToString(), null);
        }
    }

Complete Visual Studio project is available here.


Friday, May 16, 2008 #

Richard Seroter published useful utility to store/retrieve BizTalk configuration settings in Enterprise SSO database. I added some more functionality to it.

First, I added Export/Import functions allowing to actually save values in the external XML file (Richard's implementation allowed storing only field names, calling for extra work should you delete and restore SSO aplication). I kept file format the same but added values stored as text nodes:

 

<sso>
  <application name="eCommerce.BizTalk.Configuration">
    <description>Configuration storage for eCommerce BizTalk applications</description>
    <appUserAccount>BizTalk Application Users</appUserAccount>
    <appAdminAccount>BizTalk Server Administrators</appAdminAccount>
    <contactInfo>http://geekswithblogs.net/paulp</contactInfo>
    <field ordinal="0" label="App1.SiteId" masked="no">2</field>
    <field ordinal="1" label="App1.UserId" masked="no">8</field>
    <field ordinal="2" label="App2.Timeout" masked="no">5000</field>
    <flags configStoreApp="yes" allowLocalAccounts="yes" enableApp="yes" />
  </application>
</sso>

 

New usage scenarios:

1. Saving configuration:

1.1 Create application; 
1.2 Save to XML; 
1.3 Manage application - Retrieve settings; 
1.4 Export to XML file created in (1.2).

2. Restoring previously saved configuration: 

2.1 Create application;
2.2 Manage application - Import configuration from file created in (1.4) ; 
2.3 Save Changes.

Accordingly, menu items Import/Export available when the Manage Application tab is selected only.

The second part of improvement is where I failed. I added "Select Application" button on the Manage Application tab to browse for available applications in SSO database. Currently, it won't find any ConfigStore application it will come back with "No Applications Found" message. I left this button on intentionally hoping that some would help me to solve this problem. It appears that neither ISSOMapper nor ISSOMapper2 return list of ConfigStore type SSO application. This API call will return application of types Individual, Group or Host Group but not ConfigStore which is the one we create using this tool. At least with the SSOFlag combination being set (0x140006). I tried different flag combinations but could not make it work. So please let me know.

The code can be downloaded here.


Monday, February 04, 2008 #

I uploaded source code for the generic BizTalk message broker described in this post.

Wednesday, January 30, 2008 #

Fellow pilot and talented IT professional, David Megginson, put out neat web site http://www.ourairports.com that allows to leave comments about airports visited (as pilot or passenger), and share personal maps. I added those where I landed as a pilot from the logbook and it shows how I definitely need some real "cross country" trips :)


Thursday, January 24, 2008 #

This time we'll combine loosely-typed BizTalk messaging concept with dynamic mapping to create generic transformation orchestration. Then we'll add it to the message broker created in the first article to make it even more powerful.

Quite often message dispatching task must be combined with some content adaptation: data mapping, filtering, calculation, etc. Generally, maps are used in BizTalk to achieve that. Since maps are based on source and destination schemas we need to find a way to abstract transformation process from specific schema type to common XmlDocument. What makes it possible is XLANGs transform command. This command accepts output message as argument and uses System.Type of BizTalk map. It allows us dynamically load and apply any map type to any message type.

First, we have to author schemas and maps. I created two input schemas for this project: NewEmployee.xsd, NewOrder.xml; two output schemas: Employee.xsd, Order.xsd; and two corresponding maps: NewEmployee_to_Employee, NewOrder_to_Order.I did some changes in DispatchMessage orchestration to integrate with TransformMessage.odx. I added new message variable msgTransformed which is an output from transformation orchestration. Then I simply added Decide shape to check if transformation required for incoming fie. If yes, we pass map key and input message to the TransformMessage.odx using CallOrchestration shape. If no mapping provided then we simply copy incoming message content to msgTransformed:

Again, to retrieve required map name at runtime, we're going to use the same key-value pairs in the configuration file. In our example we used file name as the key so let's follow this trend. If we're gonna store these keys in the same configuration file we need to modify them to keep unique, for example by adding ".MAP" prefix, i.e. <add key="NewEmployee.xml.MAP" value="GenericBiztalkPatterns.Maps.NewEmployee_to_Employee" />.

To keep things clean and reusable I factored out mapping functionality in separate TransformMessage.odx. orchestration. This orchestration contains three parameters: msgInput, mapKey, out msgOutput which are self explanatory. Notice, all message parameters are of XmlDocument type again. The orchestration id very simple itself. Read fully qualified map name by passed in key and get Type of this map:

mapTypeQName = System.Configuration.ConfigurationManager.AppSettings[mapKey];
mapType = System.Type.GetType(mapTypeQName);

Then in ConstructMessage shape just apply transformation:

transform(msgOutput) = mapType(msgInput);

That's how it looks in designer:

I did some changes in DispatchMessage.odx to integrate with TransformMessage.odx. There is new message variable msgTransformed which is going to be an output from transformation. Also, I added Decide shape to check if mapping required. If yes, then input message and map key is passed into TransformMessage.odx through CallOrchestration shape. Ohterwise, input message simply copied to msgTransformed:

 

Now, all we need is to make sure there're proper entries in the configuration file:

  <add key="NewEmployee.xml" value="file://C:\Projects\Design Patterns\GenericBizTalkPatterns\GenericBizTalkPatterns.MessageBroker\Ports\File.out\employee.xml" />
  <add key="NewEmployee.xml.MAP" value="GenericBizTalkPatterns.MessageBroker.Maps.NewEmployee_to_Employee, GenericBizTalkPatterns.MessageBroker, Version=1.0.0.0, Culture=neutral, PublicKeyToken=44e796208f4e4812" />
  <add key="NewOrder.xml" value="ftp://[Your FTP server path here]/order.xml" />
  <add key="NewOrder.xml.MAP" value="GenericBizTalkPatterns.MessageBroker.Maps.NewOrder_to_Order, GenericBizTalkPatterns.MessageBroker, Version=1.0.0.0, Culture=neutral, PublicKeyToken=44e796208f4e4812" />
  <add key="FTPUserName" value="[Your FTP username]" />
  <add key="FTPPassword" value="[You FTP password]" />
  <add key="FTPMode" value="true" />
  <add key="FTPRepresentationType" value="Binary" />
  <add key="FTPCommandLog" value="C:\Projects\Design Patterns\GenericBizTalkPatterns\GenericBizTalkPatterns.MessageBroker\ftp.log" />
  <add key="FTPBeforePut_NewOrder.xml" value="" />
  <add key="NotRoutedDestination" value="file://C:\Projects\Design Patterns\GenericBizTalkPatterns\GenericBizTalkPatterns.MessageBroker\Ports\backup" />
  <add key="NotSuportedProtocolDestination" value="file://C:\Projects\Design Patterns\GenericBizTalkPatterns\GenericBizTalkPatterns.MessageBroker\Ports\backup" />

At some point, configuration file can get quite large, that's why I prefer to keep group of settings in individual configuration files, like destinations.config, maps.config, etc., but that's a topic for another article. So far, we've seen how generic messaging concept can help creating flexible agile BizTalk applications. [Source code is available]


Tuesday, January 15, 2008 #

Although current BizTalk release does not support .NET generics it does support concept of genericity at the message level. It is possible, of course, through "untyped" messages or messages that don't have specific type attached to their context. Such messages are represented as System.Xml.XmlDocument type in orchestrations. To read more on message typing aspect of BizTalk please refer to this excellent post by Charles Young. I'd like show practical examples of applying generic programming to typical BizTalk tasks. These examples will help to make your BizTalk solutions leaner, more flexible, and easier to maintain.

Let's start with very common pattern where BizTalk is used as merely message dispatcher - Message Broker. The goal of our generic message broker will be to dispatch any type of message to its destination decided at runtime. So, by avoiding type dependency and early routing binding (basically, hardcoding) we would get single very flexible orchestration which can easily handle requirement changes - one of the top goals of good application design.

Some things to consider before creating BizTalk orchestration. First, we need to figure out how and where to store input to output destination map. Since, it's just key-value pairs, application configuration file will work fine. In my work, I prefer to keep collections of homogenous setting like this in separate xml config files and read them in custom XmlSerializable map (objects like NameValueCollection or Dictionary being non XmlSerializable won't work in orchestration unless placed inside atomic transaction scope). I'll place them in application configuration file now to keep it simple. Next thing, is to decide what to use as a key. Since messages all of the same type to orchestration, we should select something that uniquely maps message to destination. Let's assume that per our requirements it's a input file name, although it can be anything inside message content. The destination will be expressed as complete URL, i.e. [protocol]://[path]/[fileName], for example ftp://myserver/files/dest.dat. So, the map entry can look like this: <add key="emp.dat" value=file://myserver/incoming/employees.dat />. I use .dat extension here just to emphasize it's applicable to any files and not only XML.

Once questions have been answered, the orchestration comes together fairly quickly. On the global level it has one input port, expression to read routing configuration, Decide shape to branch on routing availability condition, and dynamic send port. Receive shape receives input message of XmlDocument type. Output message of the same type is sent through dynamic send port after routing address and all properties have been set:

DispatchMessage orchestration high level view

Inside Decide_IfRoutingAvailable shape there are two branches of execution. If routing address is found then it will read destination URL from the configuration and select transport protocol. Otherwise, destination will be set to the backup location from configuration file in order for the message to be preserved:

Once protocol is identified, we can create output message and set its properties and destination address. In case if protocol is not supported, we route message to the backup location using the same dynamic send port. That's how it looks inside Choose Protocole decide shape:

DispatchMessage - select protocol

Let's dive into ConstructFtpMessage shape, to see how properties set:

msgOutput = msgInput;
msgOutput(FTP.UserName) = System.Configuration.ConfigurationManager.AppSettings["FTPUserName"];
msgOutput(FTP.Password) = System.Configuration.ConfigurationManager.AppSettings["FTPPassword"];
msgOutput(FTP.PassiveMode) = System.Convert.ToBoolean(System.Configuration.ConfigurationManager.AppSettings["FTPMode"]);
msgOutput(FTP.RepresentationType) = System.Configuration.ConfigurationManager.AppSettings["FTPRepresentationType"];
msgOutput(FTP.BeforePut) = System.Configuration.ConfigurationManager.AppSettings["FTPBeforePut_" + receivedFileName.ToUpper()];
msgOutput(FTP.CommandLogFileName) = System.Configuration.ConfigurationManager.AppSettings["FTPCommandLog"];

First line simply copies our generic input message content to the output message. Susequent lines set FTP properties from configuration file. Then, all that left is to set destination URL on dynamic port and send output message through it:

DestinationSendPort(Microsoft.XLANGs.BaseTypes.Address) = destUrl.ToString();

As the result, we have a single orchestration that can handle hundreds of different message schemas and multiple protocols. Another positive outcome is that deployment greatly simplified. The application has one orchestration, two ports, but only one of them is bound at deployment time. Also, note no schemas, no maps at this time, we will add them later when we augment application with content transformation functionality.


Tuesday, January 01, 2008 #

Happy New Year! It's good time to summarize flying experience as year past since getting pilot certificate. So, here's some dry statistics:

Hours total 105.7
Night 10.3
Cross country 28.1
Actual Instrument 0.1
Simulated Instrument 11.8
Day landings 270
Night landings 39
Instrument approaches 5

Airplanes I've flown: Cessna C152, Cessna C172M, Cessna C172SP, Cessna C177RG, Piper PA28-161, Piper PA28-181, Evektor Sportstar, Vans RV-7A, Wheeler Express.

Airports I landed at: PAE (Everett, WA), AWO (Arlington, WA), BLI (Bellingham, WA), OLM (Olympia, WA), RNT (Renton, WA), CLM (Port Angeles, WA), S43 (Snohomish, WA), W16 (Monroe, WA), GPM (Grand Prairie, TX), ADS (Addison, TX), RBD (Dallas, TX), TKI (McKinney, TX), XBP (Bridgeport, TX), SEP (Stephenville, TX), FTW (Fort Worth, TX), CRS (Corsicana, TX), JWY (Midlothian, TX), LNC (Lancaster, TX), GKY (Arlington, TX), UTS (Huntsville, TX), GLS (Galveston, TX), JXI (Gilmer, TX), T31 (McKinney, TX), SWI (Sherman, TX), DTO (Denton, TX), GYI (Sherman/Denison, TX).

One thing I don't want to add up though is the amount of money spent on flying :)

I started instrument training and logged some simulated time under the hood. So, the new year flying resolutions will be to finish instrument training and get that rating, log at least 60 hours total time, get tailwheel endorsement, get high performance and/or complex endorsements. This should keep me pretty busy, I guess. :) Happy flying in New Year!


Tuesday, December 18, 2007 #

If you receive error:

COM object that has been separated from its underlying RCW cannot be used.
The possible cause is invalid or malformed per instance configuration data.

You most likely ran into bug (or undocumented feature) of BizTalk SDK. The problem won't show up until you edit value of component custom properties during deployment configuration. The reason is described below.

When you create custom pipeline component with properties you will override ReadPropertyBag(Microsoft.BizTalk.Component.Interop.IPropertyBag pb, string propName) method of base component class (for example FFDasmComp). In your implementation you must call base.Load(IPropertyBag propertyBag, in errLog) method. The problem stems from implementation this method. It uses DisposableObjectWrapper declared in using{} scope to keep reference of IPropertyBag object. DisposableObjectWrapper is a simple wrapper to handle objects that have to be disposed. In it's own Dispose() method it will call Dispose() of it's member or if the member is a COM object it will call Marshal.ReleaseComObject(object obj). Look what happens if you implemented your ReadPropertyBag method like this:

private object ReadPropertyBag(IPropertyBag pb, string propName)
{  
  base.Load(pb, 0); //<- when this method returns reference to COM object will be lost
  object val = null;
  try
  {
    pb.Read(propName, out val, 0);//<- this line will throw COM Interop exception
  }
  catch (System.ArgumentException)
  {
    return val;
  }
}

To work around this issue just call base.Load() after all your calls to IPropertyBag. This will work fine:

pb.Read(propName, out val, 0);
base.Load(pb, 0)

Tuesday, November 13, 2007 #

Simple trick that saves me some time and coding when writing custom configuration objects is to define generic collection for them. Let's say I created several custom configuration elements derived from my CustomConfigElementBase which in turn inherits from System.Configuration.ConfigurationElement. Instead of defining strongly typed collections for each type I can simply define class:

public class GenericConfigElementCollection<T> : ConfigurationElementCollection

where T : CustomConfigElementBase, new()

{

  public T this[int index]

  {

    get

    {

      return base.BaseGet(index) as T;

    }

    set

    {

      if (base.BaseGet(index) != null)

       base.BaseRemoveAt(index);

      base.BaseAdd(index, value);

    }

  }

  protected override ConfigurationElement CreateNewElement()

  {

    return new T();

  }

  protected override object GetElementKey(ConfigurationElement element)

  {

    return ((T)element).ElementKey;

  }

}

 

Note, that base custom configuration element has property ElementKey to satisfy GetElementKey method implementation. If your custom elements have different keys, you can override this property. It's possible to skip CustomConfigElementBase and define generic collection with ConfigurationElement type restriction. GetElementKey implementation would use reflection to retreive key property:

public class GenericConfigElementCollection<T> : ConfigurationElementCollection

where T : ConfigurationElement, new()

{

  protected override object GetElementKey(ConfigurationElement element)

  {

    //- find and return property which is a key for a given type T using reflection

    ...

  }

}

Now, when we define configuration section we just return generic collection of appropriate type:

 

    public class MyCustomConfigSection : ConfigurationSection

    {

        [ConfigurationProperty("myCustomSettings", IsRequired = true)]

        public GenericConfigElementCollection<MyCustomConfigElement> MyCustomSettings

        {

            get

            {

                return this["myCustomSettings"]

                  as GenericConfigElementCollection<MyCustomConfigElement>;

            }

        }

    }

And then just use it in an application code:

GenericConfigElementCollection<MyCustomConfigElement> configCollection = configSection.MyCustomSettings;

MyCustomConfigSection configSection = (MyCustomConfigSection)ConfigurationManager.GetSection("mySection");

 


Friday, October 19, 2007 #

Recently, I had to create an orchestration that transfers files to the IBM mainframe running z/OS (also called sometimes MVS - Multiple Virtual Storage because of the legacy). The orchestration used dynamic FTP port to correlate incoming messages to the destination. During this exercise I learned something about FTPing to the mainframe with BizTalk adapter.

First, flat files obviously must be passed in ASCII mode so the FTP server is able to convert them to EBCDIC format. Passed in binary mode files will not be readable as text files since these two formats have incompatible code pages. To ensure proper conversion set FTP.RepresentationType to "ASCII" on the message context.

Second, records in files must be separated with CR LF not just LF as I was getting from UNIX system. Otherwise, only one record was transferred. You'd likely get a message like "250 Transfer completed (data was truncated)".

Third, record size must be passed to the mainframe system. This is done by setting FTP.BeforePut property to command options like: SITE DCB LRECL=50 RECFM=FB where LRECL sets the record size of 50 characters.

Finally, on some MVS datasets we experienced data truncation even if the LRECL was set properly when record size in flat file exceeded 80 characters. Those unlucky files kept getting truncated to 80 characters per records (which is default value for z/OS) until I sent command WRAPRECORD. So, for such cases setting FTP.BeforePut to something like "SITE DCB LRECL=600 RECFM=FB WRAPRECORD" did the trick.


Wednesday, June 06, 2007 #

I took son and a friend to Mid-Way Regional airport for the annual breakfast pancake fly-in. It was second time my older son was up in the small plane and he coped very well with his fear he had on the first flight and was really excited. We got there a bit late and missed racing cars but we saw all planes, toured into DC-3, watched T-28B Trojan and T-6 Texan take-offs and other good things. On the way back we landed at Lancaster, had a lunch and sat in the L-29 cockpit owned by local pilot. One day I want to fly one.


 I've seen developers stumble across this error. Complete message can look like this:

A message received by adapter "FILE" on receive location "Receive LocationTransform1" with URI "\\server001\\File.IO\ReceiveLocation1\*.*" is suspended.

Error details: There was a failure executing the receive pipeline: "MyApplication.ReceivePipeline, MySolution.Application.Pipelines, Version=1.0.0.0, Culture=neutral, PublicKeyToken=5cf43733eafbdceb" Source: "FlatFileDisassembler" Receive Port: "ReceivePortTransform" URI: "\\server001\\File.IO\ReceiveLocation1\\*.*" Reason: The Disassembler cannot retrieve the document specification using this name: "MySolution.MyApplication.Schemas.Alpha,MySolution.MyApplication.Schemas,Version=1.0.0.0,Culture=neutral,PublicKeyToken=5cf43733eafbdceb".

One typical scenario to receive this error is deploying new schema into BizTalk database and forgetting to install it into the GAC. For single server configuration it is easily avoided if assemblies are added to MSI with option GacOnImport. For multi-server deployment GacOnImport won't be enough as it only installs assembly in the GAC of one box. You still need to run MSI on every box in the group so the GacOnInstall do the job.

Another case when you get to see this message is if one of host instances still using old version of the updated assembly. Restarting host instance(s) should easily take care of the problem. But I've often seen developers stuck in frustration. This can happen on large solutions with complex deployment structure with multiple applications, hosts and servers. Consider two servers BizTalk group where 3 applications are deployed. Each application uses its own host which in turn has two instances (one per server). In total we have 6 host instances in this group. When one of the applications is updated its host instances should be restarted without stopping other's applications hosts.

If applications use the same adapters (i.e. File adapter) and if per application adapter hosts were not assigned then they will be sharing common host for send/receive operations and in most cases it will be default BizTalkApplicationHost. In this situation it's easy to miss restarting required host instance. To avoid this when sharing BizTalk group with multiple applications always assign your dedicated host for send and receive handlers and include corresponding host instances restart operation into post-deployment procedure.

To create adapter host open Platform Settings/Adapters of the BizTalk group administration. Select adapter and add your host as new send and receive handler to adapter configuration. Then, in application configuration, go to send port or receive location properties and select your host from send or receive handler dropdown.


Tuesday, May 29, 2007 #

I don't know why this site changed it's good ol' name from The Daily WTF to "politically correct" WorseThanFailure but I wouldn't do it in my case. I happen to perform code reviews on Java to .Net migration recently and in both I find enough "programming pearls" to keep me saying WTF every day for weeks. I thought about posting some of them and sharing the link with creators of such masterpieces as I may guess who's behind. They might get angry at me but it's for good to let them learn from their mistakes (if capable).

There's a Russian book for children by Gregory Oster. The title translates like Harmful Advice for Naughty Children and Their Parents. Unfortunately, it's not been translated in English yet. It's filled with sarcastic rhymes and stories that will make your abs sore of laughing. The only English reference to this book I found is here. The book "teaches" kids to do bad things in quite humorous fashion.

I compiled some "tips" from code reviews in the manner of "The Harmful Advice: For Lamer Programmers and Wannabies". Code is stripped and de-personated so no harm to business only to developer's ego perhaps:

1. Whenever you use ADO.NET open as many DataReaders as possible and do not close them when done. Better yet, open several of them in nested loops. It's OK to keep connections open, that's what connection pools for, right?

2. When you get the data back in the DataReader, religiously check the data field for null:

    if(dataReader[0] != null)
{ myVal = dataReader[0];
}

Sometimes, it's good to double check later down the road:

    if(myVal != null){}

It's all about defensive programming. And never trust DbNull and IsDbNull method of the DataReader.

3. Don't rely on reference types (.NET), they are evil. In most important cases pass them using ref keyword:

    SomeMethod(int param1, string param2, ref SomeClass imfeelingspecial);

4. Value types in .Net are more useful than you'd think. On a good day they can pass your data in and out just as easy as reference types:

SomeStruct commonInfo = new SomeStruct();
commonInfo.Field = “Let's see.”;
...
FoodProcessor proc = new FoodProcessor(commonInfo);
proc.MunchData(); //- munches data and re-assigns commonInfo fields
...
if(commonInfo.Field == "Expected Value From FoodProcessor")
{// <- lots of prayers needed to make it here!
}
else
{//- Doh! It always comes here anyways...
}

5.  Do not trust SQL select results, it's not always obvious how many columns it returns. Make a habilt of checking the columns count in the resordset:

string sqlQuery = "SELECT column1, column2, column3 from SomeTable WHERE column4 = " + someVal;                                 
...                                     
 if (reader.Read())
{
     int fcnt = reader.FieldCount;
     if (fcnt >= 1)
     {
         var1 = reader.GetString(0);
     }
     if (fcnt >= 2)
     {
         var2 = reader.GetString(1);
     }
}

6. For money calculation always use low precision floating data types:

Float itmPrice = new Float(getIn().getRecords(count).getItemPrice());
Float itmQty = new Float(getIn().getRecords(count).getItemQuantity());
float exPrice = itmPrice.floatValue() * itmQty.floatValue();
extendPrice = extendPrice.valueOf(exPrice);

So, in this Java example, for itmPrice = 2.66 and itmQty=995 you will get a nice special price of 2646.7002. Decimal arithmetic is for pedants.

7. Always warm up the CPU before performing date calculations. This can be achieved by using many variables of different types and converting them back in forth to each other:

      Calendar myCal = Calendar.getInstance();    
      myCal.setTime(myDate);
      Float fltTermNbr = new Float(newSrvcTerm);
      Float sevenDays = new Float("7");
      float fltTermNbrInDays = fltTermNbr.floatValue() * sevenDays.floatValue();
      String strTermNbrInDays = "";
      strTermNbrInDays = strTermNbrInDays.valueOf(fltTermNbrInDays);
      strTermNbrInDays = strTermNbrInDays.substring(0, strTermNbrInDays.indexOf("."));
      Integer IntegerTermNbrInDays = new Integer(strTermNbrInDays);
      int intTermNbrInDays = IntegerTermNbrInDays.intValue();
      myCal.add(myCal.DAY_OF_YEAR, intTermNbrInDays);

Just for us, lazy ones, all the Java code above is trying to do is:

             int termNbrInDays = Convert.ToInt32(newSrvcTerm) * 7;
             myDate = myCal.AddDays(myDate, termNbrInDays);

Still suck at writing bad code? Consider a mentor. Hire a developer with the thickest resume and largest amount keywords in it. At the end, one must be really hardworking to create one. Give him a title he wants: usually Lead Architect or something like this. Let him write the code and teach others. Well, I have to go back and dig up some more jewels...