Author: Romiko Derbynew

BizTalk and Enterprise Library: Logging Custom Components

Hi Folks,

This article is going to discuss how to use Enterprise Library and integrate it with BizTalk Components. The examples in this blog are based on a Pipeline component that I use to import flat files, you can easily convert the pipeline component to a file adapter, but the performance difference is negligible and is just a matter of preference. It is nice to make really robust application, but more importantly efficient caching, logging and exception handling is often missed! I assume the reader has in depth knowledge of Microsoft BizTalk 2006

First I create folders for Caching, Logging and Exception Handling in my current Pipeline Project.

 

 

The Decoders folder has the decoder components for a Pipeline

The Disassemblers folder has the disassemblers components for a Pipeline

The FileTranslator is a custom component that a disassemble component can call to transform flat files into XML using a custom configuration, this is not in scope of this blog and will be treated black box, in future blogs, I will discuss how to write such a component and it is far more powerful than using the BizTalk Mapper, since the BizTalk Mapper is only good for small schema’s when trandforms become complicated and many rules need to be applied, I recommend a custom file translator which you can implement as a File Adapter or Pipeline, does not really matter as long as you handle exceptions correctly and catch them and always treat you files in streams and use a recordline processing model so you never load the entire file in memory.

 

Requisites

Install Microsoft Enterprise Library 3.1 May 2007

http://www.microsoft.com/downloads/details.aspx?FamilyID=4c557c63-708f-4280-8f0c-637481c31718&displaylang=en

 

Logging

We are going to start setting up the Logging mechanism first.

  • Add a reference to the Project to Microsoft.Practices.EnterpriseLibrary.Logging, I like to create an External Assemblies folders, so when deployment occurs, I remember to add it to the GAC. I add the dll from the install folder e.g. D:\Program Files\Microsoft Enterprise Library 3.1 – May 2007\Bin to my own project, that way, external dll’s are never lost.

  • Add a using statement for Microsoft.Practices.EnterpriseLibrary.Logging
  • Create a public, serializable class in the Logging folder and add a method that accepts two strings.

using System;

using Microsoft.Practices.EnterpriseLibrary.Logging;

 

namespace MMIT.BizTalk.Components.Pipelines.Logging

{

 

[Serializable]

public class LogManager

{

public static void Log(string message, string category)

{

Logger.Write(message, category);

}

 

}

}

 

  • The next step is to implement the configuration file for this. Things to note, usually when you have a Windows form application, the app.config file is automatically read, and if you use a web form then the web.config file is used. Now think about it, if we calling an Enterprise Library Component from a pipeline component, neither the app.config or web.config is going to be called, because this is excuted in the App Domain for the BizTalk process, we will need to configure the config file to use the ‘BTSNTSvc.exe.config’, if you ar efamilair with BizTalk, this is the place that BizTalk server reads for configuration as well as custom configuration. This file is located in C:\Program Files\Microsoft BizTalk Server 2006 by default. You can add many things here for example a custom timeout for HTTP connections:

     

    </system.runtime.remoting>

    <system.net>

    <connectionManagement>

    <add address="*" maxconnection="5000"/>

    </connectionManagement>

    </system.net>

     

    </configuration> 

    So, what we going to do is create a new logging application configuration file for Enterprise Library to read, then, we will modify the BTSNTSvc.exe.config to add a reference to the configuration file. So that at runtime the configuration file can still be found. There are many ways to do this, you could even use the SSO database, but for this article I am sticking to simple configuration files. We will store these configuration files in the C:\Program Files (x86)\Microsoft BizTalk Server 2006 folder, excuse my path, I am on a 64 bit OS.

  • Open the Enterprise Library Configuration tool, located at Start\Program Files\Microsoft patterns & practices\ Enterprise Library 3.1 – May 2007 \Enterprise Library Configuration.
  • Open the ‘BTSNTSvc.exe.config’ by clicking File > New Application
  • Right click on the Application Configuration name and add a new Logging Application Block by clicking the file name and select New > Logging Application Block.

  • Select the Trace Listeners and right click on it to select the New > Rolling File Trace Listener.

  • Select Formatter on the right hand pane and select Text Formatter

I like to put all my log files in %windir%\system32\logfiles\<application name>\filename<TimeStampPattern>.log, since many MS application do this, why not make our application do the same to keep log files centrally located. We going to implement a rolling flat file logger that increments

  • Change the FileName of the log file to %windir%\system32\logfiles\BizTalk\Pipeline.log (NOTE: DO NOT USE %WINDIR% use the path C:\…. etc, else EL will append the current execution folder to %windir%)
  • Change the attribute for the RoleFileExistsBehavior to Increment
  • Change the RoleInterval to Day, this will create a new log file per day, similar to IIS
  • Change the TraceOutputOptions to DateTime, so we can get a nice row prefix for the logging

The options set here can be overridden when you hook the listener to a Special Source, which we see later.

This is what my configuration looks like:

 

  • The next stage is to assign the listener. To do so you select Formatted EventLog TraceListener from the Special Sources.
  • On the drop down many assign it the Rolling Flat File Trace Listener

So what we have done is created a listener and then told the configuration to log errors and warnings to this listener. If you click the plus sign you can override the default settings we defined earlier on.

  • Set the Rolling Flat File Trace Listener in the Category Sources\General\Formatted EventLog TraceListener. Select the node and select the Referenced TraceListener to Rolling Flat File Trace Listener from the dropdown.

 

From a configuration perspective we are complete, we configured the Rolling Flat File as the location for logging.

 

We hooked the listener to be called when Logging Errors and Warnings and when the category is General.

The configuration tool from Enterprise Library is extremely customizable, for example we could add a new listener for the event log and create a new category called "Critical" and then hook the event log listener to this category, so that if you call the logger, with a different category, it will log to a different source, based on the configuration. i.e.

Logger.Write(message, category);

 

What is left is that we need to save the configuration to file, in my case, I will save it to:

C:\Program Files\Microsoft BizTalk Server 2006\ MMIT.BizTalk.Components.Logging.config

What we now need to do is add a section to the BTSNTSvc.exe.config that points to the configuration file we created.

  • Open the BTSNTSvc.exe.config file

Be careful adding custom conifgSection to BTSNTSvc.exe.config file: it must be the first child of the root element.

You will get an unfriendly message in the event log:

Event Type:    Error

Event Source:    BizTalk Server 2006

Event Category:    BizTalk Server 2006

Event ID:    5410

Date:        6/2/2008

Time:        7:35:17 PM

User:        N/A

Computer:    V-BIZDEV32I-02

Description:

A failure occurred when executing a Windows service request.

 

Service request: Start

 

BizTalk host name: BizTalkServerApplication

Windows service name: BTSSvc$BizTalkServerApplication

 

Additional error information:

Error code: 0xc0c0153a

Error source: BizTalk Server 2006

Error description: A BizTalk subservice has failed while executing a service request.

 

Subservice: Tracking

Service request: Start

 

Additional error information:

Error code: 0x80131534

Error source: Microsoft.BizTalk.Bam.EventBus

Error description: The type initializer for ‘System.Data.SqlClient.SqlConnection’ threw an exception.

 

For more information, see Help and Support Center at http://go.microsoft.com/fwlink/events.asp.

 

  • Add the following section to the config file

<?xml version="1.0" ?>

<configuration>

<configSections>

<section name="enterpriseLibrary.ConfigurationSource" type="Microsoft.Practices.EnterpriseLibrary.Common.Configuration.ConfigurationSourceSection, Microsoft.Practices.EnterpriseLibrary.Common, Version=3.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />

</configSections>

<enterpriseLibrary.ConfigurationSource selectedSource="File Configuration Source: MMIT.BizTalk.Components.Logging.config">

<sources>

<add name="File Configuration Source: MMIT.BizTalk.Components.Logging.config" type="Microsoft.Practices.EnterpriseLibrary.Common.Configuration.FileConfigurationSource, Microsoft.Practices.EnterpriseLibrary.Common, Version=3.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" filePath="C:\Program Files\Microsoft BizTalk Server 2006\MMIT.BizTalk.Components.Logging.config" />

</sources>

</enterpriseLibrary.ConfigurationSource>

 

  • So the entire configuration file for the BTSNTSvc.exe.config looks like this (assuming you don’t have any other custom stuff in it, you might do!)

<?xml version="1.0" ?>

<configuration>

<configSections>

<section name="enterpriseLibrary.ConfigurationSource" type="Microsoft.Practices.EnterpriseLibrary.Common.Configuration.ConfigurationSourceSection, Microsoft.Practices.EnterpriseLibrary.Common, Version=3.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />

</configSections>

<appSettings>

<add key="StagingDataManagementWS" value="http://localhost/Workflow/StagingGateway/StagingDataManagementWS.asmx"/&gt;

</appSettings>

<enterpriseLibrary.ConfigurationSource selectedSource="File Configuration Source: MMIT.BizTalk.Components.Logging.config">

<sources>

<add name="File Configuration Source: MMIT.BizTalk.Components.Logging.config" type="Microsoft.Practices.EnterpriseLibrary.Common.Configuration.FileConfigurationSource, Microsoft.Practices.EnterpriseLibrary.Common, Version=3.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" filePath="C:\Program Files\Microsoft BizTalk Server 2006\MMIT.BizTalk.Components.Logging.config" />

</sources>

</enterpriseLibrary.ConfigurationSource>

 

<runtime>

<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">

<probing privatePath="BizTalk Assemblies;Developer Tools;Tracking;Tracking\interop" />

</assemblyBinding>

</runtime>

 

<system.runtime.remoting>

<channelSinkProviders>

<serverProviders>

<provider id="sspi" type="Microsoft.BizTalk.XLANGs.BTXEngine.SecurityServerChannelSinkProvider,Microsoft.XLANGs.BizTalk.Engine" securityPackage="ntlm" authenticationLevel="packetPrivacy" />

</serverProviders>

</channelSinkProviders>

 

<application>

<channels>

<channel ref="tcp" port="0" name="">

<serverProviders>

<provider ref="sspi" />

<formatter ref="binary" typeFilterLevel="Full"/>

</serverProviders>

</channel>

</channels>

</application>

</system.runtime.remoting>

<system.net>

<connectionManagement>

<add address="*" maxconnection="5000"/>

</connectionManagement>

</system.net>

</configuration>

Notice I have PublicKeyToken in the reference here above, so that it forces to look in the GAC!

The option in read is other customizing not needed here, but put in for additional info.

 

So after making these configuration files, you can quickly review them to have a look, I sorted my BizTalk folder my date modified to get easy access to them.

 

I now add some log calls in my custom pipeline component, so I open my disassemble component and add some logging e.g.

 

This particular disassemble component is called by a custom pipeline, in a separate BizTalk project, you will notice that my component project is just standard c# project, so the Pipeline will look in the GAC for the custom component, here it is:

So I hope this brings back a memory, the Pipeline is executed in a Receive Location like this:

I now drag my component DLL to the GAC, restart BizTalk services etc and the pipeline will now execute with the updated component J

 

Now, what I do is compile my custom pipeline component and deploy the component to the GAC, and restart the BizTalk application and Host Instance that uses this custom Pipeline component, here is a picture of my pipeline that calls this custom component. You do not need to recompile BizTalk projects, just the dll for the pipeline component!

 

I can now set breakpoints in the custom pipeline component, so that when the pipeline executes it will hit the breakpoint, what you do is attached to the BTSNTSvc.exe services, Im too lazy to find out which one here below, you may have other host instances running:

 

NOTE: Make sure you attach to Biztalk After you deployed the dll to the GAC and then restarted the host instances in that order (Only instances using the pipeline, in my case only one service to restart)

If you BizTalk service cannot restart you probably got an error in the BTSNTSvc.exe.config

 

