Showing posts with label Web Services. Show all posts
Showing posts with label Web Services. Show all posts

Thursday, 19 April 2012

Logging into file in C#


If this approach is used in WCF service hosted on IIS, we will more likely be interested in the Web Service Application directory which can be obtained by using HostingEnvironment.ApplicationPhysicalPath property - it returns WCF service application's physical path e.g. C:\inetpub\wwwroot\WebServices\MyService.



Make sure that IIS_IUSRS has permission to create, modify and delete files in the directory where WCF service log file resides.


Thursday, 15 March 2012

How to deploy WCF Web Service on IIS

In my article "How to create and test WCF Web Service" I described how to create a simple WCF Web Service. Its natural host environment is Microsoft IIS web server. Here is the step-by-step guide how to deploy WCF Service on IIS.

Let us assume that c:\inetpub\wwwroot\WCFServices is a directory that will contain all our WCF Web Services. Build output of Calculator project was CalculatorServiceLibrary.dll and its configuration file, CalculatorServiceLibrary.dll.config.

Let us create directory for our Calculator service, with its subdirectory bin:

c:\inetpub\wwwroot\WCFServices\Calculator
c:\inetpub\wwwroot\WCFServices\Calculator\bin

Now we need to copy our service dll and config file:

c:\inetpub\wwwroot\WCFServices\Calculator\CalculatorServiceLibrary.dll.config
c:\inetpub\wwwroot\WCFServices\Calculator\bin\CalculatorServiceLibrary.dll

IIS requires config file to be named as web.config so we will rename our config file accordingly:

c:\inetpub\wwwroot\WCFServices\Calculator\web.config
c:\inetpub\wwwroot\WCFServices\Calculator\bin\CalculatorServiceLibrary.dll

The next step is creating a simple single-line document named service.svc:



Service name stated here must match the one from web.config (CalculatorServiceLibrary.dll.config).

Complete set of files is:

c:\inetpub\wwwroot\WCFServices\Calculator\web.config
c:\inetpub\wwwroot\WCFServices\Calculator\service.svc
c:\inetpub\wwwroot\WCFServices\Calculator\bin\CalculatorServiceLibrary.dll

In IIS Manager, under Default Web Site, find WCFServices directory and its subdirectory, Calculator. Select Calculator, right-click on it and click on Convert to Application item in the context menu. Web Application Settings dialog appears and we can leave default values:

IISManagement-Calculator-App-Settings

When we close this dialog box, a Web Application icon appears next to the directory name:

IISManagement-Calculator-App

