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

  • Advertisement
    • Uncategorized

    Leave a Reply

    Please log in using one of these methods to post your comment:

    WordPress.com Logo

    You are commenting using your WordPress.com account. Log Out /  Change )

    Facebook photo

    You are commenting using your Facebook account. Log Out /  Change )

    Connecting to %s