To see if my logger works (You can create a test project for this, but then your configuration will need to be in an app.config file, we will do this later, since I like test driven development.

So now, we drop a file in the receive location that is configured to run the pipeline.

 

Above is the file receive location, with the custom pipeline.

 

Ok, I now drop a file at the receive location and then it hits the break point.

 

I get an error!

 

What you need to do here is ensure this assembly is in the GAC

  • Microsoft.Practices.EnterpriseLibrary.Logging.dll

So drag the dll into the GAC or use your tool GACUTIL and then RESTART the host instance, since it is on host start up that the Microsoft.Practices.EnterpriseLibrary.Logging.dll will be loaded in memory and cached

After doing this, my step into works

 

However, I now get a new error:

The inner exception has this:

 

So we need to add this to the GAC to:

 

Ok, restart instance, attached to process and off we go again, fun debugging pipelines!

To cut a long story short, you can debug the problems with Enterprise Library and figure out the problem, in all these assemblies need to be in the GAC:

  • Microsoft.Practices.ObjectBuilder.dll
  • Microsoft.Practices.EnterpriseLibrary.Logging.dll
  • Microsoft.Practices.EnterpriseLibrary.Common.dll

But before we get ahead of ourselves, I get another exception.

If you get this, ensure the BizTalk account has permission to the folder and create the folder BizTalk at the location of your log file or whatever you chose to use!

Now I get my log file!

 

And if we open it:

—————————————-

Timestamp: 6/2/2008 6:44:28 PM

Message: Started File Import:

Category: General

Priority: -1

EventId: 1

Severity: Information

Title:

Machine: V-BIZDEV32I-02

Application Domain: DefaultDomain

Process Id: 5340

Process Name: C:\Program Files\Microsoft BizTalk Server 2006\BTSNTSvc.exe

Win32 Thread Id: 4676

Thread Name:

Extended Properties:

—————————————-

—————————————-

Timestamp: 6/2/2008 6:44:28 PM

Message: Started File Import: D:\Data\Feeds\POST_SPS\GMUK\VXUK\CAPGEMINI\OSC\TESTIGNORE.txt

Category: General

Priority: -1

EventId: 1

Severity: Information

Title:

Machine: V-BIZDEV32I-02

Application Domain: DefaultDomain

Process Id: 5340

Process Name: C:\Program Files\Microsoft BizTalk Server 2006\BTSNTSvc.exe

Win32 Thread Id: 4676

Thread Name:

Extended Properties:

—————————————-

 

That’s it really, so you can add logging calls to your program and can even create other logging categories instead of general etc, so above I might as well add the file import end log and call it again when the file import is complete, totally up to you!

There is one problem left, you notice that a timestamp was not created and appended to the file!!!

However, I waited an entire day (just kidding, changed the time on the computer on my DEV machine) and get this when I dropped another file in the receive location:

 

Ok, that’s it for now. Don’t forget to add the other assemblies to the referenced assemblies, so when you have them in source safe, you remember to deploy them to the GAC!

  • Microsoft.Practices.ObjectBuilder.dll
  • Microsoft.Practices.EnterpriseLibrary.Logging.dll
  • Microsoft.Practices.EnterpriseLibrary.Common.dll

 

 

In later blogs we will have a look at how we can leverage caching to optimize BizTalk custom assemblies.

T-SQL and XML Data Query

Hi Folks,

I am going to show how to query data from a SQL Table based on values in an XML Data Column.

Here is the SQL Script:

Use TestXml
go

 

USE [TestXML]
GO
/****** Object:  Table [dbo].[XmlTest]    Script Date: 12/26/2008 22:44:44 ******/
IF  EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[XmlTest]’) AND type in (N’U’))
DROP TABLE [dbo].[XmlTest]
GO
/****** Object:  Table [dbo].[XmlTest]    Script Date: 12/26/2008 22:44:44 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[XmlTest]’) AND type in (N’U’))
BEGIN
CREATE TABLE [dbo].[XmlTest](
    [ID] [int] IDENTITY(1,1) NOT NULL,
    [XMLData] [xml] NULL,
    [Country] [nvarchar](50) NULL
) ON [PRIMARY]
END
GO

insert into Xmltest
(
XMLData,
Country
)
values
(‘<Regions  xmlns="http://schemas.romiko.com/Regions.xsd">
    <Natal Capital="Pietermaritzburg">Surfing</Natal>
    <EasternCape Capital="Bhisho">Hiking</EasternCape>
    <WesterCape Capital="Cape Town">Fishing</WesterCape>
    <Gauteng Capital="Johannesburg">Partying</Gauteng>
    <Mpumalanga  Capital="Nelspruit">Adventure</Mpumalanga>
    <Limpopo  Capital="Polokwane">Safari</Limpopo>
    <NorthernCape Capital="Kimberley">Mining</NorthernCape>
    <FreeState Capital="Bloemfontein">Hiking</FreeState>
</Regions>’,’South Africa’)

Go

WITH XMLNAMESPACES(DEFAULT ‘http://schemas.romiko.com/Regions.xsd’ )
SELECT XMLData.value(‘(/Regions/Natal)[1]’, ‘VARCHAR(30)’) AS Activities   
, XMLData.value(‘(/Regions/Natal/@Capital)[1]’, ‘VARCHAR(30)’) AS CapitalCity
, XMLData.value(‘(/Regions/Gauteng)[1]’, ‘VARCHAR(30)’) AS Activities
, XMLData.value(‘(/Regions/Gauteng/@Capital)[1]’, ‘VARCHAR(30)’) AS CapitalCity
FROM dbo.XmlTest

go

truncate table XmlTest

As we can see, we always start off with the XML NameSpace and then where use the .value notation on the XML Data column name to run the Xpath on. Notice I queries both an element and attribute, where attributes are prefixed with the ‘@’ sign.

Results:

Activites                      CapitalCity                    Activites                      CapitalCity
—————————— —————————— —————————— ——————————
Surfing                        Pietermaritzburg               Partying                       Johannesburg

Hope this gets you started with querying XML in SQL, it is relatively straight forward. I could not think of any exciting XML Data, so South Africa provinces did the trick, some geography lessons for free 🙂

Developing Web Services: Part 3

Hi Folks!

I am glad to be back…Been a good week. My landlord sent the boys around to my spot in Amsterdam, since I had taken him
to this organization that can reduce you rent if you feel they over charging and providing  $%^ service. Naturally I won the case,
and he sent the boys around. I decided life is to short to mingle and jingle with rubbish, so I moved out of my shared apartment
and am loving my privacy! Waiting for the girl to visit, so in the mean time, lets BLOG!!!

I want you to sit on you chair and imagine all the Microsoft haters out there! How many customers did you deal with that
had a natural affinity to disliking anything you developed because the product started with some like M…….. .It is amazing,
imagine Microsoft made Mozilla FireFox and Mozilla made Internet Explorer. I bet those Penguin geek books would be boasting
just how better IE is compared to Mozilla Firefox.

Introduction

I had a difficult customer today, they refuse to use our web service, since it is not WS-I compliant. True to their word,
I check the report and indeed it failed. How can this be? Microsoft states their web services are compliant.

Well, in this part I am going to show you exactly how to make sure yours is compliant and then you can skip the bureaucrats
and carrying on Geeking.

To sum up, by default when you create a web service, the WSDL automatically generated will also have the SOAP 1.2 binding,
and this binding is not widely in use, so the best thing to do is disable it.

When you develop a web service, the following attribute is situated above the class name of the web service:

/// <summary>
   /// Summary description for Romiko’s Web Service
   /// </summary>
   [WebService(Namespace = "http://Romiko.WebServices")]
  [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
   [ToolboxItem(false)]
   public class RomikoService: WebService
   {

However, it is possible that the WS-I profile conformance report (What is that?) can fail it. Well, I noticed why the customer
refused to use our web service, this was due to them running the reporting against our SOAP 1.2 binding and not SOAP 1.1
binding, naturally you would expect people to use 1.1 since 1.2 is not De Facto yet. So to hide 1.2 binding, you can use the
following in the web.config. I will explain why SOAP 1.2 binding fails on the WSDL auto generated, even with a Microsoft Hello
World simple web service.

 

<system.web>
        <webServices>
        <protocols>
            <remove name="HttpSoap12"/>
           </protocols>
</webServices>

To remove SOAP 1.2 bindings from the WSDL, all you need to do is insert the above code in your web.config.

WS-I Conformance Report

This article will provide a step by step approach to generating WS-I conformance reports on Windows XP systems.

Install Java

Download and Install from Sun Micro Systems, current release I used is Java 6 update 11

http://java.sun.com/javase/downloads/index.jsp

Install SOAPUI 2.5

Download Location:  http://sourceforge.net/project/showfiles.php?group_id=136013&package_id=163662&release_id=641323

I use the soapUI-2.5-installer.exe

 

Interoperability Testing Tools 1.1

Download Location: http://www.ws-i.org/Testing/Tools/2005/06/WSI_Test_Java_Final_1.1.zip

You may be tempted to use the C# version, however, it is bugged. It will not work correctly with complex WSDL documents.
I find it ridiculous that the ws-i.org published critical conformance tools that are intrinsically flawed. I used the C# version of
the tools and it failed our WSDL on the basis that it could not validate the embedded schema! The java version is your safest bet,
but why they publish a flawed version? You can read more about this here:

http://www.ws-i.org/Testing/Tools/2004/02/Testing_Tools_1.00.01_Known_Issues.htm

When I install the tool, I unzip it to a folder location. Then you must create a environment variable for the location, afterwards
you must REBOOT the system.

image

Note that the folder locations for TEMP will be used to store temporary files from the SOAPUI tool, and this can be a handy
place to look for diagnostics.

Run SOAPUI

  1. Start SOAPUI
    image
  2. Click on the file menu and select preferences
    image
  3. Click WS-I Settings and ensure you settings are correct, here are mine, ensure they point to the java tool folder!
    image
  4. Below is what the wsi-test-tools folder contains:
    image
  5. Notice the README.TXT is using version WS-I Testing Tools V1.1. However when we generate the report using
    SOAPUI, the xslt that is used to generate the report is bugged, it will always say that you used 1.0.0!
    image

 

Now we in a position to generate the report. Fist we need to create a project and point it to the WSDL of the web service
we created in Part 1 of this blog series. By default Microsoft generates binding information for SOAP 1.1 and SOAP 1.2. So lets
create the project.

  1. Right click the projects in the left pain and click new project.
    image
  2. We now point the project to the WSDL online or to a local WSDL file. I point it to a live URL.
    image
  3. This was my URL I inserted:
    http://10.0.1.225/Romiko.Audit.WebServices/AuditWebService.asmx?wsdl
  4. Once this is done, the SOAPUI tool will automatically show the bindings on the left and so on.
    Notice that the web service we developed has both SOAP 1.1 and SOAP 1.2 published!
    image
    As you can see above, SOAP 1.1 is just called AuditWebServiceSoap
  5. Right Click the AuditWebServiceSoap binding and click Check WSI Compliance, not the 1.2 version!
    image
  6. I get the following output on my machine:

    Running WSI Analyzer for [AuditWebServiceSoap]

    directory: C:\downloads\wsi-test-tools\java\bin

    command: [cmd.exe, /C, Analyzer, -config, C:\DOCUME~1\romiko\LOCALS~1\Temp\wsi-analyzer-config30438.xml, -assertionDescription, true]

    Conformance Analyzer Tool, Version: 1.0.0, Release Date: 2005-07-04

    Copyright (C) 2002-2003 by The Web Services-Interoperability Organization and Certain of its Members. All Rights Reserved.

    Use of this Material is governed by WS-I licenses included within the documentation.

      verbose ……………….. true

      Assertion Results:

        type ………………… all

        messageEntry …………. true

        assertionDescription ….. true

        failureMessage ……….. true

        failureDetail ………… true

      Report File:

        replace ……………… true

        location …………….. C:\DOCUME~1\romiko\LOCALS~1\Temp\wsi-report30436.xml

        Style Sheet:

          href ………………. ./../common/Profiles/SSBP10_BP11_TAD.xml

          type ………………. text/xsl

          alternate ………….. false

      testAssertionsFile ……… ../../common/profiles/SSBP10_BP11_TAD.xml

      WSDL Reference:

        WSDL Element:

          type ………………. binding

          namespace ………….. http://romiko.audit.com

          name ………………. AuditWebServiceSoap

        wsdlURI ……………… C:\DOCUME~1\romiko\LOCALS~1\Temp\tempdir30437.tmp\AuditWebService_1.wsdl

    Please wait while the specified artifacts are analyzed…

      Processing BP2201 for entry reference ID [file:/C:/DOCUME~1/romiko/LOCALS~1/Temp/tempdir30437.tmp/AuditWebService_1.wsdl] …

        Elapsed time: 15ms

      Processing BP2700 for entry reference ID [file:/C:/DOCUME~1/romiko/LOCALS~1/Temp/tempdir30437.tmp/AuditWebService_1.wsdl] …

        Elapsed time: 0ms

      Processing BP2701 for entry reference ID [file:/C:/DOCUME~1/romiko/LOCALS~1/Temp/tempdir30437.tmp/AuditWebService_1.wsdl] …

        Elapsed time: 16ms

      Processing BP2703 for entry reference ID [file:/C:/DOCUME~1/romiko/LOCALS~1/Temp/tempdir30437.tmp/AuditWebService_1.wsdl] …

        Elapsed time: 94ms

      Processing BP2034 for entry reference ID [file:/C:/DOCUME~1/romiko/LOCALS~1/Temp/tempdir30437.tmp/AuditWebService_1.wsdl] …

        Elapsed time: 0ms

      Processing BP2018 for entry reference ID [file:/C:/DOCUME~1/romiko/LOCALS~1/Temp/tempdir30437.tmp/AuditWebService_1.wsdl] …

        Elapsed time: 0ms

      Processing BP2101 for entry reference ID [file:/C:/DOCUME~1/romiko/LOCALS~1/Temp/tempdir30437.tmp/AuditWebService_1.wsdl] …

        Elapsed time: 0ms

      Processing BP2103 for entry reference ID [file:/C:/DOCUME~1/romiko/LOCALS~1/Temp/tempdir30437.tmp/AuditWebService_1.wsdl] …

        Elapsed time: 0ms

      Processing BP2104 for entry reference ID [file:/C:/DOCUME~1/romiko/LOCALS~1/Temp/tempdir30437.tmp/AuditWebService_1.wsdl] …

        Elapsed time: 0ms

      Processing BP2105 for entry reference ID [file:/C:/DOCUME~1/romiko/LOCALS~1/Temp/tempdir30437.tmp/AuditWebService_1.wsdl] …

        Elapsed time: 0ms

      Processing BP2416 for entry reference ID [file:/C:/DOCUME~1/romiko/LOCALS~1/Temp/tempdir30437.tmp/AuditWebService_1.wsdl] …

        Elapsed time: 0ms

      Processing BP2417 for entry reference ID [file:/C:/DOCUME~1/romiko/LOCALS~1/Temp/tempdir30437.tmp/AuditWebService_1.wsdl] …

        Elapsed time: 0ms

      Processing BP2123 for entry reference ID [file:/C:/DOCUME~1/romiko/LOCALS~1/Temp/tempdir30437.tmp/AuditWebService_1.wsdl] …

        Elapsed time: 0ms

      Processing BP4200 for entry reference ID [file:/C:/DOCUME~1/romiko/LOCALS~1/Temp/tempdir30437.tmp/AuditWebService_1.wsdl] …

        Elapsed time: 0ms

      Processing BP4201 for entry reference ID [file:/C:/DOCUME~1/romiko/LOCALS~1/Temp/tempdir30437.tmp/AuditWebService_1.wsdl] …

        Elapsed time: 0ms

      Processing BP2202 for entry reference ID [file:/C:/DOCUME~1/romiko/LOCALS~1/Temp/tempdir30437.tmp/AuditWebService_1.wsdl-Types] …

        Elapsed time: 0ms

      Processing BP2102 for entry reference ID [file:/C:/DOCUME~1/romiko/LOCALS~1/Temp/tempdir30437.tmp/AuditWebService_1.wsdl-Types] …

        Elapsed time: 0ms

      Processing BP2011 for entry reference ID [file:/C:/DOCUME~1/romiko/LOCALS~1/Temp/tempdir30437.tmp/AuditWebService_1.wsdl-Types] …

        Elapsed time: 0ms

      Processing BP2107 for entry reference ID [file:/C:/DOCUME~1/romiko/LOCALS~1/Temp/tempdir30437.tmp/AuditWebService_1.wsdl-Types] …

        Elapsed time: 0ms

      Processing BP2108 for entry reference ID [file:/C:/DOCUME~1/romiko/LOCALS~1/Temp/tempdir30437.tmp/AuditWebService_1.wsdl-Types] …

        Elapsed time: 0ms

      Processing BP2110 for entry reference ID [file:/C:/DOCUME~1/romiko/LOCALS~1/Temp/tempdir30437.tmp/AuditWebService_1.wsdl-Types] …

        Elapsed time: 0ms

      Processing BP2122 for entry reference ID [file:/C:/DOCUME~1/romiko/LOCALS~1/Temp/tempdir30437.tmp/AuditWebService_1.wsdl-Types] …

        Elapsed time: 0ms

      Processing BP4202 for entry reference ID [file:/C:/DOCUME~1/romiko/LOCALS~1/Temp/tempdir30437.tmp/AuditWebService_1.wsdl-Types] …

        Elapsed time: 0ms

      Processing BP2020 for entry reference ID [{http://romiko.audit.com}AuditWebServiceSoap]

        Elapsed time: 0ms

      Processing BP2021 for entry reference ID [{http://romiko.audit.com}AuditWebServiceSoap]

        Elapsed time: 0ms

      Processing BP2402 for entry reference ID [{http://romiko.audit.com}AuditWebServiceSoap]

        Elapsed time: 0ms

      Processing BP2022 for entry reference ID [{http://romiko.audit.com}AuditWebServiceSoap]

        Elapsed time: 0ms

      Processing BP2032 for entry reference ID [{http://romiko.audit.com}AuditWebServiceSoap]

        Elapsed time: 0ms

      Processing BP2404 for entry reference ID [{http://romiko.audit.com}AuditWebServiceSoap]

        Elapsed time: 0ms

      Processing BP2012 for entry reference ID [{http://romiko.audit.com}AuditWebServiceSoap]

        Elapsed time: 0ms

      Processing BP2406 for entry reference ID [{http://romiko.audit.com}AuditWebServiceSoap]

        Elapsed time: 0ms

      Processing BP2019 for entry reference ID [{http://romiko.audit.com}AuditWebServiceSoap]

        Elapsed time: 0ms

      Processing BP2013 for entry reference ID [{http://romiko.audit.com}AuditWebServiceSoap]

        Elapsed time: 0ms

      Processing BP2017 for entry reference ID [{http://romiko.audit.com}AuditWebServiceSoap]

        Elapsed time: 0ms

      Processing BP2111 for entry reference ID [{http://romiko.audit.com}AuditWebServiceSoap]

        Elapsed time: 0ms

      Processing BP2112 for entry reference ID [{http://romiko.audit.com}AuditWebServiceSoap]

        Elapsed time: 0ms

      Processing BP2113 for entry reference ID [{http://romiko.audit.com}AuditWebServiceSoap]

        Elapsed time: 0ms

      Processing BP2117 for entry reference ID [{http://romiko.audit.com}AuditWebServiceSoap]

        Elapsed time: 0ms

      Processing BP2118 for entry reference ID [{http://romiko.audit.com}AuditWebServiceSoap]

        Elapsed time: 0ms

      Processing BP2114 for entry reference ID [{http://romiko.audit.com}AuditWebServiceSoap]

        Elapsed time: 15ms

      Processing BP2119 for entry reference ID [{http://romiko.audit.com}AuditWebServiceSoap]

        Elapsed time: 0ms

      Processing BP2120 for entry reference ID [{http://romiko.audit.com}AuditWebServiceSoap]

        Elapsed time: 0ms

      Processing SSBP2209 for entry reference ID [{http://romiko.audit.com}AuditWebServiceSoap]

        Elapsed time: 0ms

      Processing SSBP2403 for entry reference ID [{http://romiko.audit.com}AuditWebServiceSoap]

        Elapsed time: 0ms

      Processing BP2010 for entry reference ID [{http://romiko.audit.com}AuditWebServiceSoap]

        Elapsed time: 0ms

      Processing BP2208 for entry reference ID [AuditComplex] …

        Elapsed time: 0ms

      Processing BP2014 for entry reference ID [AuditComplex] …

        Elapsed time: 0ms

      Processing BP2208 for entry reference ID [AuditSimple] …

        Elapsed time: 0ms

      Processing BP2014 for entry reference ID [AuditSimple] …

        Elapsed time: 0ms

      Processing BP2115 for entry reference ID [{http://romiko.audit.com}AuditComplexSoapIn]

        Elapsed time: 0ms

      Processing BP2116 for entry reference ID [{http://romiko.audit.com}AuditComplexSoapIn]

        Elapsed time: 0ms

      Processing BP2115 for entry reference ID [{http://romiko.audit.com}AuditSimpleSoapIn]

        Elapsed time: 0ms

      Processing BP2116 for entry reference ID [{http://romiko.audit.com}AuditSimpleSoapIn]

        Elapsed time: 0ms

      Processing BP2115 for entry reference ID [{http://romiko.audit.com}AuditComplexSoapOut]

    Conformance report has been created.

        Elapsed time: 0ms

      Processing BP2116 for entry reference ID [{http://romiko.audit.com}AuditComplexSoapOut]

        Elapsed time: 0ms

      Processing BP2115 for entry reference ID [{http://romiko.audit.com}AuditSimpleSoapOut]

        Elapsed time: 0ms

      Processing BP2116 for entry reference ID [{http://romiko.audit.com}AuditSimpleSoapOut]

        Elapsed time: 0ms

  7. There is a couple of things to notice, first a xml version of the report and html version are created. The HTML version
    is done by SOAPUI and the xml version by the WSI-TOOLS Analyzer.exe file. My xml report is located here:

    C:\DOCUME~1\romiko\LOCALS~1\Temp\wsi-report30436.xml

  8. The html report is located in c:\wsi-report30439.html on my machine. SOAPUI also shows the report, the summary result
    of the report is a PASS.
    image
    image
  9. Now lets do the same report for the SOAP 1.2 binding! It will fail, and this is a problem i guess with the way Microsoft
    specified the style attribute. In Soap 1.2 it needs to be at the wsdl:binding level and not only the operation level.
    image

    image

  10. If I look at the details of the report, this is what failed:
    BP2402 FAILED.
    image
  11. What does this mean. Well, if we open this document in the WSI-Tools folder:
    C:\downloads\wsi-test-tools\common\profiles\BasicProfile_1.1_TAD.xml
    I get this:

    <!–  _________________BP2402___________________   –>
    <testAssertion id="BP2402" entryType="binding" type="required" enabled="true">
        <context>For a candidate wsdl:binding element</context>
        <assertionDescription>The wsdl:binding element has a soapbind:binding child element.</assertionDescription>
        <failureMessage>The wsdl:binding element does not use a soapbind:binding element as defined in section "3 SOAP Binding." of the WSDL 1.1 specification.</failureMessage>
        <failureDetailDescription>wsdl:binding.</failureDetailDescription>
        <additionalEntryTypeList/>
        <prereqList>
            <testAssertionID>BP2703</testAssertionID>
        </prereqList>
        <referenceList>
            <reference profileID="BP11">R2401</reference>
        </referenceList>
        <comments/>
    </testAssertion>

  12. This shows that Microsoft’s SOAP 1.2 binding should never be run against a PROFILE 1.1 conformance, else you will get
    problems. It seems that even building simple web services under SOAP 1.2 will fail conformance with the .NET Framework.
    The failure here is:

    http://www.ws-i.org/Profiles/BasicProfile-1.0-2004-04-16.html

    5.5.1 Use of SOAP Binding

    The Profile limits the choice of bindings to the well defined and most commonly used SOAP binding.
    MIME and HTTP GET/POST bindings are not permitted by the Profile.

    R2401 A wsdl:binding element in a DESCRIPTION MUST use WSDL SOAP Binding as defined in WSDL 1.1 Section 3.

    Note that this places a requirement on the construction of conformant wsdl:binding elements. It does not place a
    requirement on descriptions as a whole; in particular, it does not preclude WSDL documents from containing
    non-conformant wsdl:binding elements.

     
  13. I recently had a client who ran his conformance reports against our SOAP 1.2 binding, and it failed and we lost allot of
    time rectifying the problem with them. In the end, the best solution is to just not publish it.
  14. So, I edit my web,.config and to remove SOAP 1.2 binding, then when I recreate a project to the same web service, the
    soap 1.2 binding will not be visible!
    image

    <?xml version="1.0"?>
    <configuration>
        <configSections>
    </configSections>
    <appSettings/>
        <connectionStrings>
            <add name="AuditDatabase" connectionString="Data Source=(local);Initial Catalog=Audit;Integrated Security=True" providerName="System.Data.SqlClient"/>
        </connectionStrings>
        <system.web>
            <compilation debug="true" />
            <authentication mode="Windows" />
            <trust level="Full" />
            <webServices>
                <protocols>
                    <remove name="HttpSoap12"/>
                    <add name="HttpGet"/>
                    <add name="HttpPost"/>
                </protocols>
            </webServices>
        </system.web>
    </configuration>

  15. Now I recreate the project in SOAPIO and voila, no more SOAP 1.2, and your customers cannot fail your reports 🙂
    image
  16. Some other problems I notice with the SOAP 1.2 is the document style attribute only being defined on the Operation Level
    and not the Binding level:

    image
    Notice style="document" is not defined on the wsdl:binding level. This is mentioned in the ws-i profile:

    http://www.ws-i.org/Profiles/BasicProfile-1.0-2004-04-16.html
    R2705 A wsdl:binding in a DESCRIPTION MUST either be an rpc-literal binding or a document-literal binding.

    Hopefully Microsoft will notice this bug with the way the .NET libraries auto generate the WSDL. Also it wold be cool if
    XSD.exe tool supports Imports, Includes

 

Generating the report with the command console.

You can also generate the report from the command console, however it is only xml report.

First you need an XML Configuration file and a path to the wsdl.

<?xml version="1.0" encoding="UTF-8"?>
<anal:configuration xmlns:anal="http://www.ws-i.org/testing/2004/07/analyzerConfig/"><anal:verbose>true</anal:verbose><anal:assertionResults type="all" messageEntry="true" failureMessage="true" assertionDescription="true"/><anal:reportFile location="C:\WSIReports\wsi-report8548.xml" replace="true"><anal:addStyleSheet href="./../common/Profiles/SSBP10_BP11_TAD.xml" type="text/xsl" alternate="false"/></anal:reportFile><anal:testAssertionsFile>../../common/profiles/SSBP10_BP11_TAD.xml
</anal:testAssertionsFile><anal:wsdlReference><anal:wsdlElement type="binding" namespace="http://romiko.audit.com">AuditWebServiceSoap</anal:wsdlElement><anal:wsdlURI>
http://10.0.1.225/Romiko.Audit.WebServices/AuditWebService.asmx?wsdl</anal:wsdlURI></anal:wsdlReference></anal:configuration>

Here is my command run:

C:\downloads\wsi-test-tools\java\bin>dir
Volume in drive C is Windows XP x64
Volume Serial Number is 8C8B-5AA2

Directory of C:\downloads\wsi-test-tools\java\bin

15-07-2005  15:29    <DIR>          .
15-07-2005  15:29    <DIR>          ..
15-07-2005  15:29             4.542 Analyzer.bat
15-07-2005  15:29             3.979 Analyzer.sh
15-07-2005  15:29             4.516 Monitor.bat
15-07-2005  15:29             3.948 Monitor.sh
15-07-2005  15:29             5.045 setenv.bat
15-07-2005  15:29             4.665 setenv.sh
               6 File(s)         26.695 bytes
               2 Dir(s)  26.607.079.424 bytes free

C:\downloads\wsi-test-tools\java\bin>Analyzer -config C:\WSIReports\wsi-analyzer
-config30450.xml -verbose true

 

Output:

C:\downloads\wsi-test-tools\java\bin>Analyzer -config C:\WSIReports\wsi-analyzer
-config30450.xml -verbose true
Conformance Analyzer Tool, Version: 1.0.0, Release Date: 2005-07-04
Copyright (C) 2002-2003 by The Web Services-Interoperability Organization and Ce
rtain of its Members. All Rights Reserved.
Use of this Material is governed by WS-I licenses included within the documentat
ion.

  verbose ……………….. true
  Assertion Results:
    type ………………… all
    messageEntry …………. true
    assertionDescription ….. false
    failureMessage ……….. true
    failureDetail ………… true
  Report File:
    replace ……………… true
    location …………….. C:\WSIReports\wsi-report8548.xml
    Style Sheet:
      href ………………. ./../common/Profiles/SSBP10_BP11_TAD.xml
      type ………………. text/xsl
      alternate ………….. false
  testAssertionsFile ……… ../../common/profiles/SSBP10_BP11_TAD.xml
  WSDL Reference:
    WSDL Element:
      type ………………. binding
      namespace ………….. http://romiko.audit.com
      name ………………. AuditWebServiceSoap
    wsdlURI ……………… http://10.0.1.225/Romiko.Audit.WebServices/AuditW
ebService.asmx?wsdl

Please wait while the specified artifacts are analyzed…
  Processing BP2201 for entry reference ID [http://10.0.1.225/Romiko.Audit.WebSe
rvices/AuditWebService.asmx?wsdl] …
    Elapsed time: 16ms
  Processing BP2700 for entry reference ID [http://10.0.1.225/Romiko.Audit.WebSe
rvices/AuditWebService.asmx?wsdl] …
    Elapsed time: 16ms
  Processing BP2701 for entry reference ID [http://10.0.1.225/Romiko.Audit.WebSe
rvices/AuditWebService.asmx?wsdl] …
    Elapsed time: 15ms
  Processing BP2703 for entry reference ID [http://10.0.1.225/Romiko.Audit.WebSe
rvices/AuditWebService.asmx?wsdl] …
    Elapsed time: 110ms
  Processing BP2034 for entry reference ID [http://10.0.1.225/Romiko.Audit.WebSe
rvices/AuditWebService.asmx?wsdl] …
    Elapsed time: 0ms
  Processing BP2018 for entry reference ID [http://10.0.1.225/Romiko.Audit.WebSe
rvices/AuditWebService.asmx?wsdl] …
    Elapsed time: 0ms
  Processing BP2101 for entry reference ID [http://10.0.1.225/Romiko.Audit.WebSe
rvices/AuditWebService.asmx?wsdl] …
    Elapsed time: 15ms
  Processing BP2103 for entry reference ID [http://10.0.1.225/Romiko.Audit.WebSe
rvices/AuditWebService.asmx?wsdl] …
    Elapsed time: 0ms
  Processing BP2104 for entry reference ID [http://10.0.1.225/Romiko.Audit.WebSe
rvices/AuditWebService.asmx?wsdl] …
    Elapsed time: 0ms
  Processing BP2105 for entry reference ID [http://10.0.1.225/Romiko.Audit.WebSe
rvices/AuditWebService.asmx?wsdl] …
    Elapsed time: 0ms
  Processing BP2416 for entry reference ID [http://10.0.1.225/Romiko.Audit.WebSe
rvices/AuditWebService.asmx?wsdl] …
    Elapsed time: 0ms
  Processing BP2417 for entry reference ID [http://10.0.1.225/Romiko.Audit.WebSe
rvices/AuditWebService.asmx?wsdl] …
    Elapsed time: 0ms
  Processing BP2123 for entry reference ID [http://10.0.1.225/Romiko.Audit.WebSe
rvices/AuditWebService.asmx?wsdl] …
    Elapsed time: 0ms
  Processing BP4200 for entry reference ID [http://10.0.1.225/Romiko.Audit.WebSe
rvices/AuditWebService.asmx?wsdl] …
    Elapsed time: 16ms
  Processing BP4201 for entry reference ID [http://10.0.1.225/Romiko.Audit.WebSe
rvices/AuditWebService.asmx?wsdl] …
    Elapsed time: 0ms
  Processing BP2202 for entry reference ID [http://10.0.1.225/Romiko.Audit.WebSe
rvices/AuditWebService.asmx?wsdl-Types] …
    Elapsed time: 0ms
  Processing BP2102 for entry reference ID [http://10.0.1.225/Romiko.Audit.WebSe
rvices/AuditWebService.asmx?wsdl-Types] …
    Elapsed time: 0ms
  Processing BP2011 for entry reference ID [http://10.0.1.225/Romiko.Audit.WebSe
rvices/AuditWebService.asmx?wsdl-Types] …
    Elapsed time: 0ms
  Processing BP2107 for entry reference ID [http://10.0.1.225/Romiko.Audit.WebSe
rvices/AuditWebService.asmx?wsdl-Types] …
    Elapsed time: 0ms
  Processing BP2108 for entry reference ID [http://10.0.1.225/Romiko.Audit.WebSe
rvices/AuditWebService.asmx?wsdl-Types] …
    Elapsed time: 16ms
  Processing BP2110 for entry reference ID [http://10.0.1.225/Romiko.Audit.WebSe
rvices/AuditWebService.asmx?wsdl-Types] …
    Elapsed time: 0ms
  Processing BP2122 for entry reference ID [http://10.0.1.225/Romiko.Audit.WebSe
rvices/AuditWebService.asmx?wsdl-Types] …
    Elapsed time: 0ms
  Processing BP4202 for entry reference ID [http://10.0.1.225/Romiko.Audit.WebSe
rvices/AuditWebService.asmx?wsdl-Types] …
    Elapsed time: 0ms
  Processing BP2020 for entry reference ID [{http://romiko.audit.com}AuditWebSer
viceSoap] …
    Elapsed time: 0ms
  Processing BP2021 for entry reference ID [{http://romiko.audit.com}AuditWebSer
viceSoap] …
    Elapsed time: 0ms
  Processing BP2402 for entry reference ID [{http://romiko.audit.com}AuditWebSer
viceSoap] …
    Elapsed time: 0ms
  Processing BP2022 for entry reference ID [{http://romiko.audit.com}AuditWebSer
viceSoap] …
    Elapsed time: 0ms
  Processing BP2032 for entry reference ID [{http://romiko.audit.com}AuditWebSer
viceSoap] …
    Elapsed time: 0ms
  Processing BP2404 for entry reference ID [{http://romiko.audit.com}AuditWebSer
viceSoap] …
    Elapsed time: 0ms
  Processing BP2012 for entry reference ID [{http://romiko.audit.com}AuditWebSer
viceSoap] …
    Elapsed time: 0ms
  Processing BP2406 for entry reference ID [{http://romiko.audit.com}AuditWebSer
viceSoap] …
    Elapsed time: 0ms
  Processing BP2019 for entry reference ID [{http://romiko.audit.com}AuditWebSer
viceSoap] …
    Elapsed time: 0ms
  Processing BP2013 for entry reference ID [{http://romiko.audit.com}AuditWebSer
viceSoap] …
    Elapsed time: 0ms
  Processing BP2017 for entry reference ID [{http://romiko.audit.com}AuditWebSer
viceSoap] …
    Elapsed time: 0ms
  Processing BP2111 for entry reference ID [{http://romiko.audit.com}AuditWebSer
viceSoap] …
    Elapsed time: 0ms
  Processing BP2112 for entry reference ID [{http://romiko.audit.com}AuditWebSer
viceSoap] …
    Elapsed time: 0ms
  Processing BP2113 for entry reference ID [{http://romiko.audit.com}AuditWebSer
viceSoap] …
    Elapsed time: 0ms
  Processing BP2117 for entry reference ID [{http://romiko.audit.com}AuditWebSer
viceSoap] …
    Elapsed time: 0ms
  Processing BP2118 for entry reference ID [{http://romiko.audit.com}AuditWebSer
viceSoap] …
    Elapsed time: 0ms
  Processing BP2114 for entry reference ID [{http://romiko.audit.com}AuditWebSer
viceSoap] …
    Elapsed time: 0ms
  Processing BP2119 for entry reference ID [{http://romiko.audit.com}AuditWebSer
viceSoap] …
    Elapsed time: 0ms
  Processing BP2120 for entry reference ID [{http://romiko.audit.com}AuditWebSer
viceSoap] …
    Elapsed time: 0ms
  Processing SSBP2209 for entry reference ID [{http://romiko.audit.com}AuditWebS
erviceSoap] …
    Elapsed time: 0ms
  Processing SSBP2403 for entry reference ID [{http://romiko.audit.com}AuditWebS
erviceSoap] …
    Elapsed time: 0ms
  Processing BP2010 for entry reference ID [{http://romiko.audit.com}AuditWebSer
viceSoap] …
    Elapsed time: 0ms
  Processing BP2208 for entry reference ID [AuditComplex] …
    Elapsed time: 0ms
  Processing BP2014 for entry reference ID [AuditComplex] …
    Elapsed time: 0ms
  Processing BP2208 for entry reference ID [AuditSimple] …
    Elapsed time: 0ms
  Processing BP2014 for entry reference ID [AuditSimple] …
    Elapsed time: 0ms
  Processing BP2115 for entry reference ID [{http://romiko.audit.com}AuditSimple
SoapOut] …
    Elapsed time: 0ms
  Processing BP2116 for entry reference ID [{http://romiko.audit.com}AuditSimple
SoapOut] …
    Elapsed time: 0ms
  Processing BP2115 for entry reference ID [{http://romiko.audit.com}AuditComple
xSoapOut] …
    Elapsed time: 0ms
  Processing BP2116 for entry reference ID [{http://romiko.audit.com}AuditComple
xSoapOut] …
    Elapsed time: 0ms
  Processing BP2115 for entry reference ID [{http://romiko.audit.com}AuditSimple
SoapIn] …
    Elapsed time: 0ms
  Processing BP2116 for entry reference ID [{http://romiko.audit.com}AuditSimple
SoapIn] …
    Elapsed time: 0ms
  Processing BP2115 for entry reference ID [{http://romiko.audit.com}AuditComple
xSoapIn] …
    Elapsed time: 0ms
  Processing BP2116 for entry reference ID [{http://romiko.audit.com}AuditComple
xSoapIn] …
    Elapsed time: 0ms
Conformance report has been created.
C:\downloads\wsi-test-tools\java\bin>

 

Here is the XML report:

<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet href="./../common/Profiles/SSBP10_BP11_TAD.xml" type="text/xsl" alternate="false" ?>
<report name="WS-I Basic Profile Conformance Report."    timestamp="2009-01-17T15:47:51.749"
    xmlns="http://www.ws-i.org/testing/2004/07/report/"
    xmlns:wsi-report="http://www.ws-i.org/testing/2004/07/report/"
    xmlns:wsi-log="http://www.ws-i.org/testing/2003/03/log/"
    xmlns:wsi-analyzerConfig="http://www.ws-i.org/testing/2004/07/analyzerConfig/"
    xmlns:wsi-monConfig="http://www.ws-i.org/testing/2003/03/monitorConfig/"
    xmlns:wsi-assertions="http://www.ws-i.org/testing/2004/07/assertions/"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <analyzer version="1.0.0" releaseDate="2005-07-04">
    <implementer name="WS-I Organization" location="http://www.ws-i.org"/>
    <environment>
      <runtime name="Java(TM) SE Runtime Environment" version="1.6.0_11-b03"/>
      <operatingSystem name="Windows XP" version="5.2"/>
      <xmlParser name="Apache Xerces" version="Xerces-J 2.6.2"/>
    </environment>
    <wsi-analyzerConfig:configuration>
      <wsi-analyzerConfig:verbose>true</wsi-analyzerConfig:verbose>
      <wsi-analyzerConfig:assertionResults type="all" messageEntry="true" assertionDescription="false" failureMessage="true" failureDetail="true"/>
        <wsi-analyzerConfig:reportFile replace="true" location="C:\WSIReports\wsi-report8548.xml">
      <wsi-analyzerConfig:addStyleSheet href="./../common/Profiles/SSBP10_BP11_TAD.xml" type="text/xsl" alternate="false" />
        </wsi-analyzerConfig:reportFile>
        <wsi-analyzerConfig:testAssertionsFile>../../common/profiles/SSBP10_BP11_TAD.xml</wsi-analyzerConfig:testAssertionsFile>
        <wsi-analyzerConfig:logFile correlationType="operation">null</wsi-analyzerConfig:logFile>
      <wsi-analyzerConfig:wsdlReference>
        <wsi-analyzerConfig:wsdlElement type="binding" namespace="http://romiko.audit.com" >AuditWebServiceSoap</wsi-analyzerConfig:wsdlElement>
        <wsi-analyzerConfig:wsdlURI>http://10.0.1.225/Romiko.Audit.WebServices/AuditWebService.asmx?wsdl</wsi-analyzerConfig:wsdlURI>
      </wsi-analyzerConfig:wsdlReference>
    </wsi-analyzerConfig:configuration>
  </analyzer>

  <artifact type="discovery">
    <entry type="[discovery]" >
      <assertionResult id="BP3001" result="missingInput">
      </assertionResult>
      <assertionResult id="BP3002" result="missingInput">
      </assertionResult>
      <assertionResult id="BP3003" result="missingInput">
      </assertionResult>
    </entry>
  </artifact>
  <artifact type="description">
    <entry type="definitions" referenceID="http://10.0.1.225/Romiko.Audit.WebServices/AuditWebService.asmx?wsdl">
      <assertionResult id="BP2201" result="passed">
      </assertionResult>
      <assertionResult id="BP2700" result="passed">
      </assertionResult>
      <assertionResult id="BP2701" result="passed">
      </assertionResult>
      <assertionResult id="BP2703" result="passed">
      </assertionResult>
      <assertionResult id="BP2034" result="passed">
      </assertionResult>
      <assertionResult id="BP2018" result="passed">
      </assertionResult>
      <assertionResult id="BP2101" result="notApplicable">
      </assertionResult>
      <assertionResult id="BP2103" result="passed">
      </assertionResult>
      <assertionResult id="BP2104" result="notApplicable">
      </assertionResult>
      <assertionResult id="BP2105" result="notApplicable">
      </assertionResult>
      <assertionResult id="BP2416" result="passed">
      </assertionResult>
      <assertionResult id="BP2417" result="passed">
      </assertionResult>
      <assertionResult id="BP2123" result="notApplicable">
      </assertionResult>
      <assertionResult id="BP4200" result="passed">
      <failureDetail xml:lang="en" >The use of a WSDL extension element or attribute from a namespace other than &quot;http://schemas.xmlsoap.org/wsdl/soap/&quot; may require out of band negotiation.

{http://schemas.xmlsoap.org/wsdl/mime/}content,
{http://schemas.xmlsoap.org/wsdl/http/}address,
{http://schemas.xmlsoap.org/wsdl/http/}urlEncoded,
{http://schemas.xmlsoap.org/wsdl/http/}binding,
{http://schemas.xmlsoap.org/wsdl/mime/}mimeXml,
{http://schemas.xmlsoap.org/wsdl/http/}operation
Element Location:
  lineNumber=2
      </failureDetail>
      </assertionResult>
      <assertionResult id="BP4201" result="notApplicable">
      </assertionResult>
    </entry>
    <entry type="types" referenceID="http://10.0.1.225/Romiko.Audit.WebServices/AuditWebService.asmx?wsdl-Types">
      <assertionResult id="BP2202" result="passed">
      </assertionResult>
      <assertionResult id="BP2102" result="passed">
      </assertionResult>
      <assertionResult id="BP2011" result="passed">
      </assertionResult>
      <assertionResult id="BP2107" result="passed">
      </assertionResult>
      <assertionResult id="BP2108" result="passed">
      </assertionResult>
      <assertionResult id="BP2110" result="passed">
      </assertionResult>
      <assertionResult id="BP2122" result="passed">
      </assertionResult>
      <assertionResult id="BP4202" result="notApplicable">
      </assertionResult>
    </entry>
    <entry type="import" referenceID="[import]">
      <assertionResult id="BP2098" result="missingInput">
      </assertionResult>
      <assertionResult id="BP2803" result="missingInput">
      </assertionResult>
    </entry>
    <entry type="binding" referenceID="{http://romiko.audit.com}AuditWebServiceSoap">
      <assertionResult id="BP2020" result="notApplicable">
      </assertionResult>
      <assertionResult id="BP2021" result="notApplicable">
      </assertionResult>
      <assertionResult id="BP2402" result="passed">
      </assertionResult>
      <assertionResult id="BP2022" result="notApplicable">
      </assertionResult>
      <assertionResult id="BP2032" result="passed">
      </assertionResult>
      <assertionResult id="BP2404" result="passed">
      </assertionResult>
      <assertionResult id="BP2012" result="passed">
      </assertionResult>
      <assertionResult id="BP2406" result="passed">
      </assertionResult>
      <assertionResult id="BP2019" result="passed">
      </assertionResult>
      <assertionResult id="BP2013" result="notApplicable">
      </assertionResult>
      <assertionResult id="BP2017" result="passed">
      </assertionResult>
      <assertionResult id="BP2111" result="passed">
      </assertionResult>
      <assertionResult id="BP2112" result="notApplicable">
      </assertionResult>
      <assertionResult id="BP2113" result="passed">
      </assertionResult>
      <assertionResult id="BP2117" result="notApplicable">
      </assertionResult>
      <assertionResult id="BP2118" result="passed">
      </assertionResult>
      <assertionResult id="BP2114" result="passed">
      </assertionResult>
      <assertionResult id="BP2119" result="passed">
      </assertionResult>
      <assertionResult id="BP2120" result="passed">
      </assertionResult>
      <assertionResult id="SSBP2209" result="passed">
      </assertionResult>
      <assertionResult id="SSBP2403" result="passed">
      </assertionResult>
    </entry>
    <entry type="portType" referenceID="{http://romiko.audit.com}AuditWebServiceSoap">
      <assertionResult id="BP2010" result="passed">
      </assertionResult>
    </entry>
    <entry type="operation" referenceID="AuditComplex">
      <assertionResult id="BP2208" result="passed">
      </assertionResult>
      <assertionResult id="BP2014" result="notApplicable">
      </assertionResult>
    </entry>
    <entry type="operation" referenceID="AuditSimple">
      <assertionResult id="BP2208" result="passed">
      </assertionResult>
      <assertionResult id="BP2014" result="notApplicable">
      </assertionResult>
    </entry>
    <entry type="message" referenceID="{http://romiko.audit.com}AuditSimpleSoapOut">
      <assertionResult id="BP2115" result="passed">
      </assertionResult>
      <assertionResult id="BP2116" result="passed">
      </assertionResult>
    </entry>
    <entry type="message" referenceID="{http://romiko.audit.com}AuditComplexSoapOut">
      <assertionResult id="BP2115" result="passed">
      </assertionResult>
      <assertionResult id="BP2116" result="passed">
      </assertionResult>
    </entry>
    <entry type="message" referenceID="{http://romiko.audit.com}AuditSimpleSoapIn">
      <assertionResult id="BP2115" result="passed">
      </assertionResult>
      <assertionResult id="BP2116" result="passed">
      </assertionResult>
    </entry>
    <entry type="message" referenceID="{http://romiko.audit.com}AuditComplexSoapIn">
      <assertionResult id="BP2115" result="passed">
      </assertionResult>
      <assertionResult id="BP2116" result="passed">
      </assertionResult>
    </entry>
  </artifact>
  <artifact type="message">
    <entry type="[message]" >
      <assertionResult id="BP1004" result="missingInput">
      </assertionResult>
      <assertionResult id="BP1006" result="missingInput">
      </assertionResult>
      <assertionResult id="BP1116" result="missingInput">
      </assertionResult>
      <assertionResult id="BP1002" result="missingInput">
      </assertionResult>
      <assertionResult id="BP1001" result="missingInput">
      </assertionResult>
      <assertionResult id="BP1010" result="missingInput">
      </assertionResult>
      <assertionResult id="BP1101" result="missingInput">
      </assertionResult>
      <assertionResult id="BP1103" result="missingInput">
      </assertionResult>
      <assertionResult id="BP4103" result="missingInput">
      </assertionResult>
      <assertionResult id="BP4104" result="missingInput">
      </assertionResult>
      <assertionResult id="BP4105" result="missingInput">
      </assertionResult>
      <assertionResult id="BP4106" result="missingInput">
      </assertionResult>
      <assertionResult id="BP4107" result="missingInput">
      </assertionResult>
      <assertionResult id="SSBP1003" result="missingInput">
      </assertionResult>
      <assertionResult id="SSBP5100" result="missingInput">
      </assertionResult>
      <assertionResult id="SSBP5101" result="missingInput">
      </assertionResult>
    </entry>
  </artifact>
  <artifact type="envelope">
    <entry type="[envelope]" >
      <assertionResult id="BP1107" result="missingInput">
      </assertionResult>
      <assertionResult id="BP1601" result="missingInput">
      </assertionResult>
      <assertionResult id="BP1201" result="missingInput">
      </assertionResult>
      <assertionResult id="BP1701" result="missingInput">
      </assertionResult>
      <assertionResult id="BP1308" result="missingInput">
      </assertionResult>
      <assertionResult id="BP1011" result="missingInput">
      </assertionResult>
      <assertionResult id="BP1013" result="missingInput">
      </assertionResult>
      <assertionResult id="BP1204" result="missingInput">
      </assertionResult>
      <assertionResult id="BP1301" result="missingInput">
      </assertionResult>
      <assertionResult id="BP1305" result="missingInput">
      </assertionResult>
      <assertionResult id="BP1306" result="missingInput">
      </assertionResult>
      <assertionResult id="BP1031" result="missingInput">
      </assertionResult>
      <assertionResult id="BP1032" result="missingInput">
      </assertionResult>
      <assertionResult id="BP1033" result="missingInput">
      </assertionResult>
      <assertionResult id="BP1316" result="missingInput">
      </assertionResult>
      <assertionResult id="BP1307" result="missingInput">
      </assertionResult>
      <assertionResult id="BP1202" result="missingInput">
      </assertionResult>
      <assertionResult id="BP1318" result="missingInput">
      </assertionResult>
      <assertionResult id="BP1008" result="missingInput">
      </assertionResult>
      <assertionResult id="BP1211" result="missingInput">
      </assertionResult>
      <assertionResult id="BP1212" result="missingInput">
      </assertionResult>
      <assertionResult id="BP1213" result="missingInput">
      </assertionResult>
      <assertionResult id="BP1214" result="missingInput">
      </assertionResult>
      <assertionResult id="BP1755" result="missingInput">
      </assertionResult>
      <assertionResult id="BP1005" result="missingInput">
      </assertionResult>
      <assertionResult id="BP1302" result="missingInput">
      </assertionResult>
      <assertionResult id="BP1203" result="missingInput">
      </assertionResult>
      <assertionResult id="BP1100" result="missingInput">
      </assertionResult>
      <assertionResult id="BP1600" result="missingInput">
      </assertionResult>
      <assertionResult id="BP1012" result="missingInput">
      </assertionResult>
      <assertionResult id="BP1007" result="missingInput">
      </assertionResult>
      <assertionResult id="BP1208" result="missingInput">
      </assertionResult>
      <assertionResult id="BP1009" result="missingInput">
      </assertionResult>
      <assertionResult id="BP1309" result="missingInput">
      </assertionResult>
      <assertionResult id="BP4100" result="missingInput">
      </assertionResult>
      <assertionResult id="BP4101" result="missingInput">
      </assertionResult>
      <assertionResult id="BP4102" result="missingInput">
      </assertionResult>
      <assertionResult id="BP4109" result="missingInput">
      </assertionResult>
      <assertionResult id="SSBP1601" result="missingInput">
      </assertionResult>
      <assertionResult id="SSBP9704" result="missingInput">
      </assertionResult>
    </entry>
  </artifact>
  <summary result="passed">
  </summary>
</report>

Conclusion

This article has shown you how to test if your web service conforms to the WS-I Basic profile 1.1 standards. SOAP 1.2
and Microsoft’s WSDL does not conform, well so it seems, and maybe in the future this will be addressed with new wsi-tools
for SOAP 1.2, would be nice if Microsoft can get involved and get the C# version of the tool which is heavily bugged
working correctly for the ws-i org.

Also, SOAPUI is not needed to create a report, you can use the Analzyer command (The Java version, C# version is bugged).

Remove SOAP 1.2 bindings using the web.config. Do not trust all the tools out there, they have bugs!

You can download the tools on this site, except for Java.

WS-I Conformance Report Tools

Developing Web Services: Part 2

Hi Folks!

Introduction

I hope you guys enjoyed Developing Web Services: Part 1, in this blog we going to develop a consumer for the web service we made in Part 1. Remember in Part 1 we created two types of web methods, one accepted a simple .Net Type and the other accepts a composite or custom .Net Object type. I mentioned that simple types can be used directly by HTTP-Get and HTTP-Post and that composite web methods need to use SOAP only and therefore any consumer application calling a composite web method will need a proxy class, and this is exactly what we going to do today!

This is my own definition:
A composite web method is any web method that takes input parameters that are not Simple .Net Types

Return values of the web method will not affect the protocol you use to call them, since they always return a serialized objected, which is in XML format. So the only condition is that return values of web methods are all serializable.

One more thing, if you can keep a web methods parameters as simple types, the better! Keep that in mind. So on many occasions when developing a web service you don’t need to write a custom class for the input parameters, unless absolutely necessary. I always write custom response objects, since in them I imbed my error messages and other information that a consumer can use for decision making and exception handling.

For the BizTalk geeks, this is especially true for soap send ports to call a web service, if they use simple types, then there is no need to create a BizTalk proxy class.

I include to screen shots below of a BizTalk SOAP send port, consuming a  composite web method without the use of a Orchestration.

Composite Call:

image

In part 3 of this blog article, I will discuss consuming web services from BizTalk.

Ok enough chit chat, lets get moving.

Overview

If you remember from part 1, this is the steps we going to take to develop a consumer application for the Audit Web Service.

  1. UI (Display the results of the web service response.
  2. Generate web proxy reference
  3. Call the web service
  4. Capture the response

We going to write a simple windows based application that calls the web service and displays the result. We will call the AuditComplex web method.

Prerequisites

I assume the web method we developed in part 1 is deployed to IIS. If you did not deploy it as a web service to IIS, follow these steps.

  1. Ensure IIS is installed on your machine
  2. Ensure iis_regaspnet has been run, so that IIS uses the ASP.Net engine
  3. image
  4. image
  5. image
  6. Open the Solution file
  7. Right click the project Romiko.Audit.WebServices and select properties
  8. Click the web link.
  9. image
  10. Once you are in here you can then click use IIS Web server
  11. Click the button ‘Create Virtual Directory’
  12. image
  13. Close the project properties
  14. Check in IIS if a virtual directory is created and check it is associated with Asp.NET 2.0
  15. image
  16. image
  17. Test the web service by browsing to it.
  18. image

Now when we build the web service it will work from IIS. The URL above is the URL the consuming application will use.

Application Pools for Servers (Optional)

Before we get moving, you remember in my last article in Part 1, I chatted about Application Pools, we this is where you can use them, I like my web services to run under a service account which is restricted, then I never have user name and passwords in the web.config (I hate that!). What you do is create a Application Pool and set it to run under the service account, give the service account access to the SQL database etc. Lets do this now!

Warning: This is for Windows Servers! Due to IIS on servers supporting application pools

  1. Create IIS app pool for our web service
  2. image
  3. Give the application pool a name
  4. image
  5. Go to the properties of the application pool and click identity
  6. image
  7. Notice that the pool will run under network service, this is where you can choose a Domain Service Account.
  8. I created one in my test domain
  9. image
  10. All I do is use this for the identity of the application pool
  11. image
  12. Another nice with application pools is recycling the memory used, since the web service will be isolated from other application processes. So you can recycle it to release memory.
  13. In this case, at 4 am I recycle the memory
  14. image
  15. Now we give the service account access to the database and WSS_WPG group
  16. image
  17. And in SQL
  18. image
  19. image
  20. The last part is then to point the virtual directory used by the web service to the new application pool!
  21. image

This is the nice way to do it. What you can do as well is give the WSS_WPG group access to

  1. C:\windows\Temp
  2. image
  3. Also to to the framework temp folder, C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\Temporary ASP.NET Files
  4. image
  5. If you did something wrong, like type the incorrect password for the identity, then when you browse the web service, you will get this error:
  6. image
  7. So ensure all the steps are correct and ensure the application pool is started and the account is in the WSS_WOG group.

This security model solves all problems with permissions running ASP.NET and I hope you use it in production! NO USER NAMES AND PASSWORDS in the web.config

Ok we diverted a bit here, lets get moving, but at least we now have a clean web.config, this makes deployments much easier.

Creating the solution for the consumer

First thing we going to do is create the client project. I have decided to go for a typical Win32 application.

image

First, we going to design the interface, based on the inputs.

image

The next step is to add a web reference to the project. Visual Studio will then generate the PROXY class for you, which you can then interface with.

image

You can then choose a web service. I chose the one running on my local machine.

image

Give the Web reference a name. Mine is AuditWS. Then click Add Reference.

Click show all files.

image

Then you can browse the files that the reference created, we will not alter them, however, keep in mind if you ever deploy a new version of the web service, then the proxy class may need to be updated, which is easy, just right click the reference and click update.

Reference.cs contains the proxy class.

image

Notice that the proxy class has the default URL, in reality we will store the URL is the app.config for this application. Also, you probably have different URL’s for different environments.

Now we need to write two methods, the one method calls the simple web method and the other will call the composite web method, both web methods do the exact same thing, the only difference is the simple supports http/soap posts where is the composite supports only soap.

We will keep this consumer simple with no BOL or DAL layers, in this case the DAL layer would have been the web service reference, up to if you need this for complex consumer apps.

So the first thing is we add the following directive.

using AuditConsumer.AuditWS;

Then we can access the request and response objects.

Note: I added a ForexPurchase class to the project, so that the consuming application can emulate serializing a Forex object and auditing the object in the database for us.

using System;
using System.Configuration;
using System.Windows.Forms;
using AuditConsumer.AuditWS;

namespace AuditConsumer
{
    public partial class Form1 : Form
    {
        private string url;
        AuditWebService ws = new AuditWebService();

        public Form1()
        {
            InitializeComponent();

            switch (ConfigurationManager.AppSettings["Environment"])
            {
                case "Url_Development":
                    url = ConfigurationManager.AppSettings["Url_Development"];
                    break;
                case "Url_Test":
                    url = ConfigurationManager.AppSettings["Url_Test"];
                    break;
                case "Url_PreProduction":
                    url = ConfigurationManager.AppSettings["Url_PreProduction"];
                    break;
                case "Url_Production":
                    url = ConfigurationManager.AppSettings["Url_Production"];
                    break;
            }

            ws.Url = url;
        }

        /// <summary>
        ///
        /// You can test this using a web browser instead of texecuting this code, since it is all simple data types!
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnSimple_Click(object sender, EventArgs e)
        {
            string resp =  ws.AuditSimple(Guid.NewGuid().ToString(), txtSource.Text, txtStage.Text, txtStatus.Text, txtMessage.Text, SimulateBusinessTransaction());
            txtResponse.Text = resp + Environment.NewLine + SimulateBusinessTransaction();
        }

        /// <summary>
        /// This can not be executed by a web browser, since it had a composite data type:  AuditRequest
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnComposite_Click(object sender, EventArgs e)
        {
            AuditRequest r = BuildRequest();
            AuditResponse resp = ws.AuditComplex(r);
            txtResponse.Text = resp.Message + Environment.NewLine + resp.Status + Environment.NewLine + r.SerializedObject;
        }

        private AuditRequest BuildRequest()
        {
            AuditRequest r = new AuditRequest();
            r.Id = Guid.NewGuid();
            r.Source = txtSource.Text;
            r.Stage = txtStage.Text;
            r.Status = txtStatus.Text;
            r.Message = txtMessage.Text;
            r.SerializedObject = SimulateBusinessTransaction();
            return r;
        }

        /// <summary>
        /// This would be some BOL layer or even another web service call!
        /// </summary>
        private string SimulateBusinessTransaction()
        {
            ForexPurchase f = new ForexPurchase();
            f.Amount = 200;
            f.Authorised = true;
            f.FromCurreny = ForexPurchase.Currency.EUR;
            f.ToCurrency = ForexPurchase.Currency.ZAR;
            return f.Serialize().InnerXml;
        }

    }
}

Lets run it and see what happens! Set the project as the startup project and run in debug mode:

image

I get the following response when using simple:

image

I get the following response when using composite:

 image

This is a great test, we can see the error handling is being returned in the response object, now let us resolve the Database Access problems and then run it again. I think the problem is the application pool, it is using the incorrect identity, so i will fix that quickly.

image

Now, we run it again.

Click Simple:

image

Click complex:

image

Well, that wraps up consuming applications. In order for it to support connecting to other URL’s, add the following to the app.config:

<appSettings>
    <add key="Environment" value="Url_Development"/>
    <add key="Url_Development"  value="http://localhost/Romiko.Audit.WebServices/AuditWebService.asmx"/>
    <add key="Url_Test" value="http://localhost/Romiko.Audit.WebServices/AuditWebService.asmx"/>
    <add key="Url_PreProduction" value="http://localhost/Romiko.Audit.WebServices/AuditWebService.asmx"/>
    <add key="Url_Production" value="http://localhost/Romiko.Audit.WebServices/AuditWebService.asmx"/>
</appSettings>

Download Source Code

 

Conclusion

I hope you enjoyed this article. In the next article I will discuss consuming web services from a Microsoft BizTalk Soap Send Port.

Developing Web Services: Part 1

Hi Folks,

I am rather sick this week, not sure why, I was healthy for the last year, well, I think I know why…Was out late on Friday night and got home in the late hours of the morning, the music was banging and the party was top notch, so the FLU has hit me in full force, I cannot sleep, so let’s blog!

I use web services allot, mainly to orchestrate business processes and keep components lightweight and reusable. In my quest to master these strange beasts, I think I have learnt some knowledge that would be nice to share with the community as a favour in return for all the Google searching I do, on a day to day basis to get my work done!

This article is going to show you how to get started with a web service and more, we take the bottom up approach and I will try demonstrate how to write a professional web service, I may have gone over the edge and introduced allot of layers, but this is on purpose, complex web services require layers! Especially those that interface with databases, this then allows encapsulation of key software components.

Introduction

This blog is going to discuss how to develop a web service. I will be discussing various aspects of the development process and code separation. Web services are now commonly used to achieve Service Orientated Architecture solutions.

The way in which I like to approach the development of a web service, is from the angle of a Request and Response model.

Remember everything we do with a web service, you are always going to develop the actual service and then the actual consumer of a service! Consumers of a service usually have a proxy class to represent the service.

Scenario

We have an auditing system and use a web service to call the audit web service. This will require the actual development of the web service and also a consumer of the web service. The development will consist of 3 layers. The UI layer, which is the actual web method, the Business Object layer, which will contain all the logic for both the server service and consumer and then lastly the data access layer.

It is best to think of this project as two legs, so this is how we will develop. The order in which the development process will start is as follows:

Part 1:

  1. SQL Tables and Stored Procedure
  2. Data Access Layer
  3. Business Object Layer (Server Side)
  4. UI (The actual web service)
  5. Security configuration

The other leg is the consumer:

Part 2:

  1. Data Access Layer (An agent that manages the retrieval of data from the web service)#
  2. Business Object layer
  3. UI (Display the results of the web service response.

Before we get down and dirty, lets cover some basics of web services.

This blog covers Part 1, the next blog in the future will build on what we learn here and show how to develop consumers for web services.

Source Code is here:

http://grounding.co.za/files/folders/documents/entry2026.aspx

Bindings

I would like to touch on the subject of web services and the protocols that they can be bind to.

When a web service is developed, it can be bound to various protocols, these are:

  1. SOAP
  2. HTTP GET
  3. HTTP SET

A web services binding information is contained with the Web Service Description Language for the specific web service.

Essentially, it is important to realise that a web service can be consumed without a fancy consumer application being developed, by utilising simple data types such as strings and integers etc. They can then be invoked by a web browser directly and the results will be displayed as XML in the web browser. However when developing a web service in this manner, you will need to ensure the binding information for the web service supports this, this is done by specifying in the WSDL that HTTP Get and HTTP Set is supported. In .NET you never modify the WSDL directly, so naturally this setting of binding information is done in the web.config.

To enable this on your ASMX web service you just need a few parameters in the web.config. Inside the <System.web> settings.

    <system.web>
        <webServices>
            <protocols>
                <add name="HttpGet"/>
                <add name="HttpPost"/>
            </protocols>
        </webServices>
    </system.web>

By adding the following in the web.config, the WSDL will generate the new binding information, here is an example extracted from the WSDL. As usual Soap 1.1 and Soap 1.2 is always supported in .NET 2005. Port definitions are used and in fact when working with BPEL and other orchestration systems, it will be important to understand ports and port types, which is beyond the scope of this blog.

<wsdl:service name="currencyService">
<wsdl:port name="currencyServiceSoap" binding="tns:currencyServiceSoap">
<soap:address location="
https://mywebservice/CurrencyService.asmx" />
</wsdl:port>
<wsdl:port name="currencyServiceSoap12" binding="tns:currencyServiceSoap12">
<soap12:address location="
https://mywebservice/CurrencyService.asmx" />
</wsdl:port>
<wsdl:port name="currencyServiceHttpGet" binding="tns:currencyServiceHttpGet">
<http:address location="
https://mywebservice/CurrencyService.asmx" />
</wsdl:port>
<wsdl:port name="currencyServiceHttpPost" binding="tns:currencyServiceHttpPost">
<http:address location="
https://mywebservice/CurrencyService.asmx" />
</wsdl:port>
</wsdl:service>

Then you can invoke web services with primitive data types directly from the web browser, and parse any web method arguments in the URL. Here is a sample URL:

https://mywebservice/CurrencyService.asmx?op=AddCurrency?vCurrencyCode=USD

The above Web Method has a web method called AddCurrency and a parameter called CurrencyCode, so we adding a new currency to the system, in this case USD. In C# the web method would look something like this:

[WebMethod(Description = "Add a new Currency to the system.")]
public void AddCurrency(string vCurrencyCode)
{
CurrencyDA da = new CurrencyDA(); //Create instance of Data Access Layer
if (da.AddCurrency(vCurrencyCode)); //Add the currency to the SQL table
return true;
else
return false;
}

However, it is important to realise that if a web service web method requires a custom .NET object type, either as a return value or paramter, then SOAP is the only binding that will suffice to do this, since these are complex data types that are serializeable. We will discuss serialization in the next section. Take for example this method, it takes a parameter that is not a primitive type.

public void AddCurrency(MyCompany.Animals.Tiger vTiger)

There is no way that you can pass a tiger object in the URL. Ok we got the point across lets move on to the explicit bindings.

HTTP-GET and HTTP-POST Bindings

The following shows the supported types for HTTP GET and HTTP POST protocol.

Primitive types (limited)
The following primitive types are converted into name/value pairs represented as strings: Boolean, Currency, DateTime, Decimal, Double, Int16, Int32, Int64, Single, String, UInt16, Uint32, and UInt64.

Enum types
From the client’s perspective, enumeration types become classes with a static constant string field for each value.

Arrays of primitives, enums
Arrays of the above.

SOAP Bindings

The data types supported by XML Web services when the SOAP protocol is used are directly correlated with the data types that can be serialized into XML. ASP.NET serializes and deserializes XML using the XmlSerializer class. For details on the types that are supported by XmlSerializer, see Introducing XML Serialization.

In addition to the primitive types listed in the preceding table, XML Web services using the SOAP protocol support the following additional data types.

Classes and structs,
Arrays of classes and structs
Class and struct types with public fields or properties. The public properties and fields are serialized. Classes must have a default constructor that does not accept any parameters.

DataSet,
Arrays of DataSet
ADO.NET DataSet types. Dataset types can also appear as fields in classes or structs.

XmlNode,
Arrays of XmlNode
XmlNode is an in-memory representation of an XML fragment. XmlNodes types can be passed as parameters or return values, and they are added to the rest of the XML passed to the XML Web service in a SOAP-compliant manner. This enables you to pass or return XML even if its structure changes from call to call, or if you do not know all the types being passed. XmlNode types can also appear as fields in classes or structs.

Below is the WSDL we will generate in this blog, notice that there are two web methods, only one of them can bind to HTTP, since it is primitive types and the other is composite hence soap only.

image

Streams

What about Streams? I know allot of you are going to think, I want to send the contents of a file to a web service. Well, streams are not serializable, so no, they not supported. However, you can cheat this, instead of using a stream as a data type, you will use a byte array byte[], then later on in the code, you can convert your byte array back to a stream, the reason why this can be done, is that if you look at the data types supported, arrays, are one of them and bytes are pretty simple, so a byte array is serializable.

Below is the solution to uploading files through a web service:

[WebMethod]
public bool UploadFile(Byte[] fileStream)
{
      Handler handler = new Handler();
      Stream stream = new MemoryStream(fileStream);
      return handler.Upload(stream);
}

You probably wondering how the consumer code looks that invokes this web service:

All you do is read your file into a stream or directly into a byte array and called the web service. So we just converting a stream to a byte array, or simply sending a stream of data to a web service. The call the web service is in bold.

    Byte[] bytes = new byte[fileStream.Length]; 
    fileStream.Read(bytes, 0, (int)fileStream.Length);
    fileStream.Close();
    fms.UploadFile(bytes);

The object fms, in this case, is actually an instance of a proxy class on my consumer application, we will talk about this in much more detail later on. Don’t worry if fms looks confusing for now, we will cover this in the section on Developing the Consumer of the web service.

Serialization

When you create a custom class, and intent it to be used with a web service as a parameter or return value, then the class will need to be serializable. You going to see this word allot when creating web services and even more when wrestling with BizTalk.

What serialization does, is that it will persist an instance of an object to XML, this means that all the properties of the object are written as XML elements.

This means that the implementation features of the object are lost when serializing objects. When data is sent over SOAP, custom objects are converted to XML and encapsulated in a SOAP message, if we feeling snoopy, we will sniff packets on the network with a web service call to see this in action!

Another point to remember, is that once the object is received on the web service side, the XML data is collected and the object is deserialized, this means the XML is transferred back into memory to represent the .NET custom object type, and now you see why the implementation of the object is lost.

This brings me to a hot debate I have with allot of developers interfacing with my web services…

Most of my web services will only accept a custom .NET object instead of an XmlDocument as a paramter type. Allot of people think that when you develop a web service, you should use the XmlDocument type as the method parameter, this is wrong and write as the same time.

You all remember type-safe code, well then from this perspective using XmlDocument data type is not cool, because people can send any garbage to the web service.

Imagine you have a purchase order XML Document and a web service like this:

[WebMethod]
public bool SendPO(XmlDocument purchaseOrder)
{
      Handler handler = new Handler();
      return handler.Process(purchaseOrder);
}

Do you see the problem, any data can be sent. Rather create a custom class representing the purchase order and make it serializable:

[Serializable]
public class PurchaseOrder
{

//Fill in the fields here like PONumber, Address etc.

}

Then your web method will be like this, so it is type safe.

[WebMethod]
public bool SendPO(PurchaseOrder purchaseOrder)
{
      Handler handler = new Handler();
      return handler.Process(purchaseOrder);
}

Now you wondering, how the hell does a consumer of a web service know how to create an instance of my PurchaseOrder class and access it’s properties, well, when they generate there consumer code, the proxy class will do all this, and this is something we will cover later on in the blog!

In short, .NET, Eclipse, NetBeans and other IDE’s come with web service consumer generation tools. If you smart and ahead of the pack here, .NET used the XSD.EXE tool to generate proxy classes, and my oh my what bad tool it is, I hope in 2008 they make proxy classes more clever. XSD.EXE is the tool to use when generating .NET classes from XSD’s and so on. So think about it, a code generation tool would just read the WSDL for a web service, and since the WSDL contains the schema of all object types, it is then fairly easy to extract this schema and then generate classes, and that is all their is to proxy classes. The same goes for any other IDE, you more than welcome to write your own proxy classes, as long as they can Cast you fine, in this article we use proxy classes.

One of my gripes with XSD.EXE is it’s lack of support for Include and Import directives for XSD’s, this is a serious hindrance for those of use using these features of XML Schema design.

Here is a snipped of the IDE in Eclipse(Java) that generates client code to access a web service I made for a university project. So now you know, when the word proxy comes in, all it is doing is generating client classes for a consumer that are serializable! In .NET it is even simpler, you just add a web reference to a project

clip_image002

To understand serialization in greater detail, I have explicitly created a feature in our to be audit web service to store .NET objects that are serializable. Imagine a high security system, where during the business process a Foreign Exchange purchase object needs to be serialized to the database

Developing the Web Service

Ok, so enough chit chat, lets geek it up. What we going to do is develop a web service that can be called by serveral business processes to audit events to a SQL table, not too exciting but I think it is something meaningful than a Hello World….

Here is a high level overview of the web service we will develop.

image

image

  • SQL Tables and Stored Procedure

  • The Audit Table

  • USE [Audit]
    GO

    SET ANSI_NULLS ON
    GO
    SET QUOTED_IDENTIFIER ON
    GO
    SET ANSI_PADDING ON
    GO
    CREATE TABLE [dbo].[aud_AuditTrail](
        [aud_ID] [uniqueidentifier] NOT NULL,
        [aud_Source] [nvarchar](255) NOT NULL,
        [aud_Stage] [varchar](50) NOT NULL,
        [aud_Status] [varchar](50) NOT NULL,
        [aud_Message] [ntext] NULL,
        [aud_SerializedObject] [xml] NULL,
        [aud_Date] [datetime] NOT NULL
    ) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]

    GO
    SET ANSI_PADDING OFF

    The stored procedure for inserting data and updating a audit record log:

    USE [Audit]
    GO
    SET ANSI_NULLS ON
    GO
    SET QUOTED_IDENTIFIER ON
    GO
    CREATE  PROCEDURE [dbo].[Audit]
    (   
        @ID uniqueidentifier
    ,    @Source     nvarchar(255)
    ,    @Stage     varchar(50)   
    ,    @Status     varchar(50)   
    ,    @Message ntext = null
    ,    @SerializedObject xml = null
    )
    AS
    BEGIN
        IF NOT EXISTS (SELECT 1 FROM dbo.aud_AuditTrail
        WHERE aud_ID = @ID)
        BEGIN
            INSERT INTO aud_AuditTrail
                   (
                    aud_ID
                   ,aud_Source
                   ,aud_Stage
                   ,aud_Status
                   ,aud_Message   
                   ,aud_SerializedObject
                   ,aud_Date
    )
             VALUES
                    (   
                        @ID
                    ,    @Source
                    ,    @Stage
                    ,    @Status
                    ,   @Message
                    ,    @SerializedObject
                    ,   getdate()
                    )
        END
        ELSE
        BEGIN
            UPDATE    aud_AuditTrail
            SET        aud_Source = @Source
            ,        aud_Stage = @Stage
            ,        aud_Status = @Status
            ,        aud_Message = @Message
            ,        aud_SerializedObject = @SerializedObject
            WHERE    aud_ID= @ID

            SELECT aud_ID FROM aud_AuditTrail WHERE aud_ID = @ID
        END
    END

    The stored procedure for reporting on the data:

    USE [Audit]
    GO
    SET ANSI_NULLS ON
    GO
    SET QUOTED_IDENTIFIER ON
    GO

    CREATE Proc [dbo].[ReportAuditTrail]
    (
    @ID uniqueidentifier,
    @Source nvarchar(255),
    @DateFrom datetime,
    @DateTo datetime
    )
    as

    if (@ID is not null)
    select
        aud_ID as ID,
        aud_Source as Source,
        aud_Stage as Stage,
        aud_Status as [Status],
        aud_Message as [Message],
        aud_SerializedObject as [SerializedObject],
        aud_Date as Date
    from dbo.aud_AuditTrail

    where @ID  = aud_ID
    else
    select    
        aud_ID as ID,
        aud_Source as Source,
        aud_Stage as Stage,
        aud_Status as [Status],
        aud_Message as [Message],
        aud_SerializedObject as [SerializedObject],
        aud_Date as Date
    from dbo.aud_AuditTrail
    where  aud_Date between @DateFrom and @DateTo
    and aud_Source  = isnull(@Source,aud_Source)

      

    Data Access Layer

    Perfect, we now have our procedures in place for auditing a business process and also for running a report on a business process.

    The next step is the development of the Data Access Layer. We will create a new project called Romiko.Audit.DAL and select class library.

    image

    First thing we do is add a reference to System.Configuration.

    image

    Then we rename the class and add various implementation code, below is the final code for access the data.

  • using System;
    using System.Configuration;
    using System.Data;
    using System.Data.SqlClient;

    namespace Romiko.Audit.DAL
    {
        public class AuditDA
        {
            private string connectionString;
            public AuditDA()
            {
                ConnectionString = ConfigurationManager.ConnectionStrings["AuditDatabase"].ConnectionString;
            }

            public string ConnectionString
            {
                get { return connectionString; }
                set { connectionString = value; }
            }

            public void Audit(Guid id, string source, string stage, string status, string message, string serializedObject)
            {
                using (SqlConnection conn = new SqlConnection(ConnectionString))
                {
                    SqlCommand cmd = new SqlCommand();
                    cmd.Connection = conn;
                    cmd.CommandType = CommandType.StoredProcedure;
                    cmd.CommandText = "Audit";
                    cmd.Parameters.Add(new SqlParameter("ID", id));
                    cmd.Parameters.Add(new SqlParameter("Source", source));
                    cmd.Parameters.Add(new SqlParameter("Stage", stage));
                    cmd.Parameters.Add(new SqlParameter("Status", status));
                    if (status == null)
                         cmd.Parameters.Add(new SqlParameter("Message", DBNull.Value));
                    else
                        cmd.Parameters.Add(new SqlParameter("Message", status));
                    if (serializedObject == null)
                        cmd.Parameters.Add(new SqlParameter("SerializedObject", DBNull.Value));
                    else
                        cmd.Parameters.Add(new SqlParameter("SerializedObject", serializedObject));
                    conn.Open();
                   cmd.ExecuteNonQuery();
                }
            }
        }
    }

    We not yet finished, I enjoy test driven development, so the next step is to create a unit test and see if we can enter data into the SQL table. So we right click the Audit method and click Create Unit Test

    image

    From here we accept the default settings.

    image

    You will be prompted for a Test Project Name, I entered the following, where Romiko represents my company name.

    image

    The Test project will have generate a test method for us that we can invoke, so we need to fill in the values.

    image

    So, I am going to fill them in and test. However, there is an important point here. What about the connection string? Well since this class is being invoked from my test project, it will look for the configuration of the connection string in the app.config. However if it was a web service invoking the class, it would look for the connection string in the web.config, remember this point! If we do not do this, this line of code will kick a fuss:

    ConnectionString = ConfigurationManager.ConnectionStrings["AuditDatabase"].ConnectionString;

    So, before we continue, I will add an app.config file to the test project.

    image

    I replace the contents of the file with this:

    <?xml version=’1.0′ encoding=’utf-8′?>
    <configuration>

        <appSettings>

        </appSettings>

        <connectionStrings>
            <add name="AuditDatabase" connectionString="Data Source=(local);Initial Catalog=Audit;Integrated Security=True" providerName="System.Data.SqlClient"/>
        </connectionStrings>

    </configuration>

     

    Now, we ready to rock, lets setup the test method, run it, and see it adds data to SQL.

    Here my edited test method.

    image

    Notice I changed my id to Guide.NewGuid() the way the test wizard generated it was not cool! It used New Guide(), which is always zeros!

    So lets run it, I go to my test view and run the test.

    image

    It succeeds, So now the unit tests for the DAL and concepts associated with it are working and we not living in a dream world.

    image

    Here is the data in SQL!

    image

    Ok now for a quick side track, while we stuck in the deep doldrums of the Data Access Layer, lets me introduce you to serialization here, what I am going to do is:

    1. Create a new class in the Test Project and call it ForexPurchase,
    2. Create a new test method that will populate an instance of this class and persist it to the database.
    3. Store the serialized object to a local variable
    4. create a instance of a new ForexPurchase
    5. Deserialize the local variable in step 3 into the new object in step 4

    The above has no practical purpose here, but what I am demonstrating is the storage of objects to xml and then a way to move the xml back into new objects. Allot of configuration systems, like the BizTalk rules and configuration is stored in the database as serialised objects, and when needed is deserialized back into memory. You could do the same for ASP.NET pages or creating your own configuration system for a custom application, like file mappings or answers to questionnaires etc.

    In fact this is a neat trick when you want to deep copy arrays, since arrays are always reference types. So you can serialize an array and deserialize it, and it works with generic arrays to. In the bonus section of this blog I will show you how to deep copy an array of custom .NET class type for fun. Ok back to work!

    So I fist create a new test Class.

    image

    Then we fill in some properties for the forex trade, lets keep it simple, we store a customers order and if he is authorised to make a conversion. Note the sample class below would be in a business object in another project, so this is just for demo purposes, hence we store it in the test project, we can always move it later to a Business Object Layer.

    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Xml;
    using System.Xml.Serialization;

    namespace Romiko.Audit.DAL.Tests
    {
        [Serializable]
        public class ForexPurchase
        {
            public enum Currency
            {
             EUR, ZAR, GBP  
            }

            private Currency fromCurreny;
            private Currency toCurrency;
            private double amount;
            private bool authorised = false;
            private const double commission = 0.10d;
            public const string Namespace = "
    http://schemas.Romiko/ForexOrder";

            public Currency FromCurreny
            {
                get { return fromCurreny; }
                set { fromCurreny = value; }
            }

            public Currency ToCurrency
            {
                get { return toCurrency; }
                set { toCurrency = value; }
            }

            public double Amount
            {
                get { return amount; }
                set { amount = value; }
            }

            public bool Authorised
            {
                get { return authorised; }
                set { authorised = value; }
            }

            public ForexPurchase(Currency vFromCurreny, Currency vToCurrency, double vAmount)
            {
                fromCurreny = vFromCurreny;
                toCurrency = vToCurrency;
                amount = vAmount;
            }

            /// <summary>
            /// Paramterless contructor is needed for Serialization
            /// </summary>
            public ForexPurchase()
            {
            }

            public  bool  IsAuthorised()
            {
                if (Authorised)
                    return true;
                else
                    return false;
            }

            public void Authorise(bool isValid)
            {
                if (isValid)
                    Authorised = true;

            }

            /// <summary>
            /// This function creates an Xml representation of an instance of this object
            /// </summary>
            /// <returns></returns>
            public XmlDocument Serialize()
            {
                XmlSerializer X = new XmlSerializer(typeof(ForexPurchase), Namespace);
                MemoryStream ms = new MemoryStream();
                X.Serialize(ms, this);
                ms.Position = 0;
                XmlDocument doc = new XmlDocument();
                doc.Load(ms);
                return doc;
            }

            /// <summary>
            /// From XML to ForexPurchase
            /// </summary>
            /// <param name="doc"></param>
            /// <returns></returns>
            public static ForexPurchase ForexPurchaseDocument(XmlNode doc)
            {
                if (doc == null)
                    return null;
                XmlSerializer X = new XmlSerializer(typeof(ForexPurchase), Namespace);
                return (ForexPurchase)X.Deserialize(new XmlNodeReader(doc));
            }

            /// <summary>
            /// Deep copies arrays of ForexPurchase objects, this means arrays can be copied and bypass it’s intrinsic reference type.
            /// </summary>
            /// <param name="sourceArray"></param>
            /// <returns></returns>
            public static ForexPurchase[] DeepCopy(ForexPurchase[] sourceArray)
            {
                ForexPurchase[] forexPurchase = new ForexPurchase[sourceArray.Length];
                List<XmlNode> l = new List<XmlNode>();

                for (int i = 0; i < sourceArray.Length; i++)
                {
                    l.Add(sourceArray[i].Serialize());
                }
                for (int j = 0; j < sourceArray.Length; j++)
                {
                    forexPurchase[j] = ForexPurchaseDocument(l[j]);
                }
                return forexPurchase;
            }
        }
    }

    The above class has allot of goodies in it, it has implementations for serialization, deserialization and deep copy of arrays!

    Ok, now we can create a new test method, populate the object and then save it in the audit table, as it is a business requirement when a customer asks for a currency trade! Notice this class has a Serializable attribute set up.

    Serialization

  • [TestMethod()]
           public void AuditFileMetDataSerialisation()
           {
               AuditDA target = new AuditDA();

               Guid id = Guid.NewGuid();

               string source = "TestSource";

               string stage = "Start";

               string status = "Testing";

               string message = "We made it to the database!";

               ForexPurchase fp = new ForexPurchase(ForexPurchase.Currency.EUR, ForexPurchase.Currency.ZAR, 200.57d);
               //lets simulate an authorisation business process, we should see this in the audit trail, that it was authorised!
               fp.Authorise(true);

               string serializedObject = fp.Serialize().InnerXml;

               target.Audit(id, source, stage, status, message, serializedObject);
               bool success = true;
               Assert.IsTrue(success);
           }

     

  • When I run the test method, this is the XML generated and we will store it for auditing in the SQL database.

  • image 

  • I run the test and it passed, and now have a look in SQL:

  • image

  • image

    The actual object is in here, notice no methods, only properties, so now you see why implementation code is lost during serialisation, no problem really, you don’t need it here!

    Deserialization

    The last part I want to show is take XML data and shoving it back into an Object, we call this deserialisation, and it can be a powerful tool.

    We will create a new test method, that takes XML data and puts it into a instance of the ForexPurchase class.

    Below is my new Test Method.

    [TestMethod()]
    public void Deserialization()
    {
        bool success;
        ForexPurchase target = new ForexPurchase();
        XmlDocument doc = new XmlDocument();
        string xml =
        "<ForexPurchase xmlns:xsi=\"
    http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"" +
        " xmlns=\"
    http://schemas.Romiko/ForexOrder\"><FromCurreny>ZAR</FromCurreny><ToCurrency>GBP</ToCurrency><Amount>300.57</Amount>" +
        "<Authorised>true</Authorised></ForexPurchase>";

        doc.LoadXml(xml);

        target = ForexPurchase.ForexPurchaseDocument(doc);
        if(target.Amount > 0 )
        success = true;
        else
            success = false;
        Assert.IsTrue(success);
    }

  • Here is proof the XML went into the object, I put a breakpoint near the end of the test method and look at the object variable target:

    image
    image 

    We could have retrieved the XML from a database, and in fact in many applications, this is how configuration is stored and retrieved. This allows us to type-safe our configuration data, you could even develop a high level language in this way, maybe that will be for another blog…

    Ok, this wraps up the DAL, and a quick introduction here to developing serializable/deserializable classes. I cheated a bit here and abused a test class project, but that is what they for! Your homework here is to play with the DeepCopy of an array of these forex purchases. The method is there to use in the Forex class.
    Later on we can re-factor this and move the ForexPurchase  class to the BOL somewhere…We will do this soon. 

  • Business Object Layer

  • We now going to develop the Business object layer for the web service that stores audit information.

  • Add  a new project to the solution. I called mine Romiko.Audit.BOL

  • image

  • Remember web services always work on a Request/Response model. So we add two folders for managing the data to and from a web service.

  • image

  • Another folder we need to create is for Handlers, this is the place where the request and response objects get handled to and from the Data Access Layer. So now we have three folders.

  • image

  • The first place to start development is in the building of the request and response object that the web service uses.

  • We will create a new classes called AuditRequest, AuditResponse and AuditHandler.

  • image 

  • We add a reference to the DAL

  • image

    My AuditRequest class looks like this, it basically represent my input parameters that I will pass to the DAL, nicely encapsulated into a lightweight object.

    using System;

    namespace Romiko.Audit.BOL.Request
    {
        public class AuditRequest
        {
            public Guid Id;
            public string Source;
            public string Stage;
            public string Status;
            public string Message;
            public string SerializedObject;
        }
    }

  • Here is my AuditResponse Class

  • namespace Romiko.Audit.BOL.Response
    {
        public class AuditResponse
        {

            public enum AuditStatus
            {
                Success, Failed
            }
            public AuditStatus Status;
            public string Message;
        }
    }

    What you need to realise is the response object will become a soap message that will be meaningful for a consumer of the web service!

    The Handler is the object that the web service will call, hence why it deals with request and response objects.

    using System;
    using Romiko.Audit.BOL.Request;
    using Romiko.Audit.BOL.Response;
    using Romiko.Audit.DAL;

    namespace Romiko.Audit.BOL.Handler
    {
       public  class AuditHandler
        {

            AuditDA da = new AuditDA();

            public AuditResponse Audit(AuditRequest request)
            {
                AuditResponse response = new AuditResponse();
                try
                {              
                    da.Audit(request.Id ,request.Source, request.Stage,request.Status, request.Message, request.SerializedObject);
                    response.Status = AuditResponse.AuditStatus.Success;
                    response.Message = "Saved Audit to Database";
                    return response;
                }
                catch (Exception ex)
                {
                    response.Status = AuditResponse.AuditStatus.Failed;
                    response.Message = "Error: " + ex.Message;
                    return response;
                }
            }

        }
    }

  • Handlers are very powerful layers, you can use them to take care of any funny data handling that needs to be done with input from a request object, such as cleaning and preparing it for the call to the data access layer. For example you might need to read a response object and do some decorating on it before sending it back to the web service to give to the calling consumer. In this example, I have kept them simple, for good reason, however in the real world some of your implementation will need logic, some examples of logic in a handler can be:

    1. Security authentication with custom Soap Header data (I will discuss this in another blog one day)

    2. Validating request objects

    3. Building complex response objects that need input from multiple objects

    So now you see the benefit above, we have discrete objects managing the flow to and from our lower layers in the software application.

  • We done now with the BOL, lets move to the web service itself.

  • Interface

  • Yeah yeah, a web service does not really have a UI, but hey, I need a UI so we will put the web interface here. This is the easiest part of the project!

  • So, we create another project.

  • image

  • I renamed my default web service, and this is what it looks like..for now…

  • image

  • We now going to change the default code, which looks like this for now…

  • image

  • Now is the easier part, two or three lines of code and we snorted and sorted! First add a reference to the BOL.

  • image

  • Here is the modified web method.

     

  • using System;
    using System.Web.Services;
    using System.ComponentModel;
    using Romiko.Audit.BOL.Handler;
    using Romiko.Audit.BOL.Request;
    using Romiko.Audit.BOL.Response;

    namespace Romiko.Audit.WebServices
    {
        /// <summary>
        /// Audit Web Service, used to audit various stages of internal business processed, mainly from BPEL orchestrations using custom .NET
        /// </summary>
        [WebService(Namespace = "
    http://romiko.audit.com")]
        [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
        [ToolboxItem(false)]
        public class AuditWebService : WebService
        {

            [WebMethod]
            public AuditResponse AuditComplex(AuditRequest request)
            {
                AuditHandler handler = new AuditHandler();
                return handler.Audit(request);
            } 
        }
    }

  •  

  • I changed the name of the web service class, if you do this, edit the asmx file with the xmleditor and ensure the class reference is updated as well:

  • <%@ WebService Language="C#" CodeBehind="AuditWebService.asmx.cs" Class="Romiko.Audit.WebServices.AuditWebService" %>
  • We not done, remember the connection string, since we invoking it from the web service, it needs to be in the web.config, we created an app.config for test purposes but for production the web.config is used, so we add a web.config file with this in it, also we put http-get\http-post bindings in it for now, however they cannot be used as we use complex data types as input and output paramters for the above web method. However if your web method was accepting a string and returning a string, then you can sure enough use HTTP bindings, once we have done this, we will create another web method that works on primitive type shortly so you can see how HTTP works with this web service and then in the later section on developing a consumer application you can see how soap is used.

    <?xml version="1.0"?>
    <configuration>
        <appSettings/>
        <connectionStrings>
            <add name="FileManagementDatabase" connectionString="Data Source=(local);Initial Catalog=FileManagement;Integrated Security=True" providerName="System.Data.SqlClient"/>
        </connectionStrings>
        <system.web>
            <compilation debug="true" />
            <authentication mode="Windows" />
            <trust level="Full" />
            <webServices>
                <protocols>
                    <add name="HttpGet"/>
                    <add name="HttpPost"/>
                </protocols>
            </webServices>
        </system.web>
    </configuration>

  • Ok we looking good, lets add support in the web service for a web method that supports http-get and http-post calls, since maybe we want an XSLT to call the web service for audits! It can surely be done, we might have a business process that requires an audit during a document transformation.

    Since we use the request response model, we will create a primitive web method that takes primitive types, and within the method we will convert the complex objects to primitives, like so:

  • [WebMethod]
    public string AuditSimple(string id, string source, string stage, string status, string message, string serializedObject)
    {
        AuditRequest request = new AuditRequest();
        AuditHandler handler = new AuditHandler();

        request.Id = new Guid(id);
        request.Source = source;
        request.Stage = stage;
        request.Status = status;
        request.Message = message;
        request.SerializedObject = serializedObject;

        AuditResponse response = handler.Audit(request);
        return "Status: " + response.Status + ", Message:  " + response.Message;
    }

  • Why have two methods like this, well it depends, you can be strict and only have the complex method, but you might have simple applications that need to interface with a web method and so simple types should be used, hence two web methods for flexibility that does not compromise the credibility of the data coming in, notice both web methods reuse all the code in the other layers.

    This is our complete project in regards to the web service leg.
     image

    This is the way I like to write my web service, a colleague of mine, Chris Hagens, introduced me to this style of writing web services and it is a bit long winded in the beginning, but when you get used to it, you see the benefits when changes come!

    Testing

    I set the web page to be the default and then run the project in debug mode:

    image

    Here is the interface

    image

    We can test the simple method, since it support http, so we can consume it in a web browser, however the complex web method cannot be consumed in a browser!

    image

    image

    image

    Notice above the bindings has HTTP GET and HTTP POST the complex web method ONLY has SOAP.

    image

     

    Anyway’s, we can now enter parameters into the web method and it should update the SQL database. Just make sure you put in a valid GUID, I did not add exception handling for this, you can do that as a homework exercise 🙂

    Here is my input for test:

    image

    And here is the response.

    image

    image

  • Security configuration

    In regards to security and deployment to a web server, I do not like to store user names and passwords in my web.config files. What you should do is as follows:

    1. Create a Domain Service Account for the Web Service
    2. Give it access to the Databases and add it to the IIS_WPG group
    3. Give this group access to the c:\windows\temp folder
    4. Give this group access to the C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\Temporary ASP.NET Files
    5. Create an Application Pool is IIS for the web service and set the identity of the application pool to run under the service account created in step 1.
    6. Set the Web Service virtual directory in IIS to use this application pool
    7. iisreset

    Download

    Here is a link to the sample application, I hope you enjoy it.

    http://grounding.co.za/files/folders/documents/entry2026.aspx

    Conclusion

    I hope you enjoyed this as much as I did, and in PART 2 I will show you how to consume complex web methods! Until then, Cheers

    Romiko

    WSDL

  • Altova XML Spy

  • We can now use Altova XML spy to see the WSDL with a friendly GUI, I wanted to add this so you see the different sections of the WSDL.

  • So I open XML Spy and then click File, Open URL.

  • image

  • We then click this button

  • image

  • Add the WSDL is as follows in the GUI form:

  • image

  • Here is the web service description language for the web service we made.

    <?xml version="1.0" encoding="utf-8" ?>

    <wsdl:definitions xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tm="http://microsoft.com/wsdl/mime/textMatching/" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:mime="http://schemas.xmlsoap.org/wsdl/mime/" xmlns:tns="http://romiko.audit.com" xmlns:s1="http://microsoft.com/wsdl/types/" xmlns:s="http://www.w3.org/2001/XMLSchema" xmlns:soap12="http://schemas.xmlsoap.org/wsdl/soap12/" xmlns:http="http://schemas.xmlsoap.org/wsdl/http/" targetNamespace="http://romiko.audit.com" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/">

    <wsdl:types>

    <s:schema elementFormDefault="qualified" targetNamespace="http://romiko.audit.com">

    <s:import namespace="http://microsoft.com/wsdl/types/" />

    <s:element name="AuditComplex">

    <s:complexType>

    <s:sequence>

    <s:element minOccurs="0" maxOccurs="1" name="request" type="tns:AuditRequest" />

    </s:sequence>

    </s:complexType>

    </s:element>

    <s:complexType name="AuditRequest">

    <s:sequence>

    <s:element minOccurs="1" maxOccurs="1" name="Id" type="s1:guid" />

    <s:element minOccurs="0" maxOccurs="1" name="Source" type="s:string" />

    <s:element minOccurs="0" maxOccurs="1" name="Stage" type="s:string" />

    <s:element minOccurs="0" maxOccurs="1" name="Status" type="s:string" />

    <s:element minOccurs="0" maxOccurs="1" name="Message" type="s:string" />

    <s:element minOccurs="0" maxOccurs="1" name="SerializedObject" type="s:string" />

    </s:sequence>

    </s:complexType>

    <s:element name="AuditComplexResponse">

    <s:complexType>

    <s:sequence>

    <s:element minOccurs="0" maxOccurs="1" name="AuditComplexResult" type="tns:AuditResponse" />

    </s:sequence>

    </s:complexType>

    </s:element>

    <s:complexType name="AuditResponse">

    <s:sequence>

    <s:element minOccurs="1" maxOccurs="1" name="Status" type="tns:AuditStatus" />

    <s:element minOccurs="0" maxOccurs="1" name="Message" type="s:string" />

    </s:sequence>

    </s:complexType>

    <s:simpleType name="AuditStatus">

    <s:restriction base="s:string">

    <s:enumeration value="Success" />

    <s:enumeration value="Failed" />

    </s:restriction>

    </s:simpleType>

    <s:element name="AuditSimple">

    <s:complexType>

    <s:sequence>

    <s:element minOccurs="0" maxOccurs="1" name="id" type="s:string" />

    <s:element minOccurs="0" maxOccurs="1" name="source" type="s:string" />

    <s:element minOccurs="0" maxOccurs="1" name="stage" type="s:string" />

    <s:element minOccurs="0" maxOccurs="1" name="status" type="s:string" />

    <s:element minOccurs="0" maxOccurs="1" name="message" type="s:string" />

    <s:element minOccurs="0" maxOccurs="1" name="serializedObject" type="s:string" />

    </s:sequence>

    </s:complexType>

    </s:element>

    <s:element name="AuditSimpleResponse">

    <s:complexType>

    <s:sequence>

    <s:element minOccurs="0" maxOccurs="1" name="AuditSimpleResult" type="s:string" />

    </s:sequence>

    </s:complexType>

    </s:element>

    <s:element name="string" nillable="true" type="s:string" />

    </s:schema>

    <s:schema elementFormDefault="qualified" targetNamespace="http://microsoft.com/wsdl/types/">

    <s:simpleType name="guid">

    <s:restriction base="s:string">

    <s:pattern value="[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}" />

    </s:restriction>

    </s:simpleType>

    </s:schema>

    </wsdl:types>

    <wsdl:message name="AuditComplexSoapIn">

    <wsdl:part name="parameters" element="tns:AuditComplex" />

    </wsdl:message>

    <wsdl:message name="AuditComplexSoapOut">

    <wsdl:part name="parameters" element="tns:AuditComplexResponse" />

    </wsdl:message>

    <wsdl:message name="AuditSimpleSoapIn">

    <wsdl:part name="parameters" element="tns:AuditSimple" />

    </wsdl:message>

    <wsdl:message name="AuditSimpleSoapOut">

    <wsdl:part name="parameters" element="tns:AuditSimpleResponse" />

    </wsdl:message>

    <wsdl:message name="AuditSimpleHttpGetIn">

    <wsdl:part name="id" type="s:string" />

    <wsdl:part name="source" type="s:string" />

    <wsdl:part name="stage" type="s:string" />

    <wsdl:part name="status" type="s:string" />

    <wsdl:part name="message" type="s:string" />

    <wsdl:part name="serializedObject" type="s:string" />

    </wsdl:message>

    <wsdl:message name="AuditSimpleHttpGetOut">

    <wsdl:part name="Body" element="tns:string" />

    </wsdl:message>

    <wsdl:message name="AuditSimpleHttpPostIn">

    <wsdl:part name="id" type="s:string" />

    <wsdl:part name="source" type="s:string" />

    <wsdl:part name="stage" type="s:string" />

    <wsdl:part name="status" type="s:string" />

    <wsdl:part name="message" type="s:string" />

    <wsdl:part name="serializedObject" type="s:string" />

    </wsdl:message>

    <wsdl:message name="AuditSimpleHttpPostOut">

    <wsdl:part name="Body" element="tns:string" />

    </wsdl:message>

    <wsdl:portType name="AuditWebServiceSoap">

    <wsdl:operation name="AuditComplex">

    <wsdl:input message="tns:AuditComplexSoapIn" />

    <wsdl:output message="tns:AuditComplexSoapOut" />

    </wsdl:operation>

    <wsdl:operation name="AuditSimple">

    <wsdl:input message="tns:AuditSimpleSoapIn" />

    <wsdl:output message="tns:AuditSimpleSoapOut" />

    </wsdl:operation>

    </wsdl:portType>

    <wsdl:portType name="AuditWebServiceHttpGet">

    <wsdl:operation name="AuditSimple">

    <wsdl:input message="tns:AuditSimpleHttpGetIn" />

    <wsdl:output message="tns:AuditSimpleHttpGetOut" />

    </wsdl:operation>

    </wsdl:portType>

    <wsdl:portType name="AuditWebServiceHttpPost">

    <wsdl:operation name="AuditSimple">

    <wsdl:input message="tns:AuditSimpleHttpPostIn" />

    <wsdl:output message="tns:AuditSimpleHttpPostOut" />

    </wsdl:operation>

    </wsdl:portType>

    <wsdl:binding name="AuditWebServiceSoap" type="tns:AuditWebServiceSoap">

    <soap:binding transport="http://schemas.xmlsoap.org/soap/http" />

    <wsdl:operation name="AuditComplex">

    <soap:operation soapAction="http://romiko.audit.com/AuditComplex" style="document" />

    <wsdl:input>

    <soap:body use="literal" />

    </wsdl:input>

    <wsdl:output>

    <soap:body use="literal" />

    </wsdl:output>

    </wsdl:operation>

    <wsdl:operation name="AuditSimple">

    <soap:operation soapAction="http://romiko.audit.com/AuditSimple" style="document" />

    <wsdl:input>

    <soap:body use="literal" />

    </wsdl:input>

    <wsdl:output>

    <soap:body use="literal" />

    </wsdl:output>

    </wsdl:operation>

    </wsdl:binding>

    <wsdl:binding name="AuditWebServiceSoap12" type="tns:AuditWebServiceSoap">

    <soap12:binding transport="http://schemas.xmlsoap.org/soap/http" />

    <wsdl:operation name="AuditComplex">

    <soap12:operation soapAction="http://romiko.audit.com/AuditComplex" style="document" />

    <wsdl:input>

    <soap12:body use="literal" />

    </wsdl:input>

    <wsdl:output>

    <soap12:body use="literal" />

    </wsdl:output>

    </wsdl:operation>

    <wsdl:operation name="AuditSimple">

    <soap12:operation soapAction="http://romiko.audit.com/AuditSimple" style="document" />

    <wsdl:input>

    <soap12:body use="literal" />

    </wsdl:input>

    <wsdl:output>

    <soap12:body use="literal" />

    </wsdl:output>

    </wsdl:operation>

    </wsdl:binding>

    <wsdl:binding name="AuditWebServiceHttpGet" type="tns:AuditWebServiceHttpGet">

    <http:binding verb="GET" />

    <wsdl:operation name="AuditSimple">

    <http:operation location="/AuditSimple" />

    <wsdl:input>

    <http:urlEncoded />

    </wsdl:input>

    <wsdl:output>

    <mime:mimeXml part="Body" />

    </wsdl:output>

    </wsdl:operation>

    </wsdl:binding>

    <wsdl:binding name="AuditWebServiceHttpPost" type="tns:AuditWebServiceHttpPost">

    <http:binding verb="POST" />

    <wsdl:operation name="AuditSimple">

    <http:operation location="/AuditSimple" />

    <wsdl:input>

    <mime:content type="application/x-www-form-urlencoded" />

    </wsdl:input>

    <wsdl:output>

    <mime:mimeXml part="Body" />

    </wsdl:output>

    </wsdl:operation>

    </wsdl:binding>

    <wsdl:service name="AuditWebService">

    <wsdl:port name="AuditWebServiceSoap" binding="tns:AuditWebServiceSoap">

    <soap:address location="http://localhost:2145/AuditWebService.asmx" />

    </wsdl:port>

    <wsdl:port name="AuditWebServiceSoap12" binding="tns:AuditWebServiceSoap12">

    <soap12:address location="http://localhost:2145/AuditWebService.asmx" />

    </wsdl:port>

    <wsdl:port name="AuditWebServiceHttpGet" binding="tns:AuditWebServiceHttpGet">

    <http:address location="http://localhost:2145/AuditWebService.asmx" />

    </wsdl:port>

    <wsdl:port name="AuditWebServiceHttpPost" binding="tns:AuditWebServiceHttpPost">

    <http:address location="http://localhost:2145/AuditWebService.asmx" />

    </wsdl:port>

    </wsdl:service>

    </wsdl:definitions>

  •  

  • References

    http://msdn.microsoft.com/en-us/library/3003scdt(VS.71).aspx

    http://msdn.microsoft.com/en-us/library/182eeyhh(VS.71).aspx

  • Automating Compilation of Visual Studio 2005 BizTalk projects

    Hi Folks,

    When I prepare to deploy my BizTalk assemblies and related projects, I usually choose MSBuild for the task. However, MSBuild and BizTalk projects are not compatible, and warnings will be issued if you do try to build them.

    In such cases, what I do is use a different compilation tool and just include it in my main MSBuild script. I have written about MSBuild in my other blogs on this site, if you interested to take a look at how it works in more detail.

     

    Below is sample on how I incorporate my BizTalk projects into my build scripts:

    @echo off
    "C:\Program Files\Microsoft Visual Studio 8\Common7\Ide\devenv" MMIT.FileManagement.sln /build "Release" /project MMIT.FileManagement.Orchestrations\MMIT.FileManagement.Orchestrations.btproj /projectconfig "Deployment"
    C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\msbuild MMIT.FileManagement.build
    pause

    As you can see I use the devenv command to build my BizTalk bases solutions. Then I call MSBuild to prepare my deployment scripts, below is the sample build script referenced here as MMIT.FileManagement.build

     

    <Project  xmlns="http://schemas.microsoft.com/developer/msbuild/2003" >

        <!– Set the application name as a property –>
        <PropertyGroup>
            <OutputPath>Build</OutputPath>
            <SourceDir1>MMIT.FileManagement.WebServices</SourceDir1>
            <SourceDir2>MMIT.FileManagement.BOL</SourceDir2>
            <SourceDir3>MMIT.FileManagement.BOL.Rules</SourceDir3>
            <SourceDir4>MMIT.FileManagement.DAL</SourceDir4>
            <SourceDir5>MMIT.FileManagement.Logging</SourceDir5>
            <SourceDir6>MMIT.FileManagement.Orchestrations</SourceDir6>
            <BuildDirWeb>Build\$(SourceDir1)</BuildDirWeb>
            <BuildDirGACWS>Build\BaseAssembliesToGAC\WS_Servers</BuildDirGACWS>
            <BuildDirGACBizTalk>Build\BaseAssembliesToGAC\BizTalk_Servers</BuildDirGACBizTalk>
            <Configuration>Release</Configuration>
        </PropertyGroup>

        <Target Name="MakeBuildDirectory">
            <MSBuild Projects="MMIT.FileManagement.sln" Targets="Rebuild" ></MSBuild>
            <CreateItem Include="$(SourceDir1)\bin\*.dll">
                <Output TaskParameter="Include" ItemName="WebBinFiles"/>
            </CreateItem>
            <CreateItem Include="$(SourceDir2)\bin\Release\*.dll">
                <Output TaskParameter="Include" ItemName="GACBinFilesWS"/>
            </CreateItem>
            <CreateItem Include="$(SourceDir3)\bin\Release\*.dll">
                <Output TaskParameter="Include" ItemName="GACBinFilesWS"/>
            </CreateItem>
            <CreateItem Include="$(SourceDir4)\bin\Release\*.dll">
                <Output TaskParameter="Include" ItemName="GACBinFilesWS"/>
            </CreateItem>
            <CreateItem Include="$(SourceDir5)\bin\Release\*.dll">
                <Output TaskParameter="Include" ItemName="GACBinFilesWS"/>
            </CreateItem>
            <CreateItem Include="$(SourceDir6)\bin\Deployment\MMIT.*.dll">
                <Output TaskParameter="Include" ItemName="GACBinFilesBizTalk"/>
            </CreateItem>
            <CreateItem Include="$(SourceDir6)\bin\Deployment\MMIT.Common.dll">
                <Output TaskParameter="Include" ItemName="GACBinFilesCommon"/>
            </CreateItem>
            <CreateItem Include="$(SourceDir1)\*.asmx;$(SourceDir1)\*.config;$(SourceDir1)\*.asax">
                <Output TaskParameter="Include" ItemName="WebServiceFiles"/>
            </CreateItem>
            <Copy SourceFiles="@(WebServiceFiles)" DestinationFolder="$(BuildDirWeb)"/>
            <Copy SourceFiles="@(WebBinFiles)" DestinationFolder="$(BuildDirWeb)\bin"/>
            <Copy SourceFiles="@(GACBinFilesWS)" DestinationFolder="$(BuildDirGACWS)"/>
            <Copy SourceFiles="@(GACBinFilesBizTalk)" DestinationFolder="$(BuildDirGACBizTalk)"/>
            <Copy SourceFiles="@(GACBinFilesCommon)" DestinationFolder="$(BuildDirGACWS)"/>
        </Target>
    </Project>

    The script above will prepare deployment folders based on server types for me, due to distributed components, and my output is as follows:

    Below MSBULD, groups my assemblies into those for the GAC, and also those for web services only.

    image

    image

    Also, those that need to be put in the GAC, are again grouped by server roles, in the event your production environment has dedicated hardware for web services, BizTalk servers, and web sites.

    image

    Configuring IIS using Directory Services and C#

    Introduction

    Hi Folks,

    It has been a while since I last posted, been very busy at work and I decided to take a holiday in Portugal, I spent most of my time surfing in Peniche, lovely place!

    We just finished a major development phase at work and the next step was deployments, one aspect of the deployments is Application Pools and Web Service virtual directories, we have allot of web services and would it be nice to automate the creation of these items in the IIS system.

    I decided the best way to go is to use WMI, C# and the System.DirectoryServices library.

    This article will discuss how to browse the WMI repository and write code in C# to create and configure application pools as well as virtual directories, this article will cover some advanced features of accessing Directory Service object properties which in turn customize the properties of IIS objects such as the recycle schedule in the Application Pool settings and Identity account information.

    WMI Studio

    The first thing you need to do before we get going is download the WMI Studio tools, they are a great help in understanding WMI objects when developing, you can download it from here:

    http://www.microsoft.com/downloads/details.aspx?FamilyID=6430f853-1120-48db-8cc5-f2abdc3ed314&displaylang=en

    Install the tools and you will find the following link in your program menu:

    When you click on the WMI CIM Studio shortcut, you will be presented with the following browser:

    I advise you to click the warning message and click "Allow Blocked Content"

    You will then be presented with the following screen:

    Change the connection to:

    root\MicrosoftIISv2

    And then click ok twice. You will be presented with the following page:

    We are now in the IIS repository and can start browsing around it. Let’s say we would like to understand how the CIM stores information regarding the Application Pools in IIS. Notice a section call CIM_Setting:

    This is basically the place to look for a Class that defines the settings for the application pool, in fact the class name is called:

    IIsApplicationPoolsSetting

    If you browse to it, it is near the bottom, you will notice all the settings, for example, the PeriodicRestartSchedule is this setting in IIS admin console:

    Ok, so now how about looking at some of the settings in the current IIS system, we can do this, remember what you browsing is a Class Definition and you can create an instance of one from this tool by clicking this button on the top right and then fill in the properties and save it, but we going to do it programmatically:

    But before we get down to the dirty details and exciting bits, I want you to understand some basic WQL queries, what we going to do is look at the settings of the Default Application Pool in IIS, so we going to look at an INSTANCE of a object that is already configured in IIS, why? Because this can help you reverse engineer what properties mean in the GUI and correlate them back to the property names in the WMI repository, just like I shoed you with the property PeriodicRestartSchedule property of the application pool properties collection.

    So click this icon:

    It will open up a dialog box, and we will enter the following query in it:

    select * from IIsapplicationPoolSetting

    save the query by giving it a name as well!

    Then click execute.

    It will now show you what the IIS admin tool shows, the application pools:

    Double click the DefaultAppPool, and you can then see the properties. Now if you change values in IIS admin console and then reconnect to this CMI database you can figure out what properties changed!

    Ok, so I hope my short introduction here will suffice to get you going, let’s get to the exciting bit.

    Configuration

    Of course with any tool, you need configuration to be stored somewhere, suppose we have this XML configuration file:

    <?xml version="1.0" encoding="utf-8" ?>

    <Settings>

        <IIS_Settings>

            <ApplicationsPools>

                <ApplicationPool>

                    <Server>local</Server>

                    <Name>DataCleansingAppPool</Name>

                    <UserName>DEVPlace\Gateway</UserName>

                    <Password>sdf</Password>

                    <Identity>Configurable</Identity>

                    <RecycleWorkerProcessInMinutes>0</RecycleWorkerProcessInMinutes>

                    <RecycleWorkerProcessSchedule>12:00</RecycleWorkerProcessSchedule>

                </ApplicationPool>

                <ApplicationPool>

                    <Server>local</Server>

                    <Name>Gateway Web Service</Name>

                    <UserName>DEVPlace\Gateway</UserName>

                    <Password>sdf</Password>

                    <Identity>Configurable</Identity>

                    <PeriodicRestartTime>0</PeriodicRestartTime>

                    <PeriodicRestartSchedule>12:00</PeriodicRestartSchedule>

                </ApplicationPool>

            </ApplicationsPools>

            <WebSites>

                <WebSite>

                    <Server>local</Server>

                    <VirtualDirectoryName>MMIT.DataCleansingWS</VirtualDirectoryName>

                    <LocalPath>C:\Code\MMIT.DataCleansing\MMIT.DataCleansingWS</LocalPath>

                    <ApplicationPool>DataCleansingAppPool</ApplicationPool>

                </WebSite>

     

                <WebSite>

                    <Server>local</Server>

                    <VirtualDirectoryName>MMIT.DataManipulation.WebServices</VirtualDirectoryName>

                    <LocalPath>C:\Code\MMIT.DataManipulation\MMIT.DataManipulation.WebServices</LocalPath>

                    <ApplicationPool>DefaultAppPool</ApplicationPool>

                </WebSite>

     

                <WebSite>

                    <Server>local</Server>

                    <VirtualDirectoryName>MMIT.DealerAllocation.WebServices</VirtualDirectoryName>

                    <LocalPath>C:\Code\MMIT.DealerAllocation\MMIT.DealerAllocation.WebServices</LocalPath>

                    <ApplicationPool>DefaultAppPool</ApplicationPool>

                </WebSite>

     

                <WebSite>

                    <Server>local</Server>

                    <VirtualDirectoryName>MMIT.DeliveryNotification.WebServices</VirtualDirectoryName>

                    <LocalPath>C:\Code\MMIT.DeliveryNotification\MMIT.DeliveryNotification.WebServices</LocalPath>

                    <ApplicationPool>DefaultAppPool</ApplicationPool>

                </WebSite>

     

     

                <WebSite>

                    <Server>local</Server>

                    <VirtualDirectoryName>MMIT.Gateway.WebServices</VirtualDirectoryName>

                    <LocalPath>C:\Code\MMIT.Gateway\MMIT.Gateway.WebServices</LocalPath>

                    <ApplicationPool>Gateway Web Service</ApplicationPool>

                </WebSite>

     

                <WebSite>

                    <Server>local</Server>

                    <VirtualDirectoryName>MMIT.Matching.WebServices</VirtualDirectoryName>

                    <LocalPath>C:\Code\MMIT.Matching\MMIT.Matching.WebServices</LocalPath>

                    <ApplicationPool>DefaultAppPool</ApplicationPool>

                </WebSite>

     

                <WebSite>

                    <Server>local</Server>

                    <VirtualDirectoryName>MMIT.Workflow.Common.WebServices</VirtualDirectoryName>

                    <LocalPath>C:\Code\MMIT.Workflow\MMIT.Workflow.Common.WebServices</LocalPath>

                    <ApplicationPool>DefaultAppPool</ApplicationPool>

                </WebSite>

     

                

            </WebSites>

        </IIS_Settings>

     

    </Settings>

     

    Implementing the code

    To configure the values above, I will show you how to do it for the application pool and you can figure out how to do it for the virtual directories. A good article that can help is here:

    http://msdn.microsoft.com/en-us/library/ms524896.aspx

     

    I have the following C# code in my windows form you can of course make it a console application

    using System;

    using System.Collections.Generic;

    using System.ComponentModel;

    using System.Data;

    using System.Drawing;

    using System.IO;

    using System.Text;

    using System.Windows.Forms;

    using System.Xml;

     

    namespace MMIT.Workflow.InstallationConfiguration

    {

    public partial class Form1 : Form

    {

     

    private readonly static XmlDocument doc = new XmlDocument();

    private static FileStream fs;

    private readonly static ASCIIEncoding encoding = new ASCIIEncoding();

     

    private enum Identity {Predifined=2, Configurable=3} //PreDefined defaults to NetworkService in IIS settings

     

     

    public Form1()

    {

    InitializeComponent();

    doc.Load(@"..\..\MMIT.Workflow.InstallationConfiguration.xml");

    }

     

    private void btnIISSetup_Click(object sender, EventArgs e)

    {

    txtIISOutput.Text += CreateApplicationPools();

    CreateVirtualDirectories();

    }

     

     

     

    private string CreateApplicationPools()

    {

    string output="";

    foreach (XmlElement element in doc.SelectSingleNode("Settings/IIS_Settings/ApplicationsPools").ChildNodes)

    {

    string Server = element.SelectSingleNode("Server").InnerText;

    string AppName = element.SelectSingleNode("Name").InnerText;

    string UserName = element.SelectSingleNode("UserName").InnerText;

    string Password = element.SelectSingleNode("Password").InnerText;

    int IdentitySetting = element.SelectSingleNode("Identity").InnerText == "Configurable" ? (int)Identity.Configurable : (int)Identity.Predifined;

    int PeriodicRestartTime = int.Parse(element.SelectSingleNode("RecycleWorkerProcessInMinutes").InnerText);

    string PeriodicRestartSchedule = element.SelectSingleNode("RecycleWorkerProcessSchedule").InnerText;

    output += IISAdministration.CreateaApplicationPool(AppName, UserName, Password, IdentitySetting, PeriodicRestartTime, PeriodicRestartSchedule) + Environment.NewLine;

    }

    return output;

    }

     

     

    private void CreateVirtualDirectories()

    {

     

    }

    }

    }

     

    Here is the class that implements the logic:

    using System;

    using System.DirectoryServices;

     

     

    namespace MMIT.Workflow.InstallationConfiguration

    {

    class IISAdministration

    {

    private const string WMINameSpace = @"root\MicrosoftIISv2";

    private const string metabasePath = "IIS://Localhost/W3SVC/AppPools";

     

     

     

     

    public static string CreateaApplicationPool(string appPoolName, string userName, string password, int identity, int periodicRestartTime, string PeriodicRestartSchedule)

    {

    string output = "\nCreating application pool named:" + metabasePath + "/" + appPoolName + Environment.NewLine;

     

    try

    {

    if (metabasePath.EndsWith("/W3SVC/AppPools"))

    {

    DirectoryEntry newpool;

    DirectoryEntry apppools = new DirectoryEntry(metabasePath);

    newpool = apppools.Children.Add(appPoolName, "IIsApplicationPool");

    newpool.Properties["WAMUserName"][0] = userName;

    newpool.Properties["WAMUserPass"][0] = password;

    newpool.Properties["AppPoolIdentityType"][0] = identity;

     

    newpool.Properties["PeriodicRestartTime"][0] = periodicRestartTime;

    newpool.Properties["PeriodicRestartSchedule"].AddRange(new string[] { PeriodicRestartSchedule });

     

     

     

    newpool.CommitChanges();

    output += "Done." + Environment.NewLine;

    }

    else

    output = " Failed in CreateAppPool; application pools can only be created in the */W3SVC/AppPools node." + Environment.NewLine;

    }

    catch (Exception ex)

    {

    output = "Failed in CreateAppPool with the following exception:" + ex.Message + Environment.NewLine;

    }

    return output;

    }

    }

    }

     

    Certain properties are access like an array, however some properties are a bit more complex, like the schedule settings for recycling worker processes, so what you do here is check in the CIM what the data type is and then it correctly:

    newpool.Properties["PeriodicRestartSchedule"].AddRange(new string[] { PeriodicRestartSchedule });

    Other settings are easier:

    newpool.Properties["WAMUserName"][0] = userName;

    The CMI property page can give you clues:

     

    Hence why I instantiated the PeriodicRestartSchedule, since it is an unitilized array, since it needs an array of configured objects to begin with, if you tried this:

    newpool.Properties["PeriodicRestartSchedule"][0]

    You would have gotten an index out of range exception.

    Summary

    Well, I hope this article has discusses some WMI programming for you, at a later time I may update this blog and provide a fully functional working admin tool kit that you can extend for IIS, BizTalk etc.

    SQL Server: Comparing data during a test cycle with the T-SQL EXCEPT command

    Hi Folks,

    Sometimes I am developing and need to compare output of data after making changes to a system that updates or inserts data into a table. What I usually do is have a production test batch of records in a table, then have my development batch, when I make new releases I compare the Production Batch with the Development Batch, to ensure the "New Features" I implemented do not affect the production rules.

    Ok, so let’s say we have this table:

     

    Now I populate it with production data:

    insert into dbo.FunctionTests(Name,Surname,Environment)

    values (‘Romiko’, ‘van de dronker’,‘PROD’)

    insert into dbo.FunctionTests(Name,Surname,Environment)

    values (‘Bob’, ‘The Builder’,‘PROD’)

     

    Let’s imagine I wrote some code to make these results from a web form or so, and I use the same form in my development environment, but I introduced a bug, where Bob The Builder’s name was spelt incorrectly, lets add a ‘S’ to it in the Dev environment:

    insert into dbo.FunctionTests(Name,Surname,Environment)

    values (‘Romiko’, ‘van de dronker’,‘DEV’)

    insert into dbo.FunctionTests(Name,Surname,Environment)

    values (‘Bob’, ‘The BuilderS’,‘DEV’)

    Notice the S.

     

    So what we want is a query to show the difference between the batches:

    select Name,Surname

    from dbo.FunctionTests

    where Environment = ‘PROD’

    EXCEPT

    select Name,Surname

    from dbo.FunctionTests

    where Environment = ‘DEV’

     

    select Name,Surname

    from dbo.FunctionTests

    where Environment = ‘DEV’

    EXCEPT

    select Name,Surname

    from dbo.FunctionTests

    where Environment = ‘PROD’

     

    And here are the results, it only shows the changes.

     

    Hope you find this useful for your functional tests and need to compare results!

    Cheers.

    BizTalk 2006: Default SQL Adapter – Custom SQL Adapter (PART 3)

    Hi Folks!

    This article pertains to BizTalk 2006 and not BizTalk 2006 R2, since R2 has WCF which is a better alternative to the default BizTalk Adapters. I have shown this example for those of us that do not have R2.

    The SQL Send Adapter that ships with BizTalk have some limitations, what I wanted was a SQL adapter that can send a message to a store procedure, thus storing XML data directory to the SQL Server 2005 table. We all know the SQL Receive Adapter can call a stored procedure, but surely a send adapter should have the same features without using Updategram etc, see Part 1 and Part 2 of this blog series.

    So imagine a SQL Send Port where you can give:

    1. SQL Server Connection String
    2. Stored Procedure name
    3. BizTalk message
    4. Optional Stored Procedure parameters and even promoted properties

    So the UI for the default adapter is like this:

    As you can see this is no good for meeting our requirements, here is a quick sneak preview of the Custom SQL Adapter Properties Page:

    So basically, what I did was get the sample Transaction Adapter from the BizTalk SDK folder and did some enhancements to it. Excuse my link, since my machine is 64 Bit, so your location of program files will be without the (x86).

    C:\Program Files (x86)\Microsoft BizTalk Server 2006\SDK\Samples\AdaptersDevelopment\TransactionalAdapter

    This Adapter solution provided with the SDK has all the tools we need to get started. I have provided the source code for the modified version of this SDK adapter for you to download.

    What is important to realize when creating a Custom SQL Send Adapter is this:

    1. GUID for the Receive Handler
    2. GUID for the Send Handler
    3. GUID for the AppID
    4. Guide for the CLSID
    5. SQLTransmitLocation.xsd – Modify the Component to show a dialog window when setting the properties in the Custom Adapter
    6. SQLAsyncBatch.cs file contains the calling code to the procedure and passes the Message as a stream and other parameters you specify in the dialog window.
    7. Registry file to register the components.

     

    Note: The GUID’S I have put in the sample code will work out the box, however, if you want to customize yours further and reuse the code and have multiple custom adapters, then ensure you change the GUID’s.

    To install this Adapter you will need to download the source code then:

    1. Open the TransactionalAdmin.reg file and modify the GUID’s (OPTIONAL)
    2. There are two GUID’s that you modify in the TransactionalAdmin.reg file that will need to be updated in the Source Code, in the Registration folder (OPTIONAL)
      1. "OutboundEngineCLSID"="{D2697EC5-414F-40DD-90F5-904EB5A54674}" – Also update your GUID in the code file: SQLTransmitter.cs
      2. "InboundEngineCLSID"="{c319016b-25bd-4162-935a-694081e146c7}" – Also update your GUID in the code file: SQLReceiver.cs
    3. There is two GUID’s in the TransactionalAdmin.reg file that needs to be updated to be unique: (OPTIONAL)

    [HKEY_CLASSES_ROOT\CLSID\{5794bde9-e834-4e11-b33c-db7b8eff8bf4}]

    @="SQL Adapter Class" "AppID"="{6e33a268-b437-4c66-9d5c-4a7a5169877a}"

     

    [HKEY_CLASSES_ROOT\CLSID\{5794bde9-e834-4e11-b33c-db7b8eff8bf4}\Implemented Categories]

    [HKEY_CLASSES_ROOT\CLSID\{5794bde9-e834-4e11-b33c-db7b8eff8bf4}\Implemented Categories\{7F46FC3E-3C2C-405B-A47F-8D17942BA8F9}]

     

    Do not update the GUID in Red.

    Tip: Use this site to make GUID’s:

    http://www.guidgenerator.com/online-guid-generator.aspx

    Cool, now that we got the nasty GUID’s out of the way, let’s get going. Build the application to generate the dll’s. You will have the following dll’s:

    1. SQLAdapter\Admin\bin\Debug\MMIT.BizTalk.Adapter.SQLAdmin.dll (This is used at Design Time)
    2. SQLAdapter\Runtime\bin\Debug\MMIT.BizTalk.Adapter.SQL.dll
    3. BaseAdapter\bin\Debug\MMIT.BizTalk.Adapter.Common.dll

    Drag the above DLL’S into the GAC. I think the only one needed in the GAC is MMIT.BizTalk.Adapter.Common.dll, but I put all three.

    Ensure the path in the registry file points to the location of these dll’s

    "InboundAssemblyPath"="C:\\ProtoTypes\\CustomSQLAdapter\\SQLAdapter\\Runtime\\bin\\Debug\\MMIT.BizTalk.Adapter.SQL.dll"

    "OutboundAssemblyPath"="C:\\ProtoTypes\\CustomSQLAdapter\\SQLAdapter\\Runtime\\bin\\Debug\\MMIT.BizTalk.Adapter.SQL.dll"

    "AdapterMgmtAssemblyPath"="C:\\ProtoTypes\\CustomSQLAdapter\\SQLAdapter\\Admin\\bin\\Debug\\MMIT.BizTalk.Adapter.SQLAdmin.dll"

     

    Of course you can create release versions of these dll’s if you like as well.

    One this is done, you then need to:

    1. Locate the TransactionalAdmin.reg file and double click it to register the adapter in the registry
    2. Open the BizTalk Server Admin Console and add the adapter:

    Once done you will get a send and receive handler for it:

    Now, you are ready to use the Adapter in a send port.

    Notice this custom SEND SQL Adapter has parameters and a store procedure!

    Fill in the procedure name.

    Click on parameters and enter them in:

     

    The above dialog box is not so smart, I have not had time to perfect it, but you can see that if the PropertyType is a message, you do not need to fill in the others and the same is true for other types respectively. See the code below to see how it is read so you know what values to fill for what types:

    The code comes from:

    SQLAsyncBatch.cs

    private void SendMessage(IBaseMessage message, SQLTransmitProperties properties)

    {

    TextReader reader = new StreamReader(message.BodyPart.Data, Encoding.UTF8);

    string storedProcedureName = properties.CmdText;

    string connectionString = properties.ConnectionString;

     

    using (SqlConnection connection = new SqlConnection(connectionString))

    {

    connection.Open();

     

    SqlCommand command = new SqlCommand(storedProcedureName, connection);

    command.CommandType = CommandType.StoredProcedure;

    //TODO: Clean up this mess.

    foreach(string parameter in properties.Parameters.Split(Environment.NewLine.ToCharArray()))

    {

    if (parameter != "")

    {

    string[] paramFields = parameter.Split(‘;’);

    string name = paramFields[0];

    string type = paramFields[1];

    string value = paramFields[2];

    string _namespace = paramFields[3];

    switch (type)

    {

    case "Message":

    command.Parameters.Add(new SqlParameter("@" + name, reader.ReadToEnd()));

    break;

    case "Constant":

    command.Parameters.Add(new SqlParameter("@" + name, value));

    break;

    case "Promoted":

    command.Parameters.Add(

    new SqlParameter("@" + name, message.Context.Read(value, _namespace)));

    break;

    }

    }

    }

    command.ExecuteScalar();

    }

    }

     

    So the beauty of this is now a BizTalk message can be sent directly to SQL table with XML Data Type:

    USE [Test]

    GO

    /****** Object: Table [dbo].[cs] Script Date: 04/28/2008 15:49:42 ******/

    SET ANSI_NULLS ON

    GO

    SET QUOTED_IDENTIFIER ON

    GO

    CREATE TABLE [dbo].[cs](

        [cs_id] [bigint] IDENTITY(1,1) NOT NULL,

        [xml_data] [xml] NULL,

        [source_name] [nvarchar](50) NULL,

        [date_inserted] [smalldatetime] NULL DEFAULT (getdate()),

        [stage] [nvarchar](20) NULL,

        [status] [nvarchar](20) NULL

    ) ON [PRIMARY]

     

    Here is a test stored procedure to use:

    USE [Test]

    GO

    /****** Object: StoredProcedure [dbo].[set_records] Script Date: 04/28/2008 15:50:06 ******/

    SET ANSI_NULLS ON

    GO

    SET QUOTED_IDENTIFIER ON

    GO

    CREATE procedure [dbo].[set_records] @Data XML–, @status NVARCHAR(20)

    AS

    Declare @status nvarchar(20)

    set @status = ‘Processed’

        IF @Data IS NULL OR @status IS NULL

            RETURN

     

        –move to next record if status = success

        IF @status = ‘Processed’

        BEGIN

            UPDATE dbo.cs

            SET xml_data = @Data,

                stage = dbo.myroute(cs.source_name, cs.stage, cs.status),

                status = case dbo.myroute(cs.source_name, cs.stage, cs.status) WHEN cs.stage THEN @status ELSE N‘Unprocessed’ END

            FROM dbo.cs cs

                JOIN @Data.nodes(‘declare default element namespace "http://SQLAdapterGetRecords.Record&quot;;Record/Row/Metadata’) as tab(col)

            ON tab.col.value(‘text()[1]’, ‘BIGINT’) = cs.cs_id

        END

        ELSE

        BEGIN

            UPDATE dbo.cs

            SET status = @status

            FROM dbo.cs cs

                JOIN @Data.nodes(‘declare default element namespace "http://SQLAdapterGetRecords.Record&quot;;Record/Row/Metadata’) as tab(col)

            ON tab.col.value(‘text()[1]’, ‘BIGINT’) = cs.cs_id

        END

     

     

     

    To use the above store procedure the only parameters to pass is the message, since the code does this:

    case "Message":

    command.Parameters.Add(new SqlParameter("@" + name, reader.ReadToEnd()));

    So the result looks like this:

     

    Now, you can spend time customising the form and make it more intelligent of course, and grey out the areas that are not needed when selecting a Type.

    The Form is located in the ComponentModel folder.

     

    The SQLTransmitLocation.xsd is used to hook up the Design Time event to the custom form:

    <xs:element minOccurs="1" default="" name="parameters" type="xs:string">

    <xs:annotation>

    <xs:appinfo>

    <baf:designer>

      <baf:displayname _locID="">Parameters</baf:displayname>

      <baf:description _locID="">The stored procedure parameters.</baf:description>

      <baf:editor>MMIT.BizTalk.Adapter.SQLAdmin.ComponentModel.ParameterUITypeEditor, MMIT.BizTalk.Adapter.SQLAdmin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=37c5f231b3e0efe3</baf:editor>

      </baf:designer>

      </xs:appinfo>

      </xs:annotation>

      </xs:element>

      </xs:sequence>

      </xs:complexType>

      </xs:element>

      </xs:schema>

     

    This all looks a bit confusing, but the best approach is to download the code and get it working and play with it!

    When you download the code, go to the project properties for each project and point the code to a strong assembly key, use this command to create one in the command prompt window (Visual Studio Command Prompt):

    Sn –k mykey.snk

     

    The program has many improvements that can be made, but I feel there is more than enough information in here to get you going! I look forward to your feedback or ideas.

     

    Hopefully many of you can get to BizTalk R2, and use WCF instead.

    Special thanks to my colleague Chris Hagens for creating the Form properties (UITypeEditor)!

    Good Luck.

    Download Source Code

    NOTE: USE WCF-CUSTOM SQL now, these blogs are from a couple of years ago 🙂 I lost my old blog server 😦

    BizTalk 2006: Default SQL Adapter – XML Stored Procedures (PART 2)

    Hello Folks,

    Ok, another way we can actually send data to SQL server using BizTalk’s default SQL Adapter is actually by calling a stored procedure by using an XML Template, weird as it sounds, it is the only method supported by the default SQL Adapter, unless you want to resort to Updategrams, which we covered in Part 1. I assume you all been working and playing with BizTalk for while to know how to setup routing and subscriptions etc.

    Ok, first thing we need is a Customer table and a primitive stored procedure that will insert data into the Customer Table.

    USE [Test]

    GO

    /****** Object: Table [dbo].[Customer] Script Date: 04/05/2008 16:02:06 ******/

    SET ANSI_NULLS ON

    GO

    SET QUOTED_IDENTIFIER ON

    GO

    CREATE TABLE [dbo].[Customer](

        [CustomerID] [int] IDENTITY(1,1) NOT NULL,

        [CustomerName] [nvarchar](30) NOT NULL,

        [Address] [nvarchar](60) NOT NULL,

        [City] [nvarchar](30) NOT NULL,

        [Region] [nvarchar](30) NOT NULL,

        [PostalCode] [nvarchar](10) NOT NULL,

        [RecordStatus] [tinyint] NOT NULL

    ) ON [PRIMARY]

     

    Primitive Stored Procedure that we will call from BizTalk

    USE [Test]

    GO

    SET ANSI_NULLS ON

    GO

    SET QUOTED_IDENTIFIER ON

    GO

    CREATE procedure [dbo].[BizTalkToCustomerInsert]

                        @CustomerName nvarchar(60)

                        , @Address nvarchar(120)

                        , @City nvarchar(60)

                        , @Region nvarchar(60)

                        , @PostalCode nvarchar(60)

    AS

     

    BEGIN

        Declare @ReturnCode int

        select    @ReturnCode = 0

     

        Insert Customer (CustomerName, Address, City, Region, PostalCode)

        Values (@CustomerName, @Address, @City, @Region, @PostalCode)

     

     

        select    @ReturnCode = @@error

                

     

        return @ReturnCode

    END

     

    Now when you create a BizTalk project you will need to generate an XML template that will be used to call the stored procedure, I will show you exactly how this is accomplished, for now, here is a sneak peak at the XML template used to call the stored procedure. I decided to put it here, so you get an idea of what we are actually creating and why.

    XML Stored Procedure

      <?xml version="1.0" encoding="utf-16" ?>

    <xs:schema xmlns:b="http://schemas.microsoft.com/BizTalk/2003" attributeFormDefault="unqualified" elementFormDefault="qualified" targetNamespace="http://SQLAdapterStoredProcedure.schCustomer" version="1.0" xmlns:xs="http://www.w3.org/2001/XMLSchema">

    <xs:annotation>

    <xs:appinfo>

      <msbtssql:sqlScript value="exec [BizTalkToCustomerInsert] @Address=" ", @City=" ", @CustomerName=" ", @PostalCode=" ", @Region=" "" xmlns:msbtssql="http://schemas.microsoft.com/BizTalk/2003" />

      </xs:appinfo>

      </xs:annotation>

    <xs:element name="Customer_Request">

    <xs:complexType>

    <xs:sequence>

    <xs:element name="BizTalkToCustomerInsert">

    <xs:complexType>

      <xs:attribute name="Address" type="xs:string" />

      <xs:attribute name="City" type="xs:string" />

      <xs:attribute name="CustomerName" type="xs:string" />

      <xs:attribute name="PostalCode" type="xs:string" />

      <xs:attribute name="Region" type="xs:string" />

      </xs:complexType>

      </xs:element>

      </xs:sequence>

      </xs:complexType>

      </xs:element>

    <xs:element name="Customer_Response">

    <xs:complexType>

    <xs:sequence>

      <xs:element name="Success" type="xs:anyType" />

      </xs:sequence>

      </xs:complexType>

      </xs:element>

      </xs:schema>

     

    So, as you can see, to configure a SQL Adapter to call this procedure, we have created a template. You wondering how you would parse parameters into the store procedure xml document, the way this is accomplished is by using a simple map.

     

    The SQL Adapter configuration will look like this.

    Notice the Document Target Namespace is the same as the XML Template: targetNamespace="http://SQLAdapterStoredProcedure.schCustomer

    So how actually did the XML schema get defined. Well what happens is that you right click your BizTalk project in Visual Studio and Click Add Generated Items.

    The you click Add Adapter Metadata

    Click SQL and then click Next

    Configure the connection string to the database where the customer table resides.

    This part is rather important, be careful to name your target namespace and root element, so you can correctly identify the documents for effective message routing, I keep it simple here, but at work we use something like:

    CompanyName.Application.DocumentFunction

    e.g. ACME.CRM.InsertCustomerRecords

    So as you can see this wizard is basically creating the XSD document I showed earlier on for you.

    Click Next and click Stored Procedure.

    Now choose the procedure, notice you can provide default values!

    Click generate

    Click next and complete the wizard.

    That’s basically how you create a XML call for a stored procedure. To wrap it up, you can have an orchestration subscribe to some sort of Customer XML Record and then send it to the SQL Adapter, but first using a map to transform it to the Schema we just made.

    So maybe you had a customer schema, we you have a receive port where new customer records are dropped.

      <?xml version="1.0" encoding="utf-16" ?>

    <xs:schema xmlns:b="http://schemas.microsoft.com/BizTalk/2003" xmlns="http://SQLAdapterStoredProcedure.schCustomer" targetNamespace="http://SQLAdapterStoredProcedure.schCustomer" xmlns:xs="http://www.w3.org/2001/XMLSchema">

    <xs:element name="Customer">

    <xs:complexType>

    <xs:sequence>

      <xs:element name="CustomerName" type="xs:string" />

      <xs:element name="Address" type="xs:string" />

      <xs:element name="City" type="xs:string" />

      <xs:element name="Region" type="xs:string" />

      <xs:element name="PostalCode" type="xs:string" />

      </xs:sequence>

      </xs:complexType>

      </xs:element>

      </xs:schema>

     

    And the map I showed a few moments ago can transform this information to call the stored procedure.

    Here is what the Orchestration looks like.

     

    Summary

    Personally, I would never use this method in a production environment. For starters I dislike the whole idea of create xml templates and maps just to call a stored procedure, and even more, I dislike using orchestrations very much, granted we could get rid of the orchestration and use a send port to subscribe to customer documents and do the mapping and send it to the SQL adapter, but what a fuss.

    Also, imagine, you developed a Customer table like this:

    USE [Test]

    GO

    /****** Object: Table [dbo].[cs] Script Date: 04/05/2008 16:29:34 ******/

    SET ANSI_NULLS ON

    GO

    SET QUOTED_IDENTIFIER ON

    GO

    CREATE TABLE [dbo].[cs](

        [cs_id] [bigint] IDENTITY(1,1) NOT NULL,

        [xml_data] [xml] NULL,

        [source_name] [nvarchar](50) NULL,

        [date_inserted] [smalldatetime] NULL DEFAULT (getdate()),

        [stage] [nvarchar](20) NULL,

        [status] [nvarchar](20) NULL

    ) ON [PRIMARY]

     

    Do you notice something here, my data is stored as XML in the SQL Table, So think for a moment, the map I showed you previously would not have a 1 to 1 mapping per record element in the XML. What you would want is take the Customer XML on the left hand side and shove the whole document into an attribute on the right, since the store procedure used to call requires XML Data like this:

    USE [Test]

    GO

    /****** Object: StoredProcedure [dbo].[set_records] Script Date: 04/05/2008 16:31:20 ******/

    SET ANSI_NULLS ON

    GO

    SET QUOTED_IDENTIFIER ON

    GO

    CREATE procedure [dbo].[set_records] @Data XML–, @status NVARCHAR(20)

    AS

    Declare @status nvarchar(20)

    set @status = ‘Processed’

        IF @Data IS NULL OR @status IS NULL

            RETURN

     

        –move to next record if status = success

        IF @status = ‘Processed’

        BEGIN

            UPDATE dbo.cs

            SET xml_data = @Data,

                stage = dbo.myroute(cs.source_name, cs.stage, cs.status),

                status = case dbo.myroute(cs.source_name, cs.stage, cs.status) WHEN cs.stage THEN @status ELSE N‘Unprocessed’ END

            FROM dbo.cs cs

                JOIN @Data.nodes(‘declare default element namespace "http://SQLAdapterGetRecords.Record&quot;;Record/Row/Metadata’) as tab(col)

            ON tab.col.value(‘text()[1]’, ‘BIGINT’) = cs.cs_id

        END

        ELSE

        BEGIN

            UPDATE dbo.cs

            SET status = @status

            FROM dbo.cs cs

                JOIN @Data.nodes(‘declare default element namespace "http://SQLAdapterGetRecords.Record&quot;;Record/Row/Metadata’) as tab(col)

            ON tab.col.value(‘text()[1]’, ‘BIGINT’) = cs.cs_id

        END

     

    I more realistic stored procedure, you see, we want the stored procedure to actually take an entire XML Document, and the only way to do this with a BizTalk Map is using XSLT. Like this:

    Notice in the properties window, that I have a path to an XSL File, Procedure_xslt.xsl.

    So in the example above I put all the XML data on the left into an attribute on the right called xml. This is what the output of the transform will look like:

    Input Document (Left): (You could have used the customer XML schema, I decided to choose another one, to show you how useful the new procedure is, it could be used to store xml data for customer or any other xml data.

    < ns0:Record xmlns:ns0="http://SQLAdapterGetRecords.Record">

    < ns0:Row>

      < ns0:test>test_0</ns0:test>

      < ns0:another>another_0</ns0:another>

      < ns0:Metadata>Metadata_0</ns0:Metadata>

      </ns0:Row>

      </ns0:Record>

     

    Output Document (Right):

    <InsertRecord xmlns="http://SQLAdapterSetRecords.Record"&gt;

    <set_records status="Processed" xml="<ns0:Record xmlns:ns0="http://SQLAdapterGetRecords.Record"&gt;

    <ns0:Row><ns0:test>test_0</ns0:test><ns0:another>another_0</ns0:another>

    <ns0:Metadata>Metadata_0</ns0:Metadata></ns0:Row></ns0:Record>" />

    </InsertRecord>

     

    Notice the document on the left is now the attribute value in the XML, see above.

    Nice is it not, all the XML data on left is now an attribute on the right, by using an XSLT transform. Here is the template:

    <xsl:stylesheet version="1.0"

    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

     

    <xsl:output omit-xml-declaration="yes"/>

     

    <xsl:param name="use-empty-syntax" select="true()"/>

    <xsl:param name="exclude-unused-prefixes" select="true()"/>

     

    <xsl:param name="start-tag-start" select="&lt;"/>

    <xsl:param name="start-tag-end" select="‘>’"/>

    <xsl:param name="empty-tag-end" select="‘/>’"/>

    <xsl:param name="end-tag-start" select="&lt;/’"/>

    <xsl:param name="end-tag-end" select="‘>’"/>

    <xsl:param name="space" select="‘ ‘"/>

    <xsl:param name="ns-decl" select="‘xmlns’"/>

    <xsl:param name="colon" select="‘:’"/>

    <xsl:param name="equals" select="‘=’"/>

    <xsl:param name="attribute-delimiter" select="&quot;"/>

    <xsl:param name="comment-start" select="&lt;!–‘"/>

    <xsl:param name="comment-end" select="‘–>’"/>

    <xsl:param name="pi-start" select="&lt;?’"/>

    <xsl:param name="pi-end" select="‘?>’"/>

     

     

    <xsl:template name="xml-to-string">

    <xsl:param name="node-set" select="."/>

    <xsl:apply-templates select="$node-set" mode="xml-to-string">

    <xsl:with-param name="depth" select="1"/>

    </xsl:apply-templates>

    </xsl:template>

     

    <xsl:template match="/" name="xml-to-string-root-rule">

         <InsertRecord xmlns="http://SQLAdapterSetRecords.Record">

             <set_records status="Processed">

                 <xsl:attribute name="xml">

                     <xsl:call-template name="xml-to-string"/>

                 </xsl:attribute>

             </set_records>

         </InsertRecord>

     

    </xsl:template>

     

    <xsl:template match="/" mode="xml-to-string">

    <xsl:param name="depth"/>

    <xsl:apply-templates mode="xml-to-string">

    <xsl:with-param name="depth" select="$depth"/>

    </xsl:apply-templates>

    </xsl:template>

     

    <xsl:template match="*" mode="xml-to-string">

    <xsl:param name="depth"/>

    <xsl:variable name="element" select="."/>

    <xsl:value-of select="$start-tag-start"/>

    <xsl:call-template name="element-name">

    <xsl:with-param name="text" select="name()"/>

    </xsl:call-template>

    <xsl:apply-templates select="@*" mode="xml-to-string"/>

    <xsl:for-each select="namespace::*">

    <xsl:call-template name="process-namespace-node">

    <xsl:with-param name="element" select="$element"/>

    <xsl:with-param name="depth" select="$depth"/>

    </xsl:call-template>

    </xsl:for-each>

    <xsl:choose>

    <xsl:when test="node() or not($use-empty-syntax)">

    <xsl:value-of select="$start-tag-end"/>

    <xsl:apply-templates mode="xml-to-string">

    <xsl:with-param name="depth" select="$depth + 1"/>

    </xsl:apply-templates>

    <xsl:value-of select="$end-tag-start"/>

    <xsl:call-template name="element-name">

    <xsl:with-param name="text" select="name()"/>

    </xsl:call-template>

    <xsl:value-of select="$end-tag-end"/>

    </xsl:when>

    <xsl:otherwise>

    <xsl:value-of select="$empty-tag-end"/>

    </xsl:otherwise>

    </xsl:choose>

    </xsl:template>

     

    <xsl:template name="process-namespace-node">

    <xsl:param name="element"/>

    <xsl:param name="depth"/>

    <xsl:variable name="declaredAbove">

    <xsl:call-template name="isDeclaredAbove">

    <xsl:with-param name="depth" select="$depth – 1"/>

    <xsl:with-param name="element" select="$element/.."/>

    </xsl:call-template>

    </xsl:variable>

    <xsl:if test="(not($exclude-unused-prefixes) or ($element | $element//@* | $element//*)[namespace-uri()=current()]) and not(string($declaredAbove)) and name()!=’xml’">

    <xsl:value-of select="$space"/>

    <xsl:value-of select="$ns-decl"/>

    <xsl:if test="name()">

    <xsl:value-of select="$colon"/>

    <xsl:call-template name="ns-prefix">

    <xsl:with-param name="text" select="name()"/>

    </xsl:call-template>

    </xsl:if>

    <xsl:value-of select="$equals"/>

    <xsl:value-of select="$attribute-delimiter"/>

    <xsl:call-template name="ns-uri">

    <xsl:with-param name="text" select="string(.)"/>

    </xsl:call-template>

    <xsl:value-of select="$attribute-delimiter"/>

    </xsl:if>

    </xsl:template>

     

    <xsl:template name="isDeclaredAbove">

    <xsl:param name="element"/>

    <xsl:param name="depth"/>

    <xsl:if test="$depth > 0">

    <xsl:choose>

    <xsl:when test="$element/namespace::*[name(.)=name(current()) and .=current()]">1</xsl:when>

    <xsl:when test="$element/namespace::*[name(.)=name(current())]"/>

    <xsl:otherwise>

    <xsl:call-template name="isDeclaredAbove">

    <xsl:with-param name="depth" select="$depth – 1"/>

    <xsl:with-param name="element" select="$element/.."/>

    </xsl:call-template>

    </xsl:otherwise>

    </xsl:choose>

    </xsl:if>

    </xsl:template>

     

    <xsl:template match="@*" mode="xml-to-string">

    <xsl:value-of select="$space"/>

    <xsl:call-template name="attribute-name">

    <xsl:with-param name="text" select="name()"/>

    </xsl:call-template>

    <xsl:value-of select="$equals"/>

    <xsl:value-of select="$attribute-delimiter"/>

    <xsl:call-template name="attribute-value">

    <xsl:with-param name="text" select="string(.)"/>

    </xsl:call-template>

    <xsl:value-of select="$attribute-delimiter"/>

    </xsl:template>

     

    <xsl:template match="comment()" mode="xml-to-string">

    <xsl:value-of select="$comment-start"/>

    <xsl:call-template name="comment-text">

    <xsl:with-param name="text" select="string(.)"/>

    </xsl:call-template>

    <xsl:value-of select="$comment-end"/>

    </xsl:template>

     

    <xsl:template match="processing-instruction()" mode="xml-to-string">

    <xsl:value-of select="$pi-start"/>

    <xsl:call-template name="pi-target">

    <xsl:with-param name="text" select="name()"/>

    </xsl:call-template>

    <xsl:value-of select="$space"/>

    <xsl:call-template name="pi-text">

    <xsl:with-param name="text" select="string(.)"/>

    </xsl:call-template>

    <xsl:value-of select="$pi-end"/>

    </xsl:template>

     

    <xsl:template match="text()" mode="xml-to-string">

    <xsl:call-template name="text-content">

    <xsl:with-param name="text" select="string(.)"/>

    </xsl:call-template>

    </xsl:template>

     

    <xsl:template name="element-name">

    <xsl:param name="text"/>

    <xsl:value-of select="$text"/>

    </xsl:template>

     

    <xsl:template name="attribute-name">

    <xsl:param name="text"/>

    <xsl:value-of select="$text"/>

    </xsl:template>

     

    <xsl:template name="attribute-value">

    <xsl:param name="text"/>

    <xsl:variable name="escaped-markup">

    <xsl:call-template name="escape-markup-characters">

    <xsl:with-param name="text" select="$text"/>

    </xsl:call-template>

    </xsl:variable>

    <xsl:choose>

    <xsl:when test="$attribute-delimiter = &quot;&quot;">

    <xsl:call-template name="replace-string">

    <xsl:with-param name="text" select="$escaped-markup"/>

    <xsl:with-param name="replace" select="&quot;&quot;"/>

    <xsl:with-param name="with" select="&amp;apos;’"/>

    </xsl:call-template>

    </xsl:when>

    <xsl:when test="$attribute-delimiter = ‘&quot;">

    <xsl:call-template name="replace-string">

    <xsl:with-param name="text" select="$escaped-markup"/>

    <xsl:with-param name="replace" select="&quot;"/>

    <xsl:with-param name="with" select="&amp;quot;’"/>

    </xsl:call-template>

    </xsl:when>

    <xsl:otherwise>

    <xsl:call-template name="replace-string">

    <xsl:with-param name="text" select="$escaped-markup"/>

    <xsl:with-param name="replace" select="$attribute-delimiter"/>

    <xsl:with-param name="with" select=""/>

    </xsl:call-template>

    </xsl:otherwise>

    </xsl:choose>

    </xsl:template>

     

    <xsl:template name="ns-prefix">

    <xsl:param name="text"/>

    <xsl:value-of select="$text"/>

    </xsl:template>

     

    <xsl:template name="ns-uri">

    <xsl:param name="text"/>

    <xsl:call-template name="attribute-value">

    <xsl:with-param name="text" select="$text"/>

    </xsl:call-template>

    </xsl:template>

     

    <xsl:template name="text-content">

    <xsl:param name="text"/>

    <xsl:call-template name="escape-markup-characters">

    <xsl:with-param name="text" select="$text"/>

    </xsl:call-template>

    </xsl:template>

     

    <xsl:template name="pi-target">

    <xsl:param name="text"/>

    <xsl:value-of select="$text"/>

    </xsl:template>

     

    <xsl:template name="pi-text">

    <xsl:param name="text"/>

    <xsl:value-of select="$text"/>

    </xsl:template>

     

    <xsl:template name="comment-text">

    <xsl:param name="text"/>

    <xsl:value-of select="$text"/>

    </xsl:template>

     

    <xsl:template name="escape-markup-characters">

    <xsl:param name="text"/>

    <xsl:variable name="ampEscaped">

    <xsl:call-template name="replace-string">

    <xsl:with-param name="text" select="$text"/>

    <xsl:with-param name="replace" select="&amp;"/>

    <xsl:with-param name="with" select="&amp;amp;’"/>

    </xsl:call-template>

    </xsl:variable>

    <xsl:variable name="ltEscaped">

    <xsl:call-template name="replace-string">

    <xsl:with-param name="text" select="$ampEscaped"/>

    <xsl:with-param name="replace" select="&lt;"/>

    <xsl:with-param name="with" select="&amp;lt;’"/>

    </xsl:call-template>

    </xsl:variable>

    <xsl:call-template name="replace-string">

    <xsl:with-param name="text" select="$ltEscaped"/>

    <xsl:with-param name="replace" select="‘]]>’"/>

    <xsl:with-param name="with" select="‘]]&amp;gt;’"/>

    </xsl:call-template>

    </xsl:template>

     

    <xsl:template name="replace-string">

    <xsl:param name="text"/>

    <xsl:param name="replace"/>

    <xsl:param name="with"/>

    <xsl:variable name="stringText" select="string($text)"/>

    <xsl:choose>

    <xsl:when test="contains($stringText,$replace)">

    <xsl:value-of select="substring-before($stringText,$replace)"/>

    <xsl:value-of select="$with"/>

    <xsl:call-template name="replace-string">

    <xsl:with-param name="text" select="substring-after($stringText,$replace)"/>

    <xsl:with-param name="replace" select="$replace"/>

    <xsl:with-param name="with" select="$with"/>

    </xsl:call-template>

    </xsl:when>

    <xsl:otherwise>

    <xsl:value-of select="$stringText"/>

    </xsl:otherwise>

    </xsl:choose>

    </xsl:template>

    </xsl:stylesheet>

     

    Conclusion

    In Summary I showed you the default way the SQL adapter works and its limitations when dealing with XML SQL Data Type, you would need to use XSLT and all sorts of stuff to call a store procedure that inserts XML data directly. In Part 3 we will look at writing a SQL Adapter, a custom one, Pimped to the max to support sending BizTalk XML messages directly to SQL, no maps, no transforms, no orchestrations and slow bumps to cause us pain.

    I hope you enjoyed this article. See you in Part 3!

    Note WCF is the best way to go now for these sort of things. best is WCF Custom  with SQL binding for total control.