Our web service is now deployed! We can check that by typing its URL (http://localhost/WCFServices/Calculator/Service.svc) in web browser:

WebBrowser-WebService-Calc

We can test web methods by using WCF Test Client:

C:\Program Files\Microsoft Visual Studio 10.0\Common7\IDE>WcfTestClient.exe http://bojan-pc/WCFServices/Calculator/service.svc/mex

If running IIS on local host, localhost name can be used in web service URL:

C:\Program Files\Microsoft Visual Studio 10.0\Common7\IDE>WcfTestClient.exe http://localhost/WCFServices/Calculator/service.svc/mex

WCF Web Client:

WCFTestClient-Calc-Add

Fiddler is able to capture IIS HTTP traffic by default:

Fidller-WS-on-IIS-Calc-Raw-View

If we are interested only in SOAP messages, we can use XML View in WCF Test Client:

WCFTestClient-Calc-Add-XMLView

How to create and test WCF Web Service

Implementing and testing web services in Visual Studio (2010) using Windows Communication Foundation (WCF) is very easy.

Let's say we want to create Calculator service which exposes methods that return results of addition, subtraction, multiplication and division applied on provided operands. These operations always take two operands so we can group them in a class Operands.



Now we can define interface of such web service:



Once we have have a clear image of exposed web methods and data types we can start implementing web service. In Visual Studio (I am using VS2010), go to File -> New -> Project... and create Visual C# WCF Service Library project named CalculatorServiceLibrary. Make sure you are using the latest .NET Framework (e.g. version 4.0). Visual Studio creates project skeleton with following files:

IService1.cs (service interface definition file):

Service1.cs (service interface implementation file):


App.config:


These auto-generated files give us a hint how to organize the code and we need to modify them, inserting specifics of our service.

WCF is a framework for building Service Oriented Architecture (SOA) applications. SOA requires defining protocols on messages and data exchanged between service and client. They are known as Service and Data Contracts and are supported by WCF.

Web Service interface definition file contains these contracts.

Service Contract defines operations exposed by the service. [ServiceContract] attribute applied to an interface (or class) tells it represents a service contract. Interface or class methods that are to be exposed as service operations are marked with [OperationContract] attribute.

Data exchanged between client and service (service operations arguments and return values) is described through Data Contract. All data is embedded into SOAP messages which are XML-based so data types must be serializable. All primitive .NET types are serializable by default and have default data contracts. But custom types that are part of data contract must be marked with [DataContract] attribute which defines them to be serializable. Type members that are part of data contract must be marked with [DataMember] attribute.

In our case, custom data type is class Operands and we need to mark it as [DataContract]. We can rename IService1.cs and Service1.cs to ICalculatorService.cs and CalculatorService.cs. After modifying the interface, its implementation class and data type, these files look like this:

ICalculatorService.cs:


CalculatorService.cs:


[ServiceBehavior] attribute controls various aspects of service object behaviour. In our case we specified that there will be only a single instance of the service object created on the server and that only a single thread at a time will be allowed to process method calls. This means that one client would need to wait for another's client web method call to complete. In our simple example this is acceptable but if amount of processing within web methods was higher, a different behaviour model would need to be applied.

Before compiling our project we yet need to modify application configuration file (App.config). Let us rename service to "CalculatorServiceLibrary.CalculatorService" and name behavior as "MetadataEnabled". In order to enable generating WSDL we need to add reference to this behavior as a service attribute: behaviorConfiguration="MetadataEnabled" and add httpGetUrl="mex" to behavior's serviceMetadata element. URL of the WSDL can now be made by appending "/mex" to the base address.

By default, service endpoint binding is set to wsHttpBinding which encrypts messages. For our convenience, as we will be using HTTP sniffer later, let us apply basicHttpBinding, which does not apply encryption. This way we will be able to see unencrypted data in HTTP messages exchanged between server and client.

Yet another thing set by default is to be changed: service endpoint contract - let us rename it to "CalculatorServiceLibrary.ICalculatorService".

App.config will have the final look:


We are now ready to build this project. If we build it in Debug mode, project's Debug directory will contain CalculatorServiceLibrary.dll and CalculatorServiceLibrary.dll.config. This config file is a pure copy of App.config visible and editable from Visual Studio.

We can test our web service from Visual Studio if we run the project (press F5 key). This will start WCF Service Host and WCF Test Client tools. Service will automatically be deployed on the host and the client will use published metadata information in order to build a list of web methods (matadata is fetched from endpoint that implements IMetadataExchange contract). We can type in arguments and invoke web methods in the Test Client.

WCF Service Host:

WCFSvcHost

WCF Test Client:

WCFTestClient

In order to see generated WSDL we can copy metadata address (WSDL URL; in our example: http://localhost:8732/Design_Time_Addresses/CalculatorServiceLibrary/Service1/mex) from Web Service Host and paste it into a web browser:

WSDL-WS-Calc

Generated WSDL for this service is:



We can run WCF Service Host out of Visual Studio, from command prompt:

C:\Program Files\Microsoft Visual Studio 10.0\Common7\IDE>WcfSvcHost.exe /service:"c:\DEVELOPMENT\RESEARCH\C#\WCF\Web Services\CalculatorServiceLibrary\CalculatorServiceLibrary\bin\Debug\CalculatorServiceLibrary.dll" /config:"c:\DEVELOPMENT\RESEARCH\C#\WCF\Web Services\CalculatorServiceLibrary\CalculatorServiceLibrary\bin\Debug\CalculatorServiceLibrary.dll.config"

C:\Program Files\Microsoft Visual Studio 10.0\Common7\IDE>

If this instance of the host cannot create endpoint (cannot listen) on the port 8732 (because previous instance run from Visual Studio has locked it), enter some other port number in baseAddress in App.config (e.g. 8733).

From another instance of cmd.exe, we can run WCF Test Client:

C:\Program Files\Microsoft Visual Studio 10.0\Common7\IDE>WcfTestClient.exe http://localhost:8732/Design_Time_Addresses/CalculatorServiceLibrary/Service1/mex

Client and host exchange SOAP messages (request and response) during web method calls. We could see the content of those SOAP messages by using some HTTP sniffer. In my next article, "How to sniff SOAP messages exchanged between WCF Service Host and Test Client" I will describe how to set Fiddler Web Debugger in order to capture HTTP traffic between these two applications.

Links and References:

What Is Windows Communication Foundation (MSDN)
Windows Communication Foundation (Wikipedia)
Designing and Implementing Services (MSDN)
Using Data Contracts (MSDN)
Sessions, Instancing, and Concurrency (MSDN)

Thursday, 15 September 2011

How to create a SOAP request

So you know there's a web service deployed on some web server and you want to make a web service call. One way of achieving this is to create SOAP message, put it in a body of HTTP POST command and send it (via TCP/IP) to that web server. If you are using some of web service frameworks (e.g. Apache Axis), you don't need to know the structure of SOAP message; you'll just be adding its elements through dedicated framework functions. But if you don't want to rely on such frameworks, be prepared for creating and parsing SOAP messages by yourself! You can use some HTTP framework (e.g. Microsoft's WinHTTP) to create HTTP POST request and then insert your SOAP request in HTTP message body. Or, you can go one level down and create TCP socket, manually build SOAP and HTTP messages and send them over TCP connection...In this article I'll focus on SOAP messages only.

SOAP (Simple Object Access Protocol) is defined by World Wide Web Consortium (W3C) and the specification of its latest version can be found here.

Paragraph 5 ("SOAP Message Construct") states that SOAP message is represented with one element, named Envelope, from a namespace "http://www.w3.org/2003/05/soap-envelope". XML namespace can be referred via its prefix which is like its alias (abbreviation) and can be any string, soapenv in this case:

<soapenv:envelope xmlns:soapenv="http://www.w3.org/2003/05/soap-envelope">
...
</soapenv:envelope>

Prefix and local name of an element together make its qualified name (QName). XML parser uses the prefix to find the actual namespace URI.

Following examples are using different namespace prefixes but they all refer to the same URI where namespace is defined:

<soap:envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope">
...
</soap:envelope>

<soap-env:envelope xmlns:soap-env="http://www.w3.org/2003/05/soap-envelope">
...
</soap-env:envelope>

<env:envelope xmlns:env="http://www.w3.org/2003/05/soap-envelope">
...
</env:envelope>

Apart from its local name and namespace, Envelope element contains:
  • other namespaces, from other schemas (optional)
  • namespace-qualified attributes (optional)
  • Header element (optional child)
  • Body element (mandatory child)

namespace-qualified means that node's name is made of namespace prefix and a local name.

One of the possible attributes determines which encoding should be used to deserialize this message. Its name must contain namespace part (envelope's namespace) and its local name - encodingStyle. Its value is URI of schema that defines encoding:

<soapenv:envelope xmlns:soapenv="http://www.w3.org/2003/05/soap-envelope"
                  soapenv:encodingStyle="http://www.w3.org/2003/05/soap-encoding">
...
</soapenv:envelope>

SOAP Header element contains:
  • its qualified name made up of envelope's namespace and local name Header (mandatory)
  • namespace-qualified attributes (optional)
  • namespace-qualified SOAP Header Blocks - children elements (optional)
Each Header Block contains:
  • local name and namespace (mandatory)
  • attributes (optional); any of all of following attributes defined in soapenv namespace:
      • encodingStyle
      • role
      • mustUnderstand
      • relay
SOAP Body element contains:
  • its qualified name made up of envelope's namespace and local name Body (mandatory)
  • namespace-qualified attributes (including encodingStyle) (optional)
  • namespace-qualified children elements (optional)

Let us assume that web service has method add which accepts two arguments - two integers - and returns their sum. It would expect to receive XML element like this:

<add>
   <op1>3</op1>
   <op2>4</op2>
</add>

Client should send this element within Body part of SOAP message which could be, in its simplest form (containing only mandatory nodes), like this:

<?xml version="1.0"?>
<soapenv:Envelope xmlns:soapenv="http://www.w3.org/2003/05/soap-envelope">
 <soapenv:Body>
  <add>
   <op1>3</op1>
   <op2>4</op2>
  </add>
 </soapenv:Body>
</soapenv:Envelope>

Web server receives HTTP POST message, extracts SOAP message from its body and forwards it to Web Service which analyses its headers (if any) and extracts XML from its body. It parses XML and extracts web method name and its arguments.

Obviously, both client and service must use the same function and argument names. This can be enforced if they both use XML documents that are associated to (and are validated against) the same XML schema. How to create one for our XML? If we know the valid structure of it (defined here; see W3C XML Schema page), we could do it manually (which is a kind of reverse engineering) but this is not a way to go: it's better using some tool which will generate it for us from a given XML. Microsoft Visual Studio (2008/2010) could be used as such tool: open your XML file in it and select XML->Create Schema from a main menu. It will create XML Schema document file, named after XML file and with extension xsd.

For wstest.xml:
<?xml version="1.0" encoding="utf-8"?>
<add>
   <op1>3</op1>
   <op2>4</op2>
</add>

wstest.xsd is output:
<?xml version="1.0" encoding="utf-8"?>
<xs:schema attributeformdefault="unqualified" elementformdefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:element name="add">
    <xs:complextype>
      <xs:sequence>
        <xs:element name="op1" type="xs:unsignedByte"/>
        <xs:element name="op2" type="xs:unsignedByte"/>
      </xs:sequence>
    </xs:complextype>
  </xs:element>
</xs:schema>

In the example above we are not using namespaces which is a bad practice as element/attribute name clashes can occur. Let us write an XML with namespace-qualified names:
<?xml version="1.0" encoding="utf-8"?>
<ns1:add xmlns:ns1="http://www.bk.com/webservices/wstest">
   <ns1:op1>3</ns1:op1>
   <ns1:op2>4</ns1:op2>
</ns1:add>

Its wstest.xsd:
<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:ns1="http://www.bk.com/webservices/wstest" attributeFormDefault="unqualified" elementFormDefault="qualified" targetNamespace="http://www.bk.com/webservices/wstest" xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:element name="add">
    <xs:complextype>
      <xs:sequence>
        <xs:element name="op1" type="xs:unsignedByte"/>
        <xs:element name="op2" type="xs:unsignedByte"/>
      </xs:sequence>
    </xs:complextype>
  </xs:element>
</xs:schema>

XML schema above defines namespace "http://www.bk.com/webservices/wstest" (look for targetNamespace attribute) and other metadata related to our XML: valid element names ("add", "op1", "op2"), element and value types. It is worth mentioning here that namespace names are just arbitrary strigs, not locations on the web that XML parsers use in order to retrieve any information. We usually use URIs though (in form of URNs or URLs) as that decreases the chance of two namespaces having the same name. XML parsers treat them just like simple strings.

SOAP request which regards this new namespace looks like this:
<?xml version="1.0"?>
<soapenv:Envelope xmlns:soapenv="http://www.w3.org/2003/05/soap-envelope">
 <soapenv:Body>
  <ns1:add xmlns:ns1="http://www.bk.com/webservices/wstest">
   <ns1:op1>3</ns1:op1>
   <ns1:op2>4</ns1:op2>
  </ns1:add>
 </soapenv:Body>
</soapenv:Envelope>

Having local names within namespaces still does not make this document valid. XML document is valid only if it meets requirements of the schema with which it has been associated.

XML Schema can be referenced in an XML document by using schemaLocation attribute in document's root element. This attribute is defined in "http://www.w3.org/2001/XMLSchema-instance" namespace (which is traditionally aliased with xsi name) so we need to include this namespace as well.

We can reference our schema in our SOAP request:
<?xml version="1.0"?>
<soapenv:Envelope xmlns:soapenv="http://www.w3.org/2003/05/soap-envelope" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.bk.com/webservices/wstest wstest.xsd">
 <soapenv:Body>
  <ns1:add xmlns:ns1="http://www.bk.com/webservices/wstest">
   <ns1:op1>3</ns1:op1>
   <ns1:op2>4</ns1:op2>
  </ns1:add>
 </soapenv:Body>
</soapenv:Envelope>

There are two forms of referencing schema:

  • using relative URL and schema file name which says that the XSD file is in the same directory as the XML document: 
xsi:schemaLocation="http://www.bk.com/webservices/wstest wstest.xsd" (used in the example above)
               or
xsi:schemaLocation="wstest.xsd"

  • using absolute URL of XSD:
xsi:schemaLocation="http://www.bk.com/webservices/wstest/wstest.xsd"

If a schema processor finds schema reference in a document, it should try to retrieve the schemas at the locations indicated. When no information is provided at all the schema processor is free to try any method to find a schema. 

If our XML document uses simple types, we can use and reference W3C XML Schema in our document. It is bounded to "http://www.w3.org/2001/XMLSchema" namespace whose aliases are traditionally xs (used in this example) and xsd (often used for Microsoft schemas).

<?xml version="1.0"?>
<soapenv:Envelope xmlns:soapenv="http://www.w3.org/2003/05/soap-envelope" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
 <soapenv:Body>
  <ns1:add xmlns:ns1="http://www.bk.com/webservices/wstest">
   <ns1:op1 xsi:type="xs:int">3</ns1:op1>
   <ns1:op2 xsi:type="xs:int">4</ns1:op2>
  </ns1:add>
 </soapenv:Body>
</soapenv:Envelope>

There are many tools that generate WSDL documents for given Web Services. WSDL document describes Web Service, lists its public methods and their arguments. It refers XML schema(s) and describes valid XML structure for web method calls. WSDL (and so XML Schemas) is made public so clients can create SOAP requests and validate them. Schema is used by web service to validate incoming SOAP request and then on the client side to validate response.

Links and references:

SOAP Version 1.2 Part 0: Primer
SOAP Version 1.2 Part 1: Messaging Framework
SOAP Version 1.2 Part 2: Adjuncts
SOAP 1.2 Part 3: One-Way MEP
XML Namespaces by Example
XML Tutorial
Creating Extensible Content Models
XML Schema Part 0: Primer
XML Schema Part 1: Structures
XML Schema Part 2: Datatypes
Using W3C XML Schema
XML Schema (O'Reilly eBook)
XML Schema (W3C)
XML schema (Wikipedia)
XML Schema (W3C)(Wikipedia)
XML Schema (XSD) validation tool? (Stack Overflow)
XML namespace (Wikipedia)
XML Namespaces (W3Schools)
XML Namespace Name: URN or URL?
Understanding XML Namespaces (MSDN Magazine)
XPath, XSLT, and other XML Specifications (MSDN Magazine)
XML Namespaces FAQ
The basics of using XML Schema to define elements
Tip: Send and receive SOAP messages with SAAJ
Apache SOAP type mapping, Part 1: Exploring Apache's serialization APIs
SOAP Tutorial (W3Schools)
XML Schema Tutorial (W3Schools)
SoapUser.com
WSDL Essentials

Tuesday, 16 August 2011

Axis2C logging and its documentation

I deployed Axis2C (1.6.0) on IIS (7.0) in order to host one web service. Axis2C is a Web Service engine with a logging feature - debug output can be redirected either to console or a text file. Log file is created in a directory with path constructed automatically by Axis engine at the web service startup. Axis installation directory path is kept in AXIS2C_HOME environment variable which must be added manually. During Axis2C deployment, additional information should be stored in a registry, at the path HKEY_LOCAL_MACHINE\SOFTWARE\Apache Axis2c\IIS ISAPI Redirector:
  • axis2c_home (string) - path to Axis2C installation directory (usually c:\axis2c)
  • log_file (string) - full path to log file log file name (read further this article for the explanation)
  • log_level (string) - trace, error, info, critical, user, debug, or warning
These registry entries can be added either manually or by running axis2_iis_regedit.js script which is included in Axis2C package.

I followed online documentation when deploying Axis and my service was running smoothly but I could not find log file. It was supposed to be in c:\axis2c\logs\axis2.log (log_file value I set) but directory was empty. Two things could be the root of the problem: engine either could not find path where to create a file or parent process didn't have enough rights to do so. Process Monitor is a good friend in these situations. I restarted World Wide Web Publishing Service and applied filter in Process Monitor to see only system events for w3wp.exe (IIS working process - one that loads Axis engine) only:

Process Monitor- Axis2C on IIS

Wow! Axis tried to create a log file at the very strange path: c:\axis2c\logs\c:\axis2c\logs\axis2.log. Axis engine was following this logic when creating path string:

log_file_path = $(AXIS2C_HOME) + "\logs\" + log_file

Documentation says:
"(add...) A String value with the name "log_file". The value is the absolute path of the log file.
Example: c:\axis2c\logs\axis2.log"


Even on the another page:
"Add a string value with the name log_file and a value of c:\axis2c\logs\axis2.log"

This seems to be wrong and correct version would be: log_file should contain log file name (e.g. axis2.log). When I applied this logic, log file appeared at the correct place!

I was curious about something else as well: what is axis2c_home registry value used for? As a test, I set it to some rubbish value: c:\axis2c123. I restarted IIS and this time set log_file to axis2.log.

To test whether Axis engine has been deployed successfully on IIS, you can use your browser: just type http://localhost/axis2/services and a page with a list of deployed services should appear. This didn't happen in my case, web page contained this message: An IIS server error occurred. An error occurred while initilizing Axis2/C. I checked log file (which was in expected directory: c:\axis2c\logs):

[error] ..\..\src\core\deployment\dep_engine.c(284) Repository path C:\axis2c123 does not exist
[error] ..\..\src\core\deployment\conf_init.c(56) Creating deployment engine failed for repository C:\axis2c123
[error] ..\..\src\core\deployment\dep_engine.c(284) Repository path C:\axis2c123 does not exist
[error] ..\..\src\core\deployment\conf_init.c(56) Creating deployment engine failed for repository C:\axis2c123

Seems that Axis engine used registry value axis2c_home (instead of environment variable AXIS2C_HOME as one would expect) to read installation directory required for proper initialization. I didn't dig further into this matter but only noticed that this confusion was a consequence of redundancy: installation directory was stored both in environment variable and registry - the same information was stored at two different places which is the practice that should be avoided.

I hope that next Axis2C release will be a bit more consistent in terms of configuration and with more accurate documentation.