ࡱ > !` bjbj\\ . > > % l l l 8 Ll l m ^ Vn " xn xn xn So So So $ J h 5 So So So So So 5 xn xn J ף ף ף So X$ xn xn ף So ף ף r T xn m : l C ` 0 Q 8 8 8 So So ף So So So So So 5 5 X So So So So So So So de l l Web Services in C# and .Net
Web Services provide the most flexible infrastructure for creating distributed computing applications. A web service in its simplest form is an object oriented class residing on a web server, and allowing remote clients to invoke its methods. Prior to web services, programmers have relied on sockets, RPC (Remote Procedure Calls), distributed COM (DCOM), and RMI (Remote Method Invocation) technologies to create distributed applications. The major problem with the above solutions is that either they are difficult to program (e.g., sockets), or require both the client and server side to be using the same technology (e.g., DCOM is mostly windows based, and RMI is Java based requiring both the client and server programs to be written in Java). The web services architecture overcomes these limitations. The following are the main motivations behind the development of web services.
The client and the web service can be remote from each other.
The client and the web service can use a totally different operating system or programming language.
The web service can be made available through firewalls that allow port 80 but block other ports.
The above goals are easily accomplished by having the client and the web service use XML (or Http Get/Post protocols for simple type of parameters in method calls) to invoke methods on the service and receive back results. The format of XML used in invoking a web service is a W3C standard known as SOAP (Simple Object Access Protocol).
Here are some of the characteristics of a web service.
Web services do not contain any user interface, and are mainly a class or classes exposing some useful methods to a client.
A web server is used to host the web service which uses port 80 to make the web service available to clients.
The methods of a web service can be invoked either by Http Get, or Http Post, or using SOAP.
Http Get/Post techniques are used when the method parameters and results are simple data types. However, for passing back and forth structures and class objects in method parameters, SOAP is the solution. .Net provides HttpGetClientProtocol, HttpPostClientProtocol and SoapHttpClientProtocol classes for using Get, Post or SOAP protocols in the client code.
The client of a web service can either be a web browser, a web application or a desktop application (e.g., a windows form application).
All clients to a web service require a proxy (very similar to proxy in RPC or DCOM or RMI). The job of proxy is to marshal the parameters and results in method calls to a web service.
The client code simply invokes an object of the proxy class (which has same methods and their signatures as the methods in the web service), and calls the proxy methods. The proxy, serializes the method calls to the web service using either Http Get/Post or SOAP, and also deserializes the results back to the client.
If the client is a web browser, or a web application, the proxy code is downloaded from the web server hosting the web service. If the client application is a desktop application. The proxy code has to be generated in advance and resides on the client.
Since web services reside on the web server and are accessed by clients using Http (SOAP is also using Http to send back and forth XML packets) which is a stateless protocol, maintaining state between method calls is possible through application or session objects.
The client of a web service should be able to locate (discovery) and find out the prototypes of methods (description) in web service. For this purpose, the web service provides a discovery file and a description (wsdl format) file that lists all the public methods and their prototypes in XML format. Web services can be registered with a UDDI (Universal Description, Discovery and Integration) server which can act as the yellow pages for web services.
The web service methods can use any .Net primitive data type (e.g., int, float, Double, string, DateTime, Decimal, Boolean, Object) in parameters or return values. Arrays and ArrayLists can also be used. User defined classes or structures used in parameters or return types require special XML streaming in using SOAP.
Web Services in .Net have a file extension of .asmx. It is possible to type all the class code in an asmx file using a simple editor like notepad. Visual Studio uses the code behind technique for creating web services. The benefit of the code behind technique in general is that it separates the user interface from the underlying code. However, in case of web services, since there is no user interface, the code behind technique has the advantage that the class code is precompiled in advance. It is possible to create a web service where all the code is written in the .asmx file which will get compiled on its first access by the user.
A web service class is usually derived from System.Web.Services.WebService class. Each public method of the web service class that needs to be made available as a web service will be marked with an attribute of [WebMethod].
Creating a simple Web Service Using C#:
Create an ASP.NET Web Service type project. Name the project StInfo.
Change the default name of the file from Service1.asmx to StInfo.asmx. Web services in .Net have files with an extension of .asmx.
Type the following code in the StInfo.asmx.cs file.
using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Web;
using System.Web.Services;
namespace StInfo
{
///
/// Summary description for Service1.
///
public class StInfo : System.Web.Services.WebService
{
string [,] students =
{
{"062987","Andrew", "Anserson","3.45","CS"},
{"062988","Jessica", "Johnson","3.75","CS"},
{"062989","Monica", "Marker","3.15","EE"},
{"062990","Michael", "Jordan","2.45","MBA"},
{"062991","Sally", "Simpson","3.12","EE"},
{"062987","Mark", "Mathews","2.85","CS"},
{"062987","Sara", "Sorenson","3.52","MBA"}
};
public StInfo()
{
//CODEGEN: This call is required by the ASP.NET Web Services Designer
InitializeComponent();
}
#region Component Designer generated code
//Required by the Web Services Designer
private IContainer components = null;
///
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
///
private void InitializeComponent(){ }
protected override void Dispose( bool disposing )
{
if(disposing && components != null)
{
components.Dispose();
}
base.Dispose(disposing);
}
#endregion
[WebMethod]
public string GetFirstName(string StID)
{
// return first name for a given student ID
for (int i = 0; i < students.GetLength(0);i++)
{
if (String.Compare(StID,students[i,0],true) == 0)
return students[i,1];
}
return "Student does not exist with this ID";
}
[WebMethod]
public string GetLastName(string StID)
{
// return first name for a given student ID
for (int i = 0; i < students.GetLength(0);i++)
{
if (String.Compare(StID,students[i,0],true) == 0)
return students[i,2];
}
return "Student does not exist with this ID";
}
[WebMethod]
public float GetGPA(string StID)
{
// return first name for a given student ID
for (int i = 0; i < students.GetLength(0);i++)
{
if (String.Compare(StID,students[i,0],true) == 0)
return float.Parse(students[i,3]);
}
return 0;
}
[WebMethod]
public string GetMajor(string StID)
{
// return first name for a given student ID
for (int i = 0; i < students.GetLength(0);i++)
{
if (String.Compare(StID,students[i,0],true) == 0)
return students[i,4];
}
return "Student does not exist with this ID";
}
[WebMethod]
public string[] GetAllStudents()
{
// return first name for a given student ID
string [] sts = new string[students.GetLength(0)];
for (int i = 0; i < students.GetLength(0);i++)
{
sts[i]=students[i,0];
}
return sts;
}
}
}
Even though the Visual Studio does not show the contents of the asmx file, you can view it by opening it in notepad. For example, the contents of StInfo.asmx look as:
<%@ WebService Language="c#" Codebehind="StInfo.asmx.cs" Class="StInfo.StInfo" %>
As you can see, the main job of the asmx file is to indicate that first of all, it is a web service using C# and to identify the code behind file (StInfo.asmx.cs in above case).
Choose Build->Build Solution to compile the web service. If there are no errors, you can test the web service by either choosing Debug-> Start, or directly typing HYPERLINK "http://localhost/StInfo/StInfo.asmx" http://localhost/StInfo/StInfo.asmx in the browser.
You will see the following screen.
If you click on the link GetLastName, you will see the following page which has a simple form that can be filled out to find out the student last name corresponding to the student ID (the form uses the Http GET method).
If you type 62990 and click on invoke, you will get the following result.
Similarly, you can test the GetAllStudents link. When you click on the invoke button, you will see the following result.
The page that shows the Invoke button also gives hints about how you can use the SOAP or Http POST protocols to invoke the web service methods, and the general form of the request and response headers for these protocols. For example, to invoke the GetLastName method using SOAP, the format of request response headers is:
SOAP
The following is a sample SOAP request and response. The placeholders shown need to be replaced with actual values.
POST /stinfo/stinfo.asmx HTTP/1.1
Host: localhost
Content-Type: text/xml; charset=utf-8
Content-Length: length
SOAPAction: "http://tempuri.org/GetLastName"
string
HTTP/1.1 200 OK
Content-Type: text/xml; charset=utf-8
Content-Length: length
string
To use POST method for invoking the GetLastName method, the format of request response headers is:
The following is a sample HTTP POST request and response. The placeholders shown need to be replaced with actual values.
POST /stinfo/stinfo.asmx/GetLastName HTTP/1.1
Host: localhost
Content-Type: application/x-www-form-urlencoded
Content-Length: length
StID=string
HTTP/1.1 200 OK
Content-Type: text/xml; charset=utf-8
Content-Length: length
string
Using POST method to Invoke the Web Service Methods:
Using notepad, create a simple ASP page as shown below.
<% @LANGUAGE=VBSCRIPT %>
<% 'StInfoPost.asp ASP client for StInfo web service %>
StInfo Client
StInfo Web Service Client Using Post
You can test the invocation of GetLastName and the GetAllStudents web service methods by clicking on the two buttons in the form.
Using SOAP to Invoke the Web Service:
The easiest way to using SOAP to invoke a web service is by creating a proxy dll and registering it with the client application. The proxy does all the marshalling and serialization/deserialization of parameters and returns to/from a web service. The proxy has all the exposed methods in a web service and to the client it appears as if the call to the web service method is a local call. The proxy makes all the appropriate SOAP packets when invoking a method on the web service.
ASP.NET Web Service Client:
To understand the creation and use of proxy dlls, create a new ASP.NET web application. Name the project StInfoClient.
Change the name of Webform1.aspx file to StInfoClient.aspx.
Creating the Proxy: The wsdl utility program can generate the source code for the proxy of a given web service. For example, the following command will generate a file called StInfo.cs for the StInfo web service.
The above StInfo.cs file needs to be compiled into a dll and copied into the bin directory of the client ASP.NET application.
The easiest solution to the above tasks is to create a bat file and run the bat file to generate the source code for the proxy dll, compile the dll and move it to the bin directory.
Using Notepad, create a bat file called StInfoProxy.bat. I stored the file in the c:\InetPub\wwwroot\StInfoClient\ProxyCode folder.
rem StInfoProxy.bat
rem bat file for creating StInfo Proxy dll
rem
rem generate the proxy source code
wsdl /l:cs http://localhost/StInfo/StInfo.asmx?wsdl
rem
rem compile the proxy dll
csc /out:StInfoProxy.dll /t:library /r:System.dll,System.web.dll,System.web.services.dll StInfo.cs
rem
rem Copy the dll to the bin directory of Client App
copy StInfoProxy.dll c:\InetPub\wwwroot\StInfoClient\bin
Run the above file from the DOS or Command prompt (from the ProxyCode folder).
Back to the ASP.NET Client Project StInfoClient:
By right clicking on the references link in the StInfoClient project (in the project explorer), add a reference to the newly created StInfoProxy.dll in the bin folder of this project. Because this dll uses the WebServices, you also need to add a reference to the System.Web.Services.dll.
Add a Label to the StInfoClient.aspx page. Set its text property to Client for StInfo Web Service.
Add a panel to the StInfoClient.aspx file. Put a table on it with 4 rows and two columns. Add labels and text boxes to each of the table cells as shown below. Give IDs of txtFirstName, txtLastName, txtMajor and txtGPA to the four text boxes in the panel. Also add a drop down list box server control to the page. Give it a name of ddlStudents. Set the AutoPostBack property of ddlStudents to true.
The important code in the StInfoClient.aspx.cs file that you will be typing is shown below in bold.
using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Web;
using System.Web.SessionState;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;
namespace StInfoClient
{
///
/// Summary description for WebForm1.
///
public class WebForm1 : System.Web.UI.Page
{
StInfo st = new StInfo();
protected System.Web.UI.WebControls.Label Label1;
protected System.Web.UI.WebControls.Label Label2;
protected System.Web.UI.WebControls.Panel pnlStudent;
protected System.Web.UI.WebControls.Label Label3;
protected System.Web.UI.WebControls.Label Label4;
protected System.Web.UI.WebControls.TextBox txtMajor;
protected System.Web.UI.WebControls.TextBox txtGPA;
protected System.Web.UI.WebControls.Label Label5;
protected System.Web.UI.WebControls.Label Label6;
protected System.Web.UI.WebControls.TextBox txtFirstName;
protected System.Web.UI.WebControls.DropDownList ddlStudents;
protected System.Web.UI.WebControls.TextBox txtLastName;
private void Page_Load(object sender, System.EventArgs e)
{
// Put user code to initialize the page here
if (!IsPostBack)
{
string []sts = st.GetAllStudents();
ddlStudents.Items.Clear();
foreach(string SID in sts)
ddlStudents.Items.Add(SID);
}
}
#region Web Form Designer generated code
override protected void OnInit(EventArgs e)
{
// CODEGEN: This call is required by the ASP.NET Web Form Designer.
InitializeComponent();
base.OnInit(e);
}
///
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
///
private void InitializeComponent()
{
this.ddlStudents.SelectedIndexChanged += new System.EventHandler(this.ddlStudents_SelectedIndexChanged);
this.Load += new System.EventHandler(this.Page_Load);
}
#endregion
private void ddlStudents_SelectedIndexChanged(object sender, System.EventArgs e)
{
txtFirstName.Text = st.GetFirstName(ddlStudents.SelectedItem.Text);
txtLastName.Text = st.GetLastName(ddlStudents.SelectedItem.Text);
txtMajor.Text = st.GetMajor(ddlStudents.SelectedItem.Text);
txtGPA.Text = (st.GetGPA(ddlStudents.SelectedItem.Text)).ToString();
}
}
}
Build the StInfoClient application. If there are no errors, you can test the web service client by typing HYPERLINK "http://localhost/StInfoClient/StInfoClient.aspx" http://localhost/StInfoClient/StInfoClient.aspx in the browser, as shown below.
You can find out the description of the methods in a web service by typing the following URL: HYPERLINK "http://localhost/StInfo/StInfo.asmx?wsdl" http://localhost/StInfo/StInfo.asmx?wsdl
Theoretically speaking, a web service created in .Net does not have to inherit from the WebService class. However, the benefit of inheriting from the WebService class is that you get access to the Application, Session, User and Context objects. If you do not inherit from the WebService class, then you can still get access to the above objects from the HttpContext.Current collection, e.g., you can add two methods to the StInfo web service class. These methods are called GetUniversityName and SetUniversityName and both use the Session object. Note that the session object is not enabled by default for a web service method, you have to enable it explicitly for the method by the WebMethod property of EnableSession=true.
[WebMethod(EnableSession=true)]
public string GetUniversityName()
{
// if this class is not derived from WebService class
// then we can obtain Session, Application, User
// and Context objects as shown below
// Application object is HttpApplicationState class
// Note that you have to explicitly enable
// the session for a web service as the default
// is disabled for a method.
try
{
System.Web.SessionState.HttpSessionState sess;
sess = HttpContext.Current.Session;
if (sess["UniversityName"] != null)
return sess["UniversityName"].ToString();
else
return "No University Name Set in Session";
}
catch (Exception e)
{
return e.Message;
}
}
[WebMethod(EnableSession=true)]
public string SetUniversityName(string uName)
{
// if this class is not derived from WebService class
// then we can obtain Session, Application, User
// and Context objects as shown below
// Note that you have to explicitly enable
// the session for a web service as the default
// is disabled for a method.
try
{
System.Web.SessionState.HttpSessionState sess;
sess = HttpContext.Current.Session;
Session["UniversityName"]=uName;
return ("University Name Set to " + uName);
}
catch(Exception e)
{ return e.Message;}
}
The User object in HttpContext.Current.User can be used for authenticating the user of a web service as we will see later on.
Note that you can debug a web service in Visual Studio.Net just like you debug any windows or console application. If you compile a web service and if it generates a run time error (e.g., a reference to an object whose value you are trying to read may be null), no XML packet is returned by the web service. For example, if you do not enable the session state in the previous example and choose Debug->Start from the Visual Studio, then as you invoke the method that is using the session object, you will get an Internal server error message. You can improve upon this error condition determination by enclosing the method code in a try catch bock and returning e.Message from the method, as was done for the GetUniversityName and the SetUniversityName methods in the previous example.
Properties of the WebService Attribute:
You can optionally specify some properties for the web service such as the description, the language being used, the name of the web service and the namespace for the web service. All of these properties are optional. If the name property is not specified, the web service uses the name of the class name implementing the web service. If you do not specify a namespace, a value of HYPERLINK "http://tempuri.org" http://tempuri.org is used for its namespace.
Example:
[WebService(Description=Student Information Web Service, Name=StudentInfo, NameSpace=www.AusifM.com)]
If the above properties are used, the client code will use a class name of StudentInfo rather than the original class name StInfo that was used in the C# code when building the web service.
Properties of WebMethod Attribute:
WebMethod attribute has a few different properties that you can specify e.g., the EnableSession=true (default is false), or BufferResponse=false (default is true). You can specify multiple properties for a WebMethod all separated by commas e.g.,
[WebMethod(EnableSession=true, BufferResponse=false)]
You may want to set the BufferResponse to false if the response being generated by the method is large and time consuming to generate. Once set to false, the response is sent in 16KB chunks.
Another property that you can specify for the WebMethod attribute is CacheDuration e.g., [WebMethod(CacheDuration=45)] will allow the methods return to be cached for 45 seconds. The caching of results can greatly improve the performance of a web service as when a method is invoked with identical values of the parameters as compared to a previously cached result, the cached value(s) are returned immediately without having to call and execute the method. The key here is that there should be exact match of parameters and the code in the method should not be sensitive to time of call. Setting the CacheDuration to a high value can be a burden on the memory resources of the web server computer on which the web service is running. The default value for the CacheDuration is 0.
The Description property for the WebMethod attribute allows you to specify a helpful description for a WebMethod e.g.,
[WedMethod(Description=This method lets you set a University Name)]
Since C# allows method overloading, more than one method can have the same name. This can cause confusion to the consumers of the web service. This can be taken care of by assigning an alias for each one of these overloaded names by the MessageName property e.g.,
// ComputeAvg method computes avg of three integers
[WebMethod(MessageName=ComputeAvgInt)]
// ComputeAvg method computes avg of three doubles
[WebMethod(MessageName=ComputeAvgDouble)]
The clients of the Web service will invoke the ComputeAvgInt and the ComputeAvgDouble method names.
A WebMethod can participate in a transaction provided it itself is the root object of the transaction (i.e., the method originates the transaction). We can indicate that the WebMethod must start a new transaction by specifying a TransactionOption value of Required or RequiresNew. Both of these values have the same effect i.e., at the beginning of the method, a transaction is started and it is committed at the end of the method. If the method throws an exception during its execution, the transaction is rolled back. To use transactions, you must add a reference to the System.EnterpriseServices.dll.
[WebMethod(TransactionOption=TransactionOption.RequiresNew)]
Theoretically, you can set the transaction option value to either Disabled, NotSupported or Supported. However, as mentioned earlier, since the method can only start a transaction, these three values cause a transaction to not to be started.
Here is a summary of the webmethod attributes and their brief descriptions.
BufferResponse - The BufferResponse property of the WebMethod attribute enables buffering of responses for an XML Web service method.
CacheDuration - The CacheDuration property of the WebMethod attribute enables caching of the results for an XML Web service method.
Description - The Description property of the WebMethod attribute supplies a description for an XML Web service method that will appear on the Service help page.
EnableSession - The EnableSession property of the WebMethod attribute enables session state for an XML Web service method.
MessageName - The MessageName property of the WebMethod attribute enables the XML Web service to uniquely identify overloaded methods using an alias.
TransactionOption - The TransactionOption property of the WebMethod attribute enables the XML Web service method to participate as the root object of a transaction.
Web Service methods can also return DataSets as the DataSet is already represented in XML in ADO.NET.
Example:
Add two more methods to the StInfo class called ListAllCourses and GetEnrollment. Both of these methods will return DataSet. Since the access to the database is done via OleDb, you will need to add the following statement in your class.
using System.Data.OleDb;
[WebMethod(EnableSession=true)]
public DataSet ListAllCourses()
{
OleDbConnection cn = new OleDbConnection();
cn.ConnectionString = "Provider = Microsoft.JET.OLEDB.4.0;" +
@"data source = d:\\databasecsharp\\stdb.mdb";
cn.Open();
OleDbDataAdapter dAdapt = new OleDbDataAdapter(
"SELECT Courses.CourseNum from Courses", cn);
DataSet myDS = new DataSet("Course");
dAdapt.Fill(myDS, "Course");
cn.Close();
return myDS;
}
[WebMethod(EnableSession=true)]
public DataSet GetEnrollment(string cnum)
{
OleDbConnection cn = new OleDbConnection();
cn.ConnectionString = "Provider = Microsoft.JET.OLEDB.4.0;" +
@"data source = d:\\databasecsharp\\stdb.mdb";
cn.Open();
string str = "SELECT FirstName,LastName, Enrollment.CourseNum, Courses.CourseName ";
str += "FROM Students,Enrollment,Courses WHERE Students.StudentID=Enrollment.StudentID ";
str += "and Enrollment.CourseNum='" + cnum + "' and Courses.CourseNum=Enrollment.CourseNum";
OleDbDataAdapter dAdapt = new OleDbDataAdapter(str, cn);
DataSet myDS = new DataSet("enroll");
dAdapt.Fill(myDS,"enroll");
cn.Close();
return myDS;
}
Build the solution for the web service StInfo. Since the StInfo has been changed, we will need to rebuilt the proxy for the StInfoClient. From the Command prompt, move to the c:\InetPub\wwwroot\StInfoClient\ProxyCode directory and type the bat file name (StInfoProxy.bat) that we had prepared earlier. Remember this bat file creates the proxy source code, then compiles it into a dll and moves it to the bin directory of the StInfoClient web application.
Open the StInfoClient project from VS.Net and add a hyper link to a page called StDbMainWSC.aspx in the StInfoClient.aspx file. Then right click on the project name and add a web form to the project called StDbMainWSC.aspx. Add a drop down list with an ID of ddlCourse, a button with a Text property of Find Enrollment from Web Service StInfo and an ID of btnFindEnrollment. Also add a DataGrid to the page with a default ID of DataGrid1. The important code in the StDbMain.aspx is shown below.
namespace StInfoClient
{
///
/// Summary description for StDbMainWSC.
///
public class StDbMainWSC : System.Web.UI.Page
{
StInfo st = new StInfo();
protected System.Web.UI.WebControls.Label Label1;
protected System.Web.UI.WebControls.DropDownList ddlCourse;
protected System.Web.UI.WebControls.DataGrid DataGrid1;
protected System.Web.UI.WebControls.Button btnFindEnrollment;
private void Page_Load(object sender, System.EventArgs e)
{
// Put user code to initialize the page here
if(!IsPostBack)
{
DataSet ds = st.ListAllCourses();
ddlCourse.DataSource = ds.Tables["Course"];
ddlCourse.DataTextField="CourseNum";
ddlCourse.DataBind();
}
}
private void btnFindEnrollment_Click(object sender, System.EventArgs e)
{
string cnum = ddlCourse.SelectedItem.ToString();
DataSet ds = st.GetEnrollment(cnum);
DataGrid1.DataSource = ds.Tables["enroll"].DefaultView;
DataGrid1.DataBind();
}
You can test the web service client application by building it and typing its URL in the browser.
Using Custom Data Types in WebMethods:
Custom class objects and data structures can be used as parameters in web methods provided there is an XML serialization possible for the parameter. C# and the .Net make it very easy to accomplish this through a simple attribute that you specify in the beginning of your custom class decelaration:
[XmlInclude(typeof(your class name)]
You also need to use the namespace System.Xml.Serializtion for the above attribute to work. The other important thing to note is that the data members of your custom class should either be declared public or be accessible through public properties, otherwise the .Net run time cannot marshal the class object properly as it needs to read all the object data.
Example: Let us add a C# class to the StInfo project. Name the class Stu. It has the following code in it.
using System;
using System.Xml.Serialization;
namespace StInfo
{
///
/// Summary description for Stu.
///
[XmlInclude(typeof(Stu))]
public class Stu
{
protected string fname; // first name for student
public string Fname
{
get {return fname;}
set {fname = value; }
}
protected string lname; // last name for student
public string Lname
{
get { return lname; }
set { lname = value; }
}
protected string id;
public string ID
{
get { return id; }
set { id = value; }
}
protected float gpa;
public float GPA
{
get { return gpa; }
set { gpa = value; }
}
protected string major;
public string Major
{
get { return major; }
set { major = value; }
}
}
}
Add a data member called StuArray of type ArrayList to the StInfo class as shown below.
ArrayList StuArray = new ArrayList();
Modify the constructor to initialize the StuArray as:
public StInfo()
{
//CODEGEN: This call is required by the ASP.NET Web Services Designer
InitializeComponent();
for (int i = 0; i < students.GetLength(0);i++)
{
Stu st = new Stu();
st.ID = students[i,0];
st.Fname = students[i,1];
st.Lname = students[i,2];
st.GPA = float.Parse(students[i,3]);
st.Major = students[i,4];
StuArray.Add(st);
}
}
Now add two more methods (WebMethods) to the StInfo class with the following code
[WebMethod]
public ArrayList GetAllStus()
{
return StuArray;
}
[WebMethod]
public Stu GetStu(string StID)
{
foreach (Stu st in StuArray)
{
if (st.ID == StID)
return st;
}
return new Stu(); // empty object
}
Build the project again.
You can test these two methods by first asking for HYPERLINK "http://localhost/StInfo/StInfo.asmx" http://localhost/StInfo/StInfo.asmx in your web browser and clicking on the GetAllStus and GetStu hyperlinks and invoking the methods to see if they return valid XML data back to the browser.
If the above test works as expected, you can modify the StInfoClient web application by adding another web form to the application. Name the form StCustom.aspx. Add a panel to the StCustom.aspx file. Put a table on it with 4 rows and two columns. Add labels and text boxes to each of the table cells as shown below. Give IDs of txtFirstName, txtLastName, txtMajor and txtGPA to the four text boxes in the panel. Also add a drop down list box server control to the page. Give it a name of ddlStudents. Set the AutoPostBack property of ddlStudents to true. Add a data member to the StCustom class (code behind file for StCustom.aspx)
StInfo st = new StInfo();
Write the following code in the Page Load event of the StCustom class.
private void Page_Load(object sender, System.EventArgs e)
{
if (!IsPostBack)
{
object [] stoba = st.GetAllStus(); // ArrayList
// returned as Object Array
ddlStudents.Items.Clear();
foreach(object obj in stoba)
ddlStudents.Items.Add(((Stu)obj).ID);
}
}
Write the SelectedIndexChanged event for the ddlStudents list box by double clicking on the list box. Type the following code in the handler.
private void ddlStudents_SelectedIndexChanged(object sender, System.EventArgs e)
{
Stu stu = st.GetStu(ddlStudents.SelectedItem.Text);
txtFirstName.Text = stu.Fname;
txtLastName.Text = stu.Lname;
txtMajor.Text = stu.Major;
txtGPA.Text = stu.GPA.ToString();
}
Note that the client can use the Stu class as the proxy has already created the appropriate information in it once we added the [XmlInclude(typeof(Stu))] attribute to it.
You can add a hyperlink to the StCustom.aspx in the StInfoClient.aspx page. Build the StInfoClient application and test it in the browser.
Invoking WebMethods in an Asynchronous Manner:
So far our client code has been using the synchronous method in invoking WebMethods in the web service via the proxy. The proxy generated code has provision for asynchronous method calls as well where the client code does not block. Once the web method returns, it calls back on one of the functions in the proxy letting the client know that the results from method call have come back. Obviously asynchronous method calls are ideal where for those methods which have long time consuming code in them. The proxy code contains two additional methods for each WebMethod to allow asynchronous calls. These are BeginMethodName( ) and EndMethodName (e.g., BeginGetStudent and EndGetStudent for the GetStudent WebMethod). The client calls the BeginMethodName to start an asynchronous call. Once the WebMethod finishes, it calls back the EndMethodName function on the proxy. The callback architecture obviously requires the use of delegates that will be invoked when the asynchronous call completes.
The steps involved in asynchronous WebMethod calls are:
Create a private reference of type AsyncCallback in your web form class.
In the constructor of the form create the AsynCallback object and pass the function name of the actual callback function you would like to be invoked (this is same as a delegate creation).
The prototype of your callback function that will be used as a delegate is:
public void AsyncCallback(IAsyncResult ar);
The IAsynResult allows the client to obtain the results from the WebMethod call.
The typical code to get the result in the delegate function is:
String s = proxy.EndMethodName(ar);
// if String type is the result from method call
Example:
We will simulate the effect of a long method call by putting Sleep in a method on purpose. Add the following line in the StInfo class code.
using System.Threading;
Add two web methods to the StInfo web service project. The code for the new web method is shown below.
[WebMethod(EnableSession=true)]
public DataSet ListAllCoursesSlow()
{
OleDbConnection cn = new OleDbConnection();
cn.ConnectionString = "Provider = Microsoft.JET.OLEDB.4.0;" +
@"data source = d:\\databasecsharp\\stdb.mdb";
cn.Open();
OleDbDataAdapter dAdapt = new OleDbDataAdapter(
"SELECT Courses.CourseNum from Courses", cn);
DataSet myDS = new DataSet("Course");
dAdapt.Fill(myDS, "Course");
cn.Close();
Thread.Sleep(8000); // wait 8 secs. on purpose
return myDS;
}
[WebMethod(EnableSession=true)]
public DataSet GetEnrollmentSlow(string cnum)
{
OleDbConnection cn = new OleDbConnection();
cn.ConnectionString = "Provider = Microsoft.JET.OLEDB.4.0;" +
@"data source = d:\\databasecsharp\\stdb.mdb";
cn.Open();
string str =
"SELECT FirstName,LastName,Enrollment.CourseNum,Courses.CourseName ";
str += "FROM Students,Enrollment,Courses WHERE Students.StudentID=Enrollment.StudentID ";
str += "and Enrollment.CourseNum='" + cnum + "' and Courses.CourseNum=Enrollment.CourseNum";
OleDbDataAdapter dAdapt = new OleDbDataAdapter(str, cn);
DataSet myDS = new DataSet("enroll");
dAdapt.Fill(myDS,"enroll");
cn.Close();
Thread.Sleep(5000);
return myDS;
}
The only special change made in the above two web methods is the addition of Thread.Sleep call so that the methods return the data sets after a while.
Build the StInfo web service. Then rebuild the proxy by running the StInfoProxy.bat file from the client application folder.
Open the StInfoClient project and add a web form called StAsyncTest.aspx to it. Add a drop down list box with an ID of ddlCourse and a data grid control with a default ID of DataGrid1 to the form. Set the AutoPostBack property of the list box to true. The important code in the StAsyncTest code behind file is shown below.
using System.Threading;
namespace StInfoClient
{
public class StAsyncTest : System.Web.UI.Page
{
StInfo st = new StInfo();
int flag; // to allow us to wait to get data from web service
private AsyncCallback myCoursesCB;
private AsyncCallback myEnrollmentCB;
protected System.Web.UI.WebControls.DropDownList ddlCourse;
protected System.Web.UI.WebControls.DataGrid DataGrid1;
protected System.Web.UI.WebControls.Label Label1;
public StAsyncTest()
{
flag = 0;
myCoursesCB = new AsyncCallback(this.OnCBListAllCoursesSlow);
myEnrollmentCB = new AsyncCallback(this.OnCBGetEnrollmentSlow);
}
private void Page_Load(object sender, System.EventArgs e)
{
// Put user code to initialize the page here
if(!IsPostBack)
{
//DataSet ds = st.ListAllCourses();
//ddlCourse.DataSource = ds.Tables["Course"];
//ddlCourse.DataTextField="CourseNum";
//ddlCourse.DataBind();
st.BeginListAllCoursesSlow(myCoursesCB,0);
while (flag != 1)
Thread.Sleep(300); // wait till data received
flag = 0;
}
}
public void OnCBListAllCoursesSlow(IAsyncResult ar)
{
DataSet ds = st.EndListAllCoursesSlow(ar);
ddlCourse.DataSource = ds.Tables["Course"];
ddlCourse.DataTextField="CourseNum";
ddlCourse.DataBind();
flag = 1; // carry on to send the results to browser
}
public void OnCBGetEnrollmentSlow(IAsyncResult ar)
{
DataSet ds = st.EndGetEnrollmentSlow(ar);
DataGrid1.DataSource = ds.Tables["enroll"].DefaultView;
DataGrid1.DataBind();
flag = 1; // carry on to send data back
}
private void ddlCourse_SelectedIndexChanged(object sender, System.EventArgs e)
{
//string cnum = ddlCourse.SelectedItem.ToString();
//DataSet ds = st.GetEnrollment(cnum);
//DataGrid1.DataSource = ds.Tables["enroll"].DefaultView;
//DataGrid1.DataBind();
string cnum = ddlCourse.SelectedItem.ToString();
st.BeginGetEnrollmentSlow(cnum,myEnrollmentCB,0);
while (flag != 1)
Thread.Sleep(300);// wait till data recvd,check after 0.3 secs
flag = 0;
}
}
}
Note that if the client code has to wait to receive the data from the web service, the data binding to controls (or even updating simple controls once the data arrives will not work). To make the data binding work properly, the above code uses a flag and a wait loop in the place where the asynchronous call to BeginMethodName is made. The flag is set by the EndMethodName handler.
You can add a hyperlink to the StAsyncTest.aspx in the StInfoClient.aspx file. Build the StInfoClient and test it as:
You will notice that each time a selection is made from the list box, the data grid update is delayed by more than five seconds due to the Sleep call in the GetEnrollmentSlow( ) web method.
PAGE
PAGE 6
9 D H O 0 1 / 4 B G \ a n s ĵĥĥĥĈĈ h$9 5OJ QJ \aJ h$9 B*CJ OJ QJ aJ ph h$9 B*CJ OJ QJ aJ ph h$9 5CJ OJ QJ \aJ h$9 CJ OJ QJ aJ h$9 B*CJ OJ QJ aJ ph j h$9 U
h$9 >*CJ h$9 6]
h$9 CJ h$9 / 9
% m \ R O Y ^ _
!
&