An Overview
Let's think of a scenario where I am planning to show information on regional, national and international news, weather information, ongoing sports scores and other personalized content in a web site. Just think about how much effort and time it will take me to develop this application if I write the code for all these functionalities. On the other hand all these functionalities are already provided by other available sites. So, what if I can use this existing logic in my application? But, the question here is “how I can use someone else's business logic in my application?”.
For situations of this sort (& many other), we have techniques like Web Services.
With Web Services, you can reuse someone else's business logic instead of replicating it yourself, using just a few lines of code. This technique is similar to what programmers currently do with libraries of APIs, DLLs or plug-ins. The main difference is that Web Services can be located remotely on another server.
When HTML pages (or the HTML output generated by ASP.NET web forms) are rendered in a browser for the end user, Web Services are invoked by other applications. They are pieces of business logic that are hosted somewhere on the internet and can be accessed by other applications.
Note 1: Web Services are not limited to the .NET Framework. The standards were defined before .NET was released and they are exposed, used and supported by vendors other than Microsoft.
Note 2: Web Services are cross-platform; a service written in one language can be invoked by an application in some other language. The only requirement for accessing a service is an internet connection to make the HTTP request.
Since a web service is cross-platform, there should be some commonly understandable language for requesting a service and getting a response from the service. Such a standard common language is XML. That's why Web Services are built on XML-based standards for exchanging data.
As a result, the set of data types Web Services can use is limited to the set of data types recognized by the XML Schema standard. So you can use simple data types such as strings and numbers to communicate with a web service and you can't send proprietary .NET objects such as a FileStream, an Image or an EventLog. This restriction makes a lot of sense. Since other programming languages have no way to interpret these .NET objects, even if you could devise a way to send them over the wire, the client might not be able to interpret them, that would thwart interoperability.
Note 3: If you need to work with .NET proprietary objects, you can go for .NET remoting. It is a distributed technology that allows use of .NET objects. But non-.NET clients can't consume it.
Here's the list of supported data types
- Built-in types (The Basics): The built in C# data types such as short, int, long, ushort, uint, ulong, float, double, decimal, bool, string, char, byte and DateTime.
- Objects: You can use an object of any user defined class. If your class has methods, these methods will not be transmitted to the client.
- Arrays: Arrays of any supported type (built-in or custom objects). You can also use an ArrayList (that is simply converted into an array).
- Enumerations: Enums are supported. However, a web service uses the string name of the enumeration value, not the underlying integer.
- XmlNode: Objects based on System.Xml.XmlNode represent a portion of an XML document. You can use this to send arbitrary XML.
- DataSet and DataTable : DataSet and DataTable are allowed. But other ADO.NET data objects, such as DataColumns and DataRows, aren't supported.
Create a web service
A web service is a simple asmx page.
Here I will use Visual Studio 2012 (though you can use any editor), with the .Net Framework 3.5 to create a web service.
Up to framework 3.5, Visual Studio provides a direct template for Web Services. Starting from 4, it doesn't provide any direct template, so you need to create a web application and add a web service to your application (right-click on your web application → Add → New Item → WebService).
Let's create a simple service that will return a sum of 2 integers.
Open Visual Studio in Administrator mode. File → New → Project then select .Net Framework 3.5 (on the top) then select ASP.NET web service application then name your project (I named it MyWebServiceDemo) then click OK.
Visual Studio will create a web service boilerplate (Service1.asmx and Service1.asmx.cs) for you. Now, let's analyze this template created by Visual Studio.
Note the following In Service1.asmx.cs:
- An additional namespace “System.Web.Services” is included along with the 4 default namespaces that Visual Studio includes for web application.
- The “Service1” class is inherited from “System.Web.Services.WebService”. By inheriting a class from “System.Web.Services.WebService”, you can access built-in ASP.NET objects such as (Application, Session, User, Context, server). If you don't need built-in objects of .Net, you don't need to inherit your service class from “WebService”.
- “Service1” is decorated with a “WebService(Namespace = "http://tempuri.org/" )” attribute. If you want to expose a class as a service, you need to decorate it with the “WebService” attribute. This WebService attribute has several properties like:
- Namespace: This property makes a service uniquely identifiable. This is an XML property. A client application may be consuming several services, so there is the probability of a naming collision. To avoid this, it's service providers responsibility to use a unique namespace.
- Name: Use this property to provide a descriptive name to your service.
- Description: To provide a brief description on the service.
- “Service1” has another attribute “WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)”. This is to indicate the standard which service is following. If the service does not confirm to this standard, you will get an exception.
- One more attribute service is decorated with is “[System.Web.Script.Services.ScriptService]”, to make a service accessible from the client script, it should be decorated with this attribute.
- The Service1 class has a method HelloWorld that is decorated with a “[WebMethod]” attribute. The methods of the service that are to be accessed by the client application should be decorated with this attribute. There may be some method that the service is using for some internal functionality, client applications don't need to access them. Don't decorate such methods with a WebMethod attribute. The WebMethod attribute also has Name and Description properties that you can use to provide a self describing name or description respectively.
Now let's see the mark up. Right-click on Service1.asmx in Solution Explorer then select view mark up. In Service1.asmx, you will see that there is only a WebService directive with some attributes, since a service will be invoked by some application not by any end user. So the asmx page has no mark up.
<%@ WebService Language="C#" CodeBehind="Service1.asmx.cs" Class="MyWebServiceDemo.Service1"%>
- The “WebService” directive, indicates this asmx page is a web service.
- The “Language="C#"”, is to indicate language used for this service.
- The “CodeBehind” property is nothing to do with ASP.NET or web service, this is completely a Visual Studio property, that is used to map the asmx page with it's code behind page.
- The “Class” property holds fully qualified name of service class. This marks the entry point of service like main() in C programming language.
Now, run your application by hitting F5, http://localhost:56655/Service1.asmx will open in your browser (the port number may vary). You will find a link for Service Description, that will redirect to the WSDL document of the service, another link for HelloWorld (list for methods exposed by service) that will redirect to a page for testing this method.
Implementing a web service
Now let's implement the service. Rename the “Service1” file in Solution Explorer to something convenient like “MyService”. Change the class name from Service1 to MyService. Open the mark up (asmx) page.
As you can see here Visual Studio is unable to resolve “Service1” in the class property, since the class indicates a fully qualified name of the service and we renamed our Service1 class to MyService. So Visual Studio is unable to resolve it. So, now change the class property to “MyWebServiceDemo.MyService”. Change the “CodeBehind” property from “Service1.asmx.cs” to “MyService.asmx.cs” as we renamed the file also.
MyService.asmx
<%@ WebService Language="C#" CodeBehind="MyService.asmx.cs" Class="MyWebServiceDemo.MyService"%>
MyService.asmx.cs
using System.Web.Script.Serialization;
using System.Web.Services;
namespace MyWebServiceDemo
{
// Use "Namespace" attribute with an unique name,to make service uniquely discoverable
[WebService(Namespace = "http://tempuri.org/")]
// To indicate service confirms to "WsiProfiles.BasicProfile1_1" standard,
// if not, it will throw compile time error.
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
// To restrict this service from getting added as a custom tool to toolbox
[System.ComponentModel.ToolboxItem(false)]
// To allow this Web Service to be called from script, using ASP.NET AJAX
[System.Web.Script.Services.ScriptService]
public class MyService : WebService
{
[WebMethod]
public int SumOfNums(int First, int Second)
{
return First + Second;
}
}
}
Now, the service is ready to be used, let's compile and test it.
Test a web service
Let's run the project by hitting F5. The “http://localhost:56655/MyService.asmx” page will open that has a link for the Service description (the WSDL document, documentation for web service) another link for SumOfNums, which is for test page of SumOfNums method.
Let's use method overloading of the OOP concept. Add the following WebMethod in MyService class.
[WebMethod]
public float SumOfNums(float First, float Second)
{
return First + Second;
}
Hit F5 to run the application, you will get “Both Single SumOfNums(Single, Single) and Int32 SumOfNums(Int32, Int32) use the message name 'SumOfNums'. Use the MessageName property of the WebMethod custom attribute to specify unique message names for the methods.” error message. We just used method overloading concept, so why this error message? This is because these methods are not unique for a client application. As the error message suggests let's use the MessageName property of the WebMethod attribute as shown below:
[WebMethod (MessageName = "SumOfFloats")]
public float SumOfNums(float First, float Second)
{
return First + Second;
}
Now, compile and run the application. Again it's showing some different error message “Service 'MyWebServiceDemo.MyService' does not conform to WS-I Basic Profile v1.1”. As, WsiProfiles.BasicProfile1_1 doesn't support method overloading we are getting this exception. Now, either remove this “[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]” attribute or make it “[WebServiceBinding(ConformsTo = WsiProfiles.None)]”.
using System.Web.Script.Serialization;
using System.Web.Services;
namespace MyWebServiceDemo
{
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.None)]
[System.ComponentModel.ToolboxItem(false)]
[System.Web.Script.Services.ScriptService]
public class MyService : WebService
{
[WebMethod]
public int SumOfNums(int First, int Second)
{
return First + Second;
}
[WebMethod(MessageName = "SumOfFloats")]
public float SumOfNums(float First, float Second)
{
return First + Second;
}
}
}
Now you can use method overloading in the service.
The Test page
Click on SumOfNums, you will be redirected to “http://localhost:56655/MyService.asmx?op=SumOfNums”. You will see here that just “?op=SumOfNums” is appended to the service URL. This page has 2 text boxes for 2 input values (First, Second) that the SumOfNums method takes as an input parameter and a button “Invoke”, clicking on which you'll be redirected to: “http://localhost/WebServiceForBlog/MyService.asmx/SumOfNums” that is has the value that the SumOfNums method returned in XML format. Similarly, by clicking on “SumOfNums MessageName="SumOfFloats"”, you will be redirected to “http://localhost:56655/MyService.asmx?op=SumOfFloats”. So the “SumOfNums MessageName="SumOfFloats"” method will be known as “SumOfFloats” for client applications.
Now, the question is, from where does this test page come? We never added any mark up but still a page was rendered!
The test pages aren't part of the Web Services; they're just a frill provided by ASP.NET. The test page is rendered by ASP.NET using the web page c:\[WinDir]\Microsoft. NET\Framework\[Version] \Config\DefaultWsdlHelpGenerator.aspx. “Reflection” concept to render the test page.
You can also modify this test page for which you simply need to copy the DefaultWsdlHelpGenerator.aspx file to your web application directory, modify it and give it a name. I named it “MyWsdlHelpGenerator.aspx” and then changed the web.config file for the application by adding the <wsdlHelpGenerator> element, as shown here:
<configuration>
<system.web>
<webServices>
<wsdlHelpGenerator href="MyWsdlHelpGenerator.aspx"/>
</webServices>
</system.web>
</configuration>
The WSDL document
Web Services are self-describing, that means ASP.NET automatically provides all the information the client needs to consume a service as a WSDL document. The WSDL document tells a client what methods are present in a web service, what parameters and return values each method uses and how to communicate with them. WSDL is a XML standard. We'll explore the WSDL document in our next articles.
Host a web service
Since we will add a reference to this service and consume it from various applications and the port number is supposed to change, let's host this service on IIS to have a specific address of a service. Open IIS, go to the default web sites node then seelct Add Application then provide an alias name (I gave it WebServiceForBlog) then browse to the physical location of your service for the physical path field then click “OK”. You can now browse with an alias name (like http://localhost/WebServiceForBlog/) to test if the application was hosted properly. You'll get a “HTTP Error 403.14 – Forbidden” error since there is no default document set for this application. Add a “MyService.asmx” page as a default document. Now you can browse your service.
How to access a web service from a client application
Here our primary focus will be on consuming a web service. Let's quickly create a web service (or modify, if you have already created one).
MyService.asmx
<%@ WebService Language="C#" CodeBehind="MyService.asmx.cs" Class="MyWebServiceDemo.MyService" %>
MyService.asmx.cs
using System.Web.Script.Serialization;
using System.Web.Services;
namespace MyWebServiceDemo
{
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.None)]
[System.ComponentModel.ToolboxItem(false)]
[System.Web.Script.Services.ScriptService]
public class MyService
{
// Takes 2 int values & returns their summation
[WebMethod]
public int SumOfNums(int First, int Second)
{
return First + Second;
}
// Takes a stringified JSON object & returns an object of SumClass
[WebMethod(MessageName = "GetSumThroughObject")]
public SumClass SumOfNums(string JsonStr)
{
var ObjSerializer = new JavaScriptSerializer();
var ObjSumClass = ObjSerializer.Deserialize<SumClass>(JsonStr);
return new SumClass().GetSumClass(ObjSumClass.First, ObjSumClass.Second);
}
}
// Normal class, an instance of which will be returned by service
public class SumClass
{
public int First, Second, Sum;
public SumClass GetSumClass(int Num1, int Num2)
{
var ObjSum = new SumClass
{
Sum = Num1 + Num2,
};
return ObjSum;
}
}
}
Compile this application. Since we will consume this service from other applications we need a fixed address for this service, the port numbers are supposed to vary. So, host the web service on IIS (refer to the previous article for hosting the web service). I hosted it with the virtual directory “WebServiceForBlog”.
How to consume a web service
Let's add another project to our solution. You can also create a new project. I am adding to the same solution, so that it will not need me to open several instances of Visual Studio. When developing and testing a web service in Visual Studio, it's often preferred to add both the web service and the client application to the same solution. This allows you to test and change both pieces at the same time. You can even use the integrated debugger to set breakpoints and step through the code in both the client and the server.
Right-click on solution, select Add → New project then seelct ASP.NET Empty Application then name your application, I am naming it “ServiceConsumer”.
Add a Web Form, I name it “Home.aspx”.
On the click of a button in this page, we will call our service.
For calling a Web Service you need a proxy object that will handle the complexities of sending a SOAP request and response messages.
To create this proxy class, you need a reference to the service class. Right-click on this project then select Add service reference, a window will open, type the URL of your service, click on discover, you will see all the webmethods exposed by your service listed. At the bottom of the window, there'll be a field for Namespace. Provide a name for the namespace in which the proxy class of the referenced service will be generated, I am giving it “MyServiceReference”. Now, click on “Go”.
By adding a service reference, we created a proxy class of the referenced service to the current project (client app).
The proxy class wraps the calls to the web service's methods. It takes care of generating the correct SOAP message format and managing the transmission of the messages over the network using HTTP and converting the results received back to the corresponding .NET data types.
Now, add mark up in Home.aspx to receive inputs. Here's my mark up.
<table>
<tr>
<td>First Number:</td>
<td><input type="text" id="Text1" runat="server" /></td>
</tr>
<tr>
<td>Second Number:</td>
<td><input type="text" id="Text2" runat="server" /></td>
</tr>
<tr>
<td><input type="button" onserverclick="AddNumber" value="Add" runat="server" /></td>
<td><div id="divSum" runat="server"></div>
<div id="divSumThroughJson" runat="server"></div></td>
</tr>
</table>
Home.aspx.cs
using System;
using ServiceConsumer.MyServiceReference;
namespace ServiceConsumer
{
public partial class Home : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{}
protected void AddNumber(object Sender, EventArgs E)
{
int Num1, Num2;
int.TryParse(txtFirstNum.Value, out Num1);
int.TryParse(txtSecondNum.Value, out Num2);
// creating object of MyService proxy class
var ObjMyService = new MyServiceSoapClient();
// Invoke service method through service proxy
divSum.InnerHtml = ObjMyService.SumOfNums(Num1, Num2).ToString();
var ObjSumClass = new SumClass { First = Num1, Second = Num2 };
var ObjSerializer = new JavaScriptSerializer();
var JsonStr = ObjSerializer.Serialize(ObjSumClass);
divSumThroughJson.InnerHtml =ObjMyServiceProxy.GetSumThroughObject(JsonStr).Sum.ToString();
}
}
}
How to consume a web service from a client script
Add another project to your solution. I named it “ConsumeServiceFromClientScript”. Add a web form (let's say Default.aspx).
Creating a JavaScript proxy
JavaScript proxies can be automatically generated by using the ASP.NET AJAX ScriptManager control's Services property. You can define one or more services that a page can call asynchronously to send or receive data using the ASP.NET AJAX “ServiceReference” control and assigning the Web Service URL to the control's “Path” property.
Add a ScriptManager control like the following inside the form tag:
<asp:ScriptManager runat="server">
<Services>
<asp:ServiceReference Path="http://localhost/WebServiceForBlog/MyService.asmx"/>
</Services>
</asp:ScriptManager>
When you add a reference to a web service (MyService.asmx) from a page using a ScriptManager control, a JavaScript proxy is generated dynamically and referenced by the page. Now if you the check page source in a browser, you can notice that:
http://localhost/MyWebServiceDemoService/MyService.asmx/js
Or:
http://localhost/MyWebServiceDemoService/MyService.asmx/jsdebug
Is included in your page with a script tag depending on whether debugging is enabled In your project.
Add an empty Web form to your project. I named it “Default.aspx”.
Default.aspx
<body>
<form id="form1" runat="server">
<asp:ScriptManager runat="server">
<Services>
<asp:ServiceReference Path="http://localhost/WebServiceForBlog/MyService.asmx"/>
</Services>
</asp:ScriptManager>
<div>
<table>
<tr>
<td>First Number:</td>
<td><input type="text" id="txtFirstNum" /></td>
</tr>
<tr>
<td>Second Number:</td>
<td><input type="text" id="txtSecondNum" /></td>
</tr>
<tr>
<td><input type="button" onclick="AddNumber();" value="Add" /></td>
<td><div id="divSum1"></div><div id="divSum2"></div><div id="divSum3"></div><div id="divSum4"></div><div id="divSum5"></div></td>
</tr>
</table>
</div>
</form>
<script type="text/javascript" src="Scripts/jquery-1.7.1.js"></script>
<script type="text/javascript" src="Scripts/json2.js"></script>
<script type="text/javascript">
function AddNumber() {
var FirstNum = $('#txtFirstNum').val();
var SecondNum = $('#txtSecondNum').val();
$.ajax({
type: "POST",
url: "http://localhost/WebServiceForBlog/MyService.asmx/SumOfNums",
data: "First=" + FirstNum + "&Second=" + SecondNum,
// the data in form-encoded format, it would appear on a querystring
//contentType: "application/x-www-form-urlencoded; //charset=UTF-8"
//form encoding is the default one
dataType: "text",
crossDomain: true,
success: function (data) {
$('#divSum1').html(data);
}
});
// i/p in JSON format, response as JSON object
$.ajax({
type: "POST",
url: "http://localhost/WebServiceForBlog/MyService.asmx/SumOfNums",
data: "{First:" + FirstNum + ",Second:" + SecondNum + "}",
contentType:"application/json; charset=utf-8",// What I am ing
dataType: "json", // What I am expecting
crossDomain: true,
success: function (data) {
$('#divSum2').html(data.d);
}
});
// i/p as JSON object, response as plain text
$.ajax({
type: "POST",
url: "http://localhost/WebServiceForBlog/MyService.asmx/SumOfNums",
data: { First: FirstNum, Second: SecondNum },
contentType: "application/x-www-form-urlencoded; charset=UTF-8",
crossDomain: true,
dataType: "text",
success: function (data) {
$("#divSum3").html(data);
}
});
// Url encoded stringified JSON object as I/p & text response
var ObjSum = new Object();
ObjSum.First = FirstNum;
ObjSum.Second = SecondNum;
$.ajax({
type: "POST",
url: "http://localhost/WebServiceForBlog/MyService.asmx/GetSumThroughObject",
data: "JsonStr=" + JSON.stringify(ObjSum),
dataType: "text",
crossDomain: true,
success: function (data) {
$('#divSum4').html($(data).find('Sum').text());
}
});
// Call SOAP-XML web services directly
var SoapMessage = '<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> \
<soap:Body> \
<SumOfNums xmlns="http://tempuri.org/"> \
<First>'+FirstNum+'</First> \
<Second>'+SecondNum+'</Second> \
</SumOfNums> \
</soap:Body> \
</soap:Envelope>';
$.ajax({
url: "http://localhost/WebServiceForBlog/MyService.asmx?op=SumOfNums",
type: "POST",
dataType: "xml",
data: SoapMessage,
complete: ShowResult,
contentType: "text/xml; charset=utf-8"
});
}
function ShowResult(xmlHttpRequest, status) {
var SoapResponse = $(xmlHttpRequest.responseXML).find('SumOfNumsResult').text();
$('#divSum5').html(SoapResponse);
}
</script>
</body>
Note: For demo purposes, I have hard-coded the service URL. But it's not a good practice. You can keep the service URL in the web.config and use RegisterClientScriptBlock or RegisterStartUpScript depending on your requirements to use this service URL in client script.
Call SOAP-XML Web Services directly
In order to call the web service, we need to supply an XML message that matches the operation definition specified by the web service's WSDL. In the test page “http://localhost/WebServiceForBlog/MyService.asmx?op=SumOfNums” you can see a sample of SOAP 1.1/1.2 request and response format. From which you can get the operations schema. Now, you just need to exchange the type names for the operation's parameters, with their actual values. Copy the SOAP envelope to the Default.aspx page and replace the place holders (type names) with an actual value.
A sample for this type of call is included in the code snippet above. The variable “SoapMessage” in the preceding example contains the complete XML message that we're going to send to the web service. On completion of the ajax request, we will call a call back method named “ShowResult”.
Note: .NET 1.x supports SOAP 1.1 only. From .NET 2.0 Both SOAP1.1 and SOAP1.2 are supported unless one is restricted explicitly. If you want to change this, you can disable either one through the web.config file:
<configuration>
<system.web>
<webServices>
<protocols>
<!-- To disable SOAP 1.2 -->
<remove name="HttpSoap12"/>
<!-- To disable SOAP 1.1 -->
<remove name="HttpSoap"/>
</protocols>
</webServices>
</system.web>
</configuration>
References
Book: Programming .NET Web Services By Alex Ferrara, Matthew MacDonald
Book: Pro ASP.NET 3.5 in C# by Matthew MacDonald , Mario Szpuszta
Understanding ASP.NET AJAX Web Services