The previous
chapter provided an overview of web services, which are basically web
applications with no user interface. Web services allow an
application to make method calls against another application over the
Internet as though it were calling a localdll
.
There are two broad aspects to web service development: creating the web service and consuming the web service. This chapter covers the creation of web services. Chapter 17 covers the creation of web service client applications, also known as consumers.
Although a web service has no user interface and no visual component, the architecture and files used to create a web service are very similar to those used to create a web page, which are described in detail in Chapter 2 through Chapter 6. Some of these similarities include:
Full implementation of the .NET Framework and Common Language Runtime (CLR), including the object-oriented architecture and all the base class libraries, as well as features such as caching, state, and data access
Nearly identical file and code structures
All source code files in plain text, which can be created in any text editor
Full support by Visual Studio .NET, with all its productivity features, including IntelliSense, code completion, and integrated debugging
Configurable on a global or application-wide basis using plain text configuration files
That said, web pages and web services are conceptually very different. A web page entails an interface designed for interaction with a person sitting at a web browser. A web service, on the other hand, consists only of methods, some of which are available for remote calls by client applications.
A web service can be coded inline, in a single file with an extension of .asmx . Alternatively, the application logic of the web service can be segregated into a code-behind file, which is the default behavior of Visual Studio .NET. While code-behind is generally preferred, especially for large projects, both methods will be demonstrated in this chapter.
The rationale for code-behind is that it provides a clean separation between the presentation and programmatic portions of an application. While this is extremely useful in the development of web pages, it is not really relevant to web services. However, since code-behind is the default coding technique for Visual Studio .NET (which offers so many productivity enhancements), code-behind becomes the de facto preferred technique. In addition, code-behind confers a performance advantage over inline code because the code-behind class must be precompiled for web services, while the .asmx file is compiled into a class the first time it is run.
Whether using an inline or code-behind architecture, the .asmx file is the target entered into the
browser for testing or referenced by the utilities that create the
proxy
dll
. (Recall from Chapter 15
that the client application actually makes calls to a proxy
dll
. Creation of this proxy
dll
will be described in
detail in the next chapter.)
As a first step in understanding how web services work, we will create a simple web service, called StockTicker, using any favorite text editor. In subsequent sections of this chapter, we will create the same web service using Visual Studio .NET.
The StockTicker web service will expose two web methods:
If this web service were an actual production program, the data returned would be fetched from a live database. In order not to confuse web service issues with data access issues, for this example the data will be stored in a two-dimensional array of strings. For a complete discussion of accessing a database, please see Chapter 11 and Chapter 12.
A single file will be created. The VB.NET version will be called vbStockTicker.asmx and is shown in Example 16-1. The C# version will be called csStockTicker.asmx and is shown in Example 16-2.
The .asmx file contains the entire web service inline. It defines a namespace called ProgAspNet, and creates a class called csStockTicker for the C# version, or vbStockTicker for the VB.NET version. The class instantiates and fills an array to contain the stock data, then creates the two WebMethods that comprise the public aspects of the web service.
If you’re familiar with web page code, you may notice in glancing over Example 16-1 and Example 16-2 that the code for a web service is virtually identical to the code in a code-behind page for an equivalent web page. There are some differences, however, which are highlighted in the code examples and are discussed in the sections following the examples.
Example 16-1. StockTicker web service in VB.NET, vbStockTicker.asmx
<%@ WebService Language="VB" Class="ProgAspNet.vbStockTicker" %>
Option Strict On Option Explicit On Imports System Imports System.Web.Services namespace ProgAspNet public class vbStockTickerinherits System.Web.Services.WebService
' Construct and fill an array of stock symbols and prices. ' Note: the stock prices are as of 7/4/01. dim stocks as string(,) = _ { _ {"MSFT","Microsoft","70.47"}, _ {"DELL","Dell Computers","26.91"}, _ {"HWP","Hewlett Packard","28.40"}, _ {"YHOO","Yahoo!","19.81"}, _ {"GE","General Electric","49.51"}, _ {"IBM","International Business Machine","112.98"}, _ {"GM","General Motors","64.72"}, _ {"F","Ford Motor Company","25.05"} _ } dim i as integer<WebMethod>
public function GetPrice(StockSymbol as string) as Double ' Given a stock symbol, return the price. ' Iterate through the array, looking for the symbol. for i = 0 to stocks.GetLength(0) - 1 ' Do a case-insensitive string compare. if (String.Compare(StockSymbol, stocks(i,0), true) = 0) then return Convert.ToDouble(stocks(i,2)) end if next return 0 End Function<WebMethod>
public function GetName(StockSymbol as string) as string ' Given a stock symbol, return the name. ' Iterate through the array, looking for the symbol. for i = 0 to stocks.GetLength(0) - 1 ' Do a case-insensitive string compare. if (String.Compare(StockSymbol, stocks(i,0), true) = 0) then return stocks(i,1) end if next return "Symbol not found." End Function End Class End namespace
Example 16-2. StockTicker web service in C#, csStockTicker.asmx
<%@ WebService Language="C#" Class="ProgAspNet.csStockTicker" %>
using System; using System.Web.Services; namespace ProgAspNet { public class csStockTicker: System.Web.Services.WebService
{ // Construct and fill an array of stock symbols and prices. // Note: the stock prices are as of 7/4/01. string[,] stocks = { {"MSFT","Microsoft","70.47"}, {"DELL","Dell Computers","26.91"}, {"HWP","Hewlett Packard","28.40"}, {"YHOO","Yahoo!","19.81"}, {"GE","General Electric","49.51"}, {"IBM","International Business Machine","112.98"}, {"GM","General Motors","64.72"}, {"F","Ford Motor Company","25.05"} };[WebMethod]
public double GetPrice(string StockSymbol) // Given a stock symbol, return the price. { // Iterate through the array, looking for the symbol. for (int i = 0; i < stocks.GetLength(0); i++) { // Do a case-insensitive string compare. if (String.Compare(StockSymbol, stocks[i,0], true) == 0) return Convert.ToDouble(stocks[i,2]); } return 0; }[WebMethod]
public string GetName(string StockSymbol) // Given a stock symbol, return the name. { // Iterate through the array, looking for the symbol. for (int i = 0; i < stocks.GetLength(0); i++) { // Do a case-insensitive string compare. if (String.Compare(StockSymbol, stocks[i,0], true) == 0) return stocks[i,1]; } return "Symbol not found."; } } }
The first difference between a
web service and a web page is seen in
the first line of Example 16-1 and Example 16-2. A normal .aspx file will have a
Page
directive as its first line, but a web
service has a
WebService
directive, as reproduced here in VB.NET:
<%@ WebService Language="VB" Class="ProgAspNet.vbStockTicker" %>
and here in C#:
<%@ WebService Language="C#" Class="ProgAspNet.csStockTicker" %>
The WebService
directive is required of all web
services. Like all directives, it has the syntax:
<%@ DirectiveName Attribute="value" %>
where there can be multiple attribute/value pairs. The order of the attribute/value pairs does not matter.
The WebService
directive’s
Language
attribute specifies the language
used in the web service. Legal values include C#
,
VB
, and JS
for C#, VB.NET, and
JScript.NET, respectively. The value is not case-sensitive.
The Language
attribute is not required. If it is
missing, the default value is C#.
The WebService
directive’s
Class
attribute specifies the
name of the class implementing the web service. The
Class
attribute is required. The class specified
can reside in the
.asmx
file or in a
separate file, a technique referred to as
code-behind.
If the implementing class resides in a separate file, then that file
must be compiled and the resulting dll
placed in
the in
subdirectory under the directory where
the .asmx
file resides. This will be
demonstrated shortly.
Notice that in the code listings in Example 16-1 and Example 16-2, a namespace,
ProgAspNet, has been defined. In order to specify the implementing
class contained in this namespace fully,
the namespace is prepended to the class name in the
WebService
directive.
Strictly speaking, the namespace containing the WebService class does
not need to be prepended to the inherited class name, since the
System.Web.Services namespace is referenced with the
Imports
keyword in VB.NET and the
using
keyword in C#. The longer syntax is used to
clarify the relationships.
In the StockTicker web service in Example 16-1 and Example 16-2, the StockTicker class (the vbStockTicker class for VB.NET and the csStockTicker class for C#) inherits from the WebService class.
Deriving from the WebService class is optional, but it offers several advantages. The principal one is that you gain access to several common ASP.NET objects, including:
These objects allow the application to take advantage of state management. For a complete discussion of state management, see Chapter 6. State as it pertains specifically to web services will be covered in more detail later in this chapter.
This object is useful for authenticating the caller of a web service. For a complete discussion of security, see Chapter 19.
This object provides access to all HTTP-specific information about the caller’s request contained in the HttpContext class.
Web services have access to the Application object, as do all ASP.NET resources, via the HttpContext object.
So, for example, you could modify Example 16-1 and Example 16-2 to add the web methods shown in Example 16-3 (for VB.NET) and Example 16-4 (for C#) to set and retrieve a value in application state.
Example 16-3. Code modification to vbStockTicker.asmx adding application state
<WebMethod>public sub SetStockExchange(Exchange as string) Application("exchange") = Exchange end sub <WebMethod>public function GetStockExchange( ) as string return Application("exchange").ToString( ) end function
Example 16-4. Code modification to csStockTicker.asmx adding application state
[WebMethod] public void SetStockExchange(string Exchange) { Application["exchange"] = Exchange; } [WebMethod] public string GetStockExchange( ) { return Application["exchange"].ToString( ); }
You could accomplish the same thing without inheriting from System.Web.Services.WebService by using the HttpContext object, as demonstrated in Example 16-5 and Example 16-6.
Example 16-5. Code modification to vbStockTicker.asmx adding application state without inheriting WebService
Option Strict On Option Explicit On Imports SystemImports System.Web
Imports System.Web.Services namespace ProgAspNet public class vbStockTicker . . . <WebMethod>public sub SetStockExchange(Exchange as string)dim app as HttpApplicationState
app = HttpContext.Current.Application
app("exchange") = Exchange end sub <WebMethod>public function GetStockExchange( ) as stringdim app as HttpApplicationState
app = HttpContext.Current.Application
return app("exchange").ToString( ) end function
Example 16-6. Code modification to csStockTicker.asmx adding application state without inheriting WebService
using System;using System.Web;
using System.Web.Services; namespace ProgAspNet { public class csStockTicker . . . [WebMethod] public void SetStockExchange(string Exchange) {HttpApplicationState app;
app = HttpContext.Current.Application;
app["exchange"] = Exchange; } [WebMethod] public string GetStockExchange( ) {HttpApplicationState app;
app = HttpContext.Current.Application;
return app["exchange"].ToString( ); }
Notice that in Example 16-5 and Example 16-6, a reference to System.Web has been added at the top of the listing. Also, the web service class, vbStockTicker or csStockTicker, no longer inherits from the class WebService. Finally, an HttpApplicationState object is declared in order to access the application state.
The main reason you might not want to inherit from WebService is to overcome the limitation imposed by the .NET Framework that a class can only inherit from one other class. It would be very inconvenient to have to inherit from WebService if you also needed to inherit from another class.
As explained previously, a web service is defined by a WebService class. It is not necessary for the WebService class to expose all of its methods to consumers of the web service. Each method you do want to expose must:
As you saw in Example 16-1 and Example 16-2, the syntax for defining a web method is slightly different, depending on the language. In VB.NET, it looks like this:
<WebMethod>public function GetName(StockSymbol as string) as string
and in C# it looks like this:
[WebMethod] public string GetName(string StockSymbol)
The WebMethod
attribute has properties that are used to configure the behavior of
the specific web method. The syntax, again, is language-dependent.
In VB.NET, the syntax is:
<WebMethod(PropertyName
:=value
)> _ public function GetName(StockSymbol as string) as string
and in C# the syntax is:
[WebMethod(PropertyName
=value
)] public string GetName(string StockSymbol)
PropertyName
is a valid property accepted
by the WebMethod
attribute (these are described
below), and value
is the value to be
assigned to that property. Note the colon (:) in VB.NET (which is
standard VB.NET syntax for named arguments), as well as the use of
the line continuation character if the combination of the WebMethod
property and method/function call stretches to more than one line.
Regardless of the language, if there are multiple
WebMethod
properties, separate each property/value
pair with a comma within a single set of parentheses. So, for
example, in VB.NET:
<WebMethod(BufferResponse:=False, Description:="Sample description")>
or in C#:
[WebMethod(BufferResponse=false, Description="Sample description")]
The following sections describe the valid
WebMethod
properties.
By default, ASP.NET buffers the entire response
to a request before sending it from the server to the client. Under
most circumstances, this is the optimal behavior. However, if the
response is very lengthy, you might want to disable this buffering by
setting the WebMethod
attribute’s
BufferResponse property to false
. If set to
false
, the response will be returned to the client
in 16KB chunks. The default value is true
.
For VB.NET, the syntax for BufferResponse is:
<WebMethod(BufferResponse:=False)>
and for C#:
[WebMethod(BufferResponse=false)]
Web services, like web pages, can cache the results returned to clients, as is described fully in Chapter 18. If a client makes a request that is identical to a request made recently by another client, then the server will return the response stored in the cache. This can result in a huge performance gain, especially if servicing the request is an expensive operation, such as querying a database or performing a lengthy computation.
It should be emphasized that in order for the cached results to be
used, the new request must be identical to the previous request. If
the web method has
parameters, the parameter values must be identical. So, for example,
if the GetPrice web method of the StockTicker web service is called
with a value of msft
passed in as the stock
symbol, that result will be cached separately from a request with a
value of dell
passed in. If the web method has
multiple parameters, all the parameter values must be the same as the
previous request for the cached results from that request to be
returned.
The CacheDuration property defines how many seconds after the initial request the cached page is sent in response to subsequent requests. Once this period has expired, a new page is sent. CacheDuration is set to 30 seconds as follows for VB.NET:
<WebMethod(CacheDuration:=30)>
and for C#:
[WebMethod(CacheDuration=30)]
The default value for CacheDuration is zero, which disables caching of results.
If the web method is returning data that does not change much -- say a query against a database that is updated once hourly -- then the cache duration can be set to a suitably long value, say 1800 (e.g., 30 minutes). You could even set the cache duration in this case to 3600 (60 minutes) if the process of updating the database also forces the cache to refresh by making a call to the WebMethod after the database is updated.
On the other hand, if the data returned is very dynamic, then you would want to set the cache duration to a very short time, or to disable it altogether. Also, if the web method does not have a relatively finite range of possible parameters, then caching may not be appropriate.
The WebMethod
attribute’s
Description property allows you to
attach a descriptive string to a web method. This description will appear on
the web service help page when you test the web service in a browser.
Also, the WebMethod
description will be made
available to the consumer of the web service, as will be seen in
Chapter 17. When a representation of the web
service is encoded into the SOAP message that is sent out to
potential consumers, the WebMethod
Description
property is included.
The syntax for Description is as follows for VB.NET:
<WebMethod(Description:="Returns the stock price for the input stock symbol.")>
and for C#:
[WebMethod(Description="Returns the stock price for the input stock symbol.")]
The WebMethod
attribute’s
EnableSession property, if set to
true
, enables session state for the
web method. The default value is
false
. (For a general discussion of session state,
see Chapter 6.)
If the EnableSession property is set to true
and
the web service inherits from the WebService class (see earlier
sections for a description of inheriting from the WebService class),
the session state collection can be accessed with the
WebService
.Session property. If the web service
does not inherit from the WebService
class, then
the session state collection can be accessed directly from
HttpContext.Current.Session.
As an example, the code in Example 16-7 and Example 16-8 adds a per-session hit counter to the ongoing StockTicker web service example.
Example 16-7. HitCounter WebMethod in VB.NET
<WebMethod(Description:="Number of hits per session.", EnableSession:=true)> _ public function HitCounter( ) as integer if Session("HitCounter") is Nothing then Session("HitCounter") = 1 else Session("HitCounter") = CInt(Session("HitCounter")) + 1 end if return CInt(Session("HitCounter")) end function
Example 16-8. HitCounter WebMethod in C#
[WebMethod(Description="Number of hits per session.", EnableSession=true)] public int HitCounter( ) { if (Session["HitCounter"] == null) { Session["HitCounter"] = 1; } else { Session["HitCounter"] = ((int) Session["HitCounter"]) + 1; } return ((int) Session["HitCounter"]); }
Enabling session state adds additional overhead to the application. By leaving session state disabled, performance may be improved.
In Example 16-7 and Example 16-8, it would probably be more efficient to use a member variable to maintain the hit counter, rather than session state, since the examples as written entail two reads of the session state and one write, while a member variable would entail only one read and one write. However, session state is often useful as a global variable that can exceed the scope of a member variable.
Session state is implemented via HTTP cookies, so if the transport mechanism is something other than HTTP, say SMTP, then the session state functionality will not be available.
It is possible to have more than one method or function in your web service class with the same name. They are differentiated by their signature -- the quantity, data type, and order of their parameters. Each unique signature can be called independently. This is called method overloading, and can cause some confusion.
The WebMethod
attribute’s
MessageName property eliminates confusion caused by overloaded
methods. It allows you to assign a unique alias to a method
signature. When this method is referred to in
SOAP messages, the MessageName will
be used, and not the method name.
Consider Example 16-9 and Example 16-10. In both examples, two methods are added to the StockTicker web service, both named GetValue. They differ in that one accepts only a single string parameter, while the other takes both a string and an integer.
Example 16-9. GetValue WebMethods in VB.NET
' WebMethod generates an error <WebMethod(Description:="Returns the value of the users holdings " & _ " in a specified stock symbol.")> _ public Function GetValue(StockSymbol as string) as double ' Put code here to get the username of the current user, fetch both ' the current price of the specified StockSymbol and number of shares ' held by the current user, multiply the two together, and return the ' result. return 0 end Function ' WebMethod generates an error <WebMethod(Description:="This method returns the value of a " & _ "specified number of shares in a specified stock symbol.")> _ public Function GetValue(StockSymbol as string, NumShares as integer) as double ' Put code here to get the current price of the specified StockSymbol, ' multiply it times NumShares, and return the result. return 0 end function
Example 16-10. GetValue WebMethods in C#
// WebMethod generates an error [WebMethod(Description="T Returns the value of the users holdings " + " in a specified stock symbol.")] public double GetValue(string StockSymbol) { /* Put code here to get the username of the current user, fetch both the current price of the specified StockSymbol and number of shares held by the current user, multiply the two together, and return the result. */ return 0; } // WebMethod generates an error [WebMethod(Description="This method returns the value of a " + "specified number of shares in a specified stock symbol.")] public double GetValue(string StockSymbol, int NumShares) { /* Put code here to get the current price of the specified StockSymbol, multiply it times NumShares, and return the result. */ return 0; }
If you attempt to test either of these in a browser, it will return an error similar to that shown in Figure 16-1.
If you modify the code in Example 16-9 and Example 16-10 by adding the MessageName property, highlighted in Example 16-11 and Example 16-12, then everything compiles nicely.
Example 16-11. GetValue WebMethods with MessageName in VB.NET
<WebMethod(Description:="Returns the value of the users holdings " & _ "in a specified stock symbol.", _MessageName:="GetValuePortfolio")> _
public Function GetValue(StockSymbol as string) as double ' Put code here to get the username of the current user, fetch ' both the current price of the specified StockSymbol and number ' of shares held by the current user, multiply the two together, ' and return the result. return 0 end Function <WebMethod(Description:="Returns the value of a specified number " & _ "of shares in a specified stock symbol.", _MessageName:="GetValueStock")> _
public Function GetValue(StockSymbol as string, NumShares as integer) as double ' Put code here to get the current price of the specified StockSymbol, ' multiply it times NumShares, and return the result. return 0 end function
Example 16-12. GetValue WebMethods with MessageName in C#
[WebMethod(Description="Returns the value of the users holdings " + "in a specified stock symbol.",MessageName="GetValuePortfolio")]
public double GetValue(string StockSymbol) { /* Put code here to get the username of the current user, fetch both the current price of the specified StockSymbol and number of shares held by the current user, multiply the two together, and return the result. */ return 0; } [WebMethod(Description="Returns the value of a specified " + "number of shares in a specified stock symbol.",MessageName="GetValueStock")]
public double GetValue(string StockSymbol, int NumShares) { /* Put code here to get the current price of the specified StockSymbol, multiply it times NumShares, and return the result. */ return 0; }
Now consumers of the web service will call GetValuePortfolio or GetValueStock rather than GetValue.
To see the impact of this change, examine the
WSDL, which is the description of the web
service used by clients of the web service. You can look at the WSDL
by entering the URL for the .asmx
file in a browser, followed by ?WSDL
. If you do
that for Example 16-11 or Example 16-12, then search for the first occurrence of
GetValuePortfolio, you will see something like Figure 16-2.
You can see that the section defined by the tag:
<operation name="GetValue">
is present twice. However, in the first instance, the method name
used within the operation
section of the document
is GetValuePortfolio, and in the second
instance it is GetValueStock.
ASP.NET web methods can participate in transactions (see Chapter 12 for more details on transactions), but only if the transaction originates in that web method. In other words, the web method can only participate as the root object in a transaction. This means that a consuming application cannot call a web method as part of a transaction and have that web method participate in the transaction. However, if the web method starts its own transaction, which then fails, the calling transaction will also fail.
The WebMethod
attribute’s
TransactionOption property specifies whether or not a web method
should start a transaction. There are five legal values of the
property, all contained in the TransactionOption enumeration.
However, due to the fact that a web method transaction must be the
root object, there are only two different behaviors: either a new
transaction is started or it is not.
These values in the TransactionOption enumeration are used throughout the .NET Framework. However, in the case of web services, the first three values produce the same behavior, and the last two values produce the same behavior.
The three values of TransactionOption that do not start a new transaction are:
The two values that do start a new transaction are:
In order to use transactions in a web service, you must take several additional steps:
Add a reference to
System.EnterpriseServices.dll
.
In Visual Studio .NET, this is done through the Solution Explorer or the Project/Add Reference... menu item. If using the Solution Explorer, right-click on References and select Add References.... In either case, you get the dialog box shown in Figure 16-3. Click on the desired component in the list, and then click OK.
Add the System.EnterpriseServices namespace to the web service. This
is done with the Imports
statement in VB.NET and
the using
statement in C#. In VB.NET, this would
be:
Imports System.EnterpriseServices
and in C#:
using System.EnterpriseServices;
Add a TransactionOption property with a value of
RequiresNew
to the WebMethod
attribute. (The value of Required
will have the
same effect.)
The syntax for TransactionOption
is as follows for
VB.NET:
<WebMethod(TransactionOption:=TransactionOption.RequiresNew)>
and for C#:
[WebMethod(Description=TransactionOption.RequiresNew)]
If there are no exceptions thrown by the web method, then the transaction will automatically commit unless the SetAbort method is explicitly called. If any exceptions are thrown, the transactionwillautomatically abort.
The WebService
attribute (not to be confused with
the WebMethod
attribute or the
WebService
directive) allows additional
information to be added to a web service. The
WebService
attribute is optional.
The syntax for a WebService
attribute is dependent
on the language used. For VB. NET, it is:
<WebService(PropertyName
:=value
)>public class vbStockTicker( )
or:
<WebService(PropertyName
:=value
)> _ public class vbStockTicker( )
and for C# it is:
[WebService(PropertyName
=value
)] public class csStockTicker( )
PropertyName
is a valid property accepted
by the WebService
attribute (these are described
below), and value
is the value to be
assigned to that property. Note the colon (:) in VB. NET (which is
standard VB.NET syntax for named arguments), as well as the use of
the line continuation character if the combination of the
WebService
attribute and the class declaration
stretches to more than one line.
If there are multiple WebService properties, separate each property/value pair with a comma within a single set of parenthesis. So for example, in VB. NET:
<WebService (Description:="A stock ticker using VB.NET.", _ Name:="StockTicker", _ Namespace:="www.LibertyAssociates.com")> _
or in C#:
[WebService (Description="A stock ticker using C#.", Name="StockTicker", Namespace="www.LibertyAssociates.com")]
There are three possible properties for a
WebService
attribute, described in the next three sections.
The WebService
attribute’s
Description property assigns a
descriptive message to the web service. As with the
WebMethod
attribute’s Description
property, the WebService
description will be
displayed in the web service help page when the page is tested in a
browser, and also made available in the SOAP message to any potential
consumers of the web service.
The name of a web service is displayed at the top of a web service help page when the page is tested in a browser. It is also made available to any potential consumers of the service.
By default, the name of a web service is the name of the class
implementing the web service. The WebService
attribute’s Name property allows the name to be
changed. If you glance back at the syntax given in Section 16.1.5, you’ll
notice that the two language implementations of the stock ticker web
service have different class names, but the code specifies that both
will now be seen as StockTicker.
Each web service has an
XML namespace associated
with it. An XML namespace allows you to create names in an XML
document that are uniquely identified by a
Uniform
Resource Identifier (URI). The web service is described using a WSDL
document, which is defined in XML. It is important that each
WebService
attribute has a unique XML namespace
associated with it to prevent name conflicts.
The default URI of a web service is http://tempuri.org/. Typically, you will define a new namespace using a unique name, such as a firm’s web site. Although the XML namespace often looks like a web site, it does not need to be a valid URL.
In the syntax given in Section 16.1.5, notice that the Namespace property is set to the web site, www.LibertyAssociates.com.
ASP.NET web services can use any CLR-supported primitive data type as either a parameter or a return value. Table 16-1 summarizes the valid types.
Table 16-1. CLR-supported primitive data types
VB.NET |
C# |
Description |
---|---|---|
Byte |
byte |
1-byte unsigned integer |
Short |
short |
2-byte signed integer |
Integer |
int |
4-byte signed integer |
Long |
long |
8-byte signed integer |
Single |
float |
4-byte floating point |
Double |
double |
8-byte floating point |
Decimal |
decimal |
16-byte floating point |
Boolean |
bool |
True/False |
Char |
char |
Single Unicode character |
String |
string |
Sequence of Unicode characters |
DateTime |
DateTime |
Represents dates and times |
Object |
object |
Any type |
In addition to the primitive data types, you can also use arrays and ArrayLists of the primitive types. Since data is passed between a web service and its clients using XML, whatever is used as either a parameter or return value must be represented in an XML schema, or XSD.
The examples shown so far in this chapter have used simple primitive types, such as strings and numbers, as parameters and return values. You could also use an array of simple types, as in the code shown here in C#:
[WebMethod] public string[] GetArray( ) { string[] TestArray = {"a","b","c"}; return TestArray; }
The main limitation of using arrays, of course, is that you must know the number of elements at design time. If the number of elements is dynamic, then an ArrayList is called for. If an ArrayList is used in the web service, it is converted to an object array when the web service description is created. The client proxy will return an array of objects, which will then have to be converted to an array of strings.
The ArrayList is contained within the System.Collections namespace.
To use an ArrayList, you must include the proper reference, with the
Imports
keyword in VB.NET, as in the following:
Imports System.Collections
or the using
keyword in C#:
using System.Collections;
The code in Example 16-13 and Example 16-14 contains a web method called GetList. It takes a string as a parameter. This match string is then compared with all the firm names in the data store (the array defined at the top of the web service class shown in Example 16-1 and Example 16-2), and the web service returns all the firm names that contain the match string anywhere within the firm name.
Example 16-13. GetList WebMethod in VB.NET
<WebMethod(Description:="Returns all the stock symbols whose firm " & _ "name matches the input string as *str*.")> _ public function GetList(MatchString as string) as ArrayList dim a as ArrayList = new ArrayList( ) ' Iterate through the array, looking for matching firm names. for i = 0 to stocks.GetLength(0) - 1 ' Search is case sensitive. if stocks(i,1).ToUpper().IndexOf(MatchString.ToUpper( )) >= 0 then a.Add(stocks(i,1)) end if next a.Sort( ) return a end function
Example 16-14. GetList WebMethod in C#
[WebMethod(Description="Returns all the stock symbols whose firm " + "matches the input string as *str*.")] public ArrayList GetList(string MatchString) { ArrayList a = new ArrayList( ); // Iterate through the array, looking for matching firm names. for (int i = 0; i < stocks.GetLength(0); i++) { // Search is case sensitive. if ( stocks[i,1].ToUpper().IndexOf(MatchString.ToUpper( )) >= 0) a.Add(stocks[i,1]); } a.Sort( ); return a; }
The web method in Example 16-13 and Example 16-14 first instantiates a new ArrayList, then iterates through the store of firms. This time the web method uses the IndexOf method of the String class. This IndexOf method does a case-sensitive search in a string, looking for the match string. If it finds a match, it returns the index of the first occurrence. If it does not find a match, it returns -1. In order to implement a case-insensitive search, the code first converts both the MatchString and the firm name to uppercase.
If the IndexOf method finds a match, the web method adds the firm name to the ArrayList. The firm name is contained in the second field of the array record, i.e., the field with index 1 (remember that array indices are zero-based). After completing the search, the web method then sorts the ArrayList before returning it to the client.
In order to test this, enter the following URL into a browser. For VB.NET, use:
http://localhost/ProgAspNet/vbStockTicker.asmx
and for C# use:
http://localhost/ProgAspNet/csStockTicker.asmx
In either case, you will get a page with each of the web methods as a link, similar to the page shown in Figure 15-2 in Chapter 15. Clicking on GetList will bring up a page for testing the method. If you enter “or”, as shown in Figure 16-4, you will see the results that would be returned to a client, shown in Figure 16-5. Notice that in the test output, Ford comes before General Motors, even though their order is reversed in the input data. That is a result of sorting the ArrayList prior to return.
Web services can also use user-defined classes and structs as either parameters or return types. The rules to remember are:
To demonstrate the use of classes with web services, add the class definitions shown in Example 16-15 and Example 16-16 to the Stock Ticker being built in this chapter.
Example 16-16. Class Definitions in C#
public class Stock { public string StockSymbol; public string StockName; public double Price; public StockHistory[] History = new StockHistory[2]; } public class StockHistory { public DateTime TradeDate; public double Price; }
The first class definition, Stock
, is comprised of
two strings, a double, and an array of type StockHistory. The
StockHistory
class consists of a date, called the
TradeDate, and the stock price on that date.
In a real-world application, you would never design a stock ticker
like this. Instead of the Stock
class having an
array with a fixed number of stock history records, you would
probably want to use a collection. You would also store the data in a
database, rather than filling an array. That way, the number of
history records returned by the web method would be dependent upon
the number of records returned from the database query. In the
example here, the data is hard-coded in an array in order to focus on
the topic of using classes with web services.
The web method shown in Example 16-17 and
Example 16-18 uses the Stock
class
to return stock history data for the stock symbol passed in to it.
Example 16-17. GetHistory WebMethod in VB.NET
<WebMethod(Description:="Returns stock history for " & _ "the stock symbol specified.")> _ public function GetHistory(StockSymbol as string) as Stockdim stock as new Stock
' Iterate through the array, looking for the symbol. for i = 0 to stocks.GetLength(0) - 1 ' Do a case-insensitive string compare. if (String.Compare(StockSymbol, stocks(i,0), true) = 0) then stock.StockSymbol = StockSymbol stock.StockName = stocks(i,1) stock.Price = Convert.ToDouble(stocks(i,2)) ' Populate the StockHistory data.stock.History(0) = new StockHistory( )
stock.History(0).TradeDate = Convert.ToDateTime("5/1/2001") stock.History(0).Price = Convert.ToDouble(23.25)stock.History(1) = new StockHistory( )
stock.History(1).TradeDate = Convert.ToDateTime("6/1/2001") stock.History(1).Price = Convert.ToDouble(28.75) return stock end if next stock.StockSymbol = StockSymbol stock.StockName = "Stock not found." return stock end function
Example 16-18. GetHistory WebMethod in C#
[WebMethod(Description="Returns stock history for " + "the stock symbol specified.")] public Stock GetHistory(string StockSymbol) {Stock stock = new Stock( );
// Iterate through the array, looking for the symbol. for (int i = 0; i < stocks.GetLength(0); i++) { // Do a case-insensitive string compare. if (String.Compare(StockSymbol, stocks[i,0], true) == 0) { stock.StockSymbol = StockSymbol; stock.StockName = stocks[i,1]; stock.Price = Convert.ToDouble(stocks[i,2]); // Populate the StockHistory data.stock.History[0] = new StockHistory( );
stock.History[0].TradeDate = Convert.ToDateTime("5/1/2001"); stock.History[0].Price = Convert.ToDouble(23.25);stock.History[1] = new StockHistory( );
stock.History[1].TradeDate = Convert.ToDateTime("6/1/2001"); stock.History[1].Price = Convert.ToDouble(28.75); return stock; } } stock.StockSymbol = StockSymbol; stock.StockName = "Stock not found."; return stock; }
In Example 16-17 and Example 16-18, notice that each class is instantiated before it can be used. Iterating over the array of stocks finds the data to return. The class variables are populated from the array, and then the class itself is returned. If the stock symbol is not found, a message is placed in a convenient field of the stock class and that is returned.
Since a web service can return any data that can be encoded in an XML file, it can also return a DataSet, since that is represented internally as XML by ADO.NET. A DataSet is the only type of ADO.NET data store that can be returned by a web service.
As an exercise, we will modify an example shown previously in Chapter 11 to return a DataSet from the Bugs database used in that chapter.
Although this sample web method does not really conform to the ongoing Stock Ticker example, we will use it for convenience.
Add the namespaces shown in Example 16-19 and Example 16-20 to the Stock Ticker example.
Example 16-19. Namespace references for DataSet in VB.NET
Imports System.Data Imports System.Data.SqlClient
Example 16-20. Namespace references for DataSet in C#
using System.Data; using System.Data.SqlClient;
Now add the web method shown in Example 16-21 (VB.NET) and Example 16-22 (C#). This web method, called GetDataSet, takes no parameters and returns a DataSet object consisting of all the BugIDs and Descriptions from the Bugs database.
Example 16-21. GetDataSet in VB.NET
<WebMethod(Description:="Returns a data set from the Bugs database.")> _ public function GetDataset( ) as DataSet dim connectionString as string dim commandString as string dim dataAdapter as SqlDataAdapter dim dataSet as new DataSet( ) ' connect to the Bugs database connectionString = "YourServer; uid=sa; pwd=YourPassword; database= ProgASPDotNetBugs " ' get records from the Bugs table commandString = "Select BugID, Description from Bugs" ' create the data set command object and the DataSet dataAdapter = new SqlDataAdapter(commandString, connectionString) ' fill the data set object dataAdapter.Fill(dataSet,"Bugs") return dataSet end function
Example 16-22. GetDataSet in C#
[WebMethod(Description="Returns a data set from the Bugs database.")] public DataSet GetDataset( ) { // connect to the Bugs database string connectionString = "server=YourServer; uid=sa; pwd= YourPassword; database= ProgASPDotNetBugs "; // get records from the Bugs table string commandString = "Select BugID, Description from Bugs"; // create the data set command object and the DataSet SqlDataAdapter dataAdapter = new SqlDataAdapter(commandString, connectionString); DataSet dataSet = new DataSet( ); // fill the data set object dataAdapter.Fill(dataSet,"Bugs"); return dataSet; }
The code is copied nearly verbatim from the Page_Load method in the code in Example 11-2 and Example 11-3 and was described fully in that chapter. The important thing to note here is that a DataSet object is created from a query, then returned by the web method to the consuming client.
Example 16-23 and Example 16-24 show the completed source code for the example web service that we’ve developed in this chapter up to this point. The code is included here to show how all the snippets of code presented so far fit together.
Example 16-23. vbStockTicker.asmx in VB.NET
<%@ WebService Language="VB" Class="ProgAspNet.vbStockTicker" %> Option Strict On Option Explicit On Imports System Imports System.Web.Services Imports System.Collections Imports System.Data Imports System.Data.SqlClient namespace ProgAspNet <WebService (Description:="A stock ticker using VB.NET.", _ Name:="StockTicker", _ Namespace:="www.LibertyAssociates.com")> _ public class vbStockTicker inherits System.Web.Services.WebService ' Construct and fill an array of stock symbols and prices. ' Note: the stock prices are as of 7/4/01. dim stocks as string(,) = _ { _ {"MSFT","Microsoft","70.47"}, _ {"DELL","Dell Computers","26.91"}, _ {"HWP","Hewlett Packard","28.40"}, _ {"YHOO","Yahoo!","19.81"}, _ {"GE","General Electric","49.51"}, _ {"IBM","International Business Machine","112.98"}, _ {"GM","General Motors","64.72"}, _ {"F","Ford Motor Company","25.05"} _ } dim i as integer public class Stock public StockSymbol as string public StockName as string public Price as double public History(2) as StockHistory end class public class StockHistory public TradeDate as DateTime public Price as double end class <WebMethod(Description:="Returns stock history for " & _ "the stock symbol specified.")> _ public function GetHistory(StockSymbol as string) as Stock dim stock as new Stock ' Iterate through the array, looking for the symbol. for i = 0 to stocks.GetLength(0) - 1 ' Do a case-insensitive string compare. if (String.Compare(StockSymbol, stocks(i,0), true) = 0) then stock.StockSymbol = StockSymbol stock.StockName = stocks(i,1) stock.Price = Convert.ToDouble(stocks(i,2)) ' Populate the StockHistory data. stock.History(0) = new StockHistory( ) stock.History(0).TradeDate = Convert.ToDateTime("5/1/2001") stock.History(0).Price = Convert.ToDouble(23.25) stock.History(1) = new StockHistory( ) stock.History(1).TradeDate = Convert.ToDateTime("6/1/2001") stock.History(1).Price = Convert.ToDouble(28.75) return stock end if next stock.StockSymbol = StockSymbol stock.StockName = "Stock not found." return stock end function <WebMethod(Description:="Returns all the stock symbols whose " & _ "firm name matches the input string as *str*.")> _ public function GetList(MatchString as string) as ArrayList dim a as ArrayList = new ArrayList( ) ' Iterate through the array, looking for matching firm names. for i = 0 to stocks.GetLength(0) - 1 ' Search is case sensitive. if stocks(i,1).ToUpper().IndexOf(MatchString.ToUpper( )) _ >= 0 then a.Add(stocks(i,1)) end if next a.Sort( ) return a end function <WebMethod(Description:="Returns the stock price for the " & _ "input stock symbol.", _ CacheDuration:=20)> _ public function GetPrice(StockSymbol as string) as Double ' Given a stock symbol, return the price. ' Iterate through the array, looking for the symbol. for i = 0 to stocks.GetLength(0) - 1 ' Do a case-insensitive string compare. if (String.Compare(StockSymbol, stocks(i,0), true) = 0) then return Convert.ToDouble(stocks(i,2)) end if next return 0 End Function <WebMethod(Description:="Returns the firm name for the input " & _ "stock symbol.", _ CacheDuration:=86400)> _ public function GetName(StockSymbol as string) as string ' Given a stock symbol, return the name. ' Iterate through the array, looking for the symbol. for i = 0 to stocks.GetLength(0) - 1 ' Do a case-insensitive string compare. if (String.Compare(StockSymbol, stocks(i,0), true) = 0) then return stocks(i,1) end if next return "Symbol not found." End Function <WebMethod(Description:="Sets the stock exchange for the " & _ "application.")> _ public sub SetStockExchange(Exchange as string) Application("exchange") = Exchange end sub <WebMethod(Description:="Gets the stock exchange for the " & _ "application. It must previously be set.")> _ public function GetStockExchange( ) as string return Application("exchange").ToString( ) end function <WebMethod(Description:="Number of hits per session.", _ EnableSession:=true)> _ public function HitCounter( ) as integer if Session("HitCounter") is Nothing then Session("HitCounter") = 1 else Session("HitCounter") = CInt(Session("HitCounter")) + 1 end if return CInt(Session("HitCounter")) end function <WebMethod(Description:="Returns the value of the users " & _ "holdings in a specified stock symbol.", _ MessageName:="GetValuePortfolio")> _ public Function GetValue(StockSymbol as string) as double ' Put code here to get the username of the current user, fetch ' both the current price of the specified StockSymbol and number ' of shares held by the current user, multiply the two together, ' and return the result. return 0 end Function <WebMethod(Description:="Returns the value of a specified " & _ "number of shares in a specified stock symbol.", _ MessageName:="GetValueStock")> _ public Function GetValue(StockSymbol as string, _ NumShares as integer) as double ' Put code here to get the current price of the specified ' StockSymbol, multiply it times NumShares, and return ' the result. return 0 end function <WebMethod(Description:="Returns a data set from the Bugs " & _ "database.")> _ public function GetDataset( ) as DataSet dim connectionString as string dim commandString as string dim dataAdapter as SqlDataAdapter dim dataSet as new DataSet( ) ' connect to the Bugs database connectionString = "server=Ath13; uid=sa; pwd=stersol; " & _ "database=Bugs" ' get records from the Bugs table commandString = "Select BugID, Description from Bugs" ' create the data set command object and the DataSet dataAdapter = new SqlDataAdapter(commandString, connectionString) ' fill the data set object dataAdapter.Fill(dataSet,"Bugs") return dataSet end function End Class End namespace
Example 16-24. csStockTicker.asmx in C#
<%@ WebService Language="C#" Class="ProgAspNet.csStockTicker" %> using System; using System.Web.Services; using System.Collections; using System.Data; using System.Data.SqlClient; namespace ProgAspNet { [WebService (Description="A stock ticker using C#.", Name="StockTicker", Namespace="www.LibertyAssociates.com")] public class csStockTicker : System.Web.Services.WebService { // Construct and fill an array of stock symbols and prices. // Note: the stock prices are as of 7/4/01. string[,] stocks = { {"MSFT","Microsoft","70.47"}, {"DELL","Dell Computers","26.91"}, {"HWP","Hewlett Packard","28.40"}, {"YHOO","Yahoo!","19.81"}, {"GE","General Electric","49.51"}, {"IBM","International Business Machine","112.98"}, {"GM","General Motors","64.72"}, {"F","Ford Motor Company","25.05"} }; public class Stock { public string StockSymbol; public string StockName; public double Price; public StockHistory[] History = new StockHistory[2]; } public class StockHistory { public DateTime TradeDate; public double Price; } [WebMethod(Description="Returns stock history for " + "the stock symbol specified.")] public Stock GetHistory(string StockSymbol) { Stock stock = new Stock( ); // Iterate through the array, looking for the symbol. for (int i = 0; i < stocks.GetLength(0); i++) { // Do a case-insensitive string compare. if (String.Compare(StockSymbol, stocks[i,0], true) == 0) { stock.StockSymbol = StockSymbol; stock.StockName = stocks[i,1]; stock.Price = Convert.ToDouble(stocks[i,2]); // Populate the StockHistory data. stock.History[0] = new StockHistory( ); stock.History[0].TradeDate = Convert.ToDateTime("5/1/2001"); stock.History[0].Price = Convert.ToDouble(23.25); stock.History[1] = new StockHistory( ); stock.History[1].TradeDate = Convert.ToDateTime("6/1/2001"); stock.History[1].Price = Convert.ToDouble(28.75); return stock; } } stock.StockSymbol = StockSymbol; stock.StockName = "Stock not found."; return stock; } [WebMethod(Description="Returns all the stock symbols whose firm " + "name matches the input string as *str*.")] public ArrayList GetList(string MatchString) { ArrayList a = new ArrayList( ); // Iterate through the array, looking for matching firm names. for (int i = 0; i < stocks.GetLength(0); i++) { // Search is case sensitive. if ( stocks[i,1].ToUpper().IndexOf(MatchString.ToUpper( )) >= 0) a.Add(stocks[i,1]); } a.Sort( ); return a; } [WebMethod(Description="Returns the stock price for the input " + "stock symbol.", CacheDuration=20)] public double GetPrice(string StockSymbol) // Given a stock symbol, return the price. { // Iterate through the array, looking for the symbol. for (int i = 0; i < stocks.GetLength(0); i++) { // Do a case-insensitive string compare. if (String.Compare(StockSymbol, stocks[i,0], true) == 0) return Convert.ToDouble(stocks[i,2]); } return 0; } [WebMethod(Description="Returns the firm name for the input " + "stock symbol.", CacheDuration=86400)] public string GetName(string StockSymbol) // Given a stock symbol, return the name. { // Iterate through the array, looking for the symbol. for (int i = 0; i < stocks.GetLength(0); i++) { // Do a case-insensitive string compare. if (String.Compare(StockSymbol, stocks[i,0], true) == 0) return stocks[i,1]; } return "Symbol not found."; } [WebMethod(Description="Sets the stock exchange for the " + "application.")] public void SetStockExchange(string Exchange) { Application["exchange"] = Exchange; } [WebMethod(Description="Gets the stock exchange for the " + "application. It must previously be set.")] public string GetStockExchange( ) { return Application["exchange"].ToString( ); } [WebMethod(Description="Number of hits per session.", EnableSession=true)] public int HitCounter( ) { if (Session["HitCounter"] == null) { Session["HitCounter"] = 1; } else { Session["HitCounter"] = ((int) Session["HitCounter"]) + 1; } return ((int) Session["HitCounter"]); } [WebMethod(Description="Returns the value of the users holdings " + "in a specified stock symbol.", MessageName="GetValuePortfolio")] public double GetValue(string StockSymbol) { /* Put code here to get the username of the current user, fetch both the current price of the specified StockSymbol and number of shares held by the current user, multiply the two together, and return the result. */ return 0; } [WebMethod(Description="Returns the value of a specified " + "number of shares in a specified stock symbol.", MessageName="GetValueStock")] public double GetValue(string StockSymbol, int NumShares) { /* Put code here to get the current price of the specified StockSymbol, multiply it times NumShares, and return the result. */ return 0; } [WebMethod(Description="Returns a data set from the Bugs " + "database.")] public DataSet GetDataset( ) { // connect to the Bugs database string connectionString = "server=Ath13; uid=sa; pwd=stersol; " + "database=Bugs"; // get records from the Bugs table string commandString = "Select BugID, Description from Bugs"; // create the data set command object and the DataSet SqlDataAdapter dataAdapter = new SqlDataAdapter(commandString, connectionString); DataSet dataSet = new DataSet( ); // fill the data set object dataAdapter.Fill(dataSet,"Bugs"); return dataSet; } } }
When you are creating web pages, code-behind allows you to separate application logic from design or user interface (UI) elements.
Code-behind is the default code model of Visual Studio .NET. In fact, it is very inconvenient to use Visual Studio .NET to write any ASP.NET application without using code-behind.
Since web services have no design or UI component, the case for using
code-behind is not quite so compelling. However, there is a
performance benefit to code-behind. As we
will discuss, the class implementing the
code-behind must be compiled into a dll
ahead of
time and made available to the web service. By contrast, the
WebService
class contained in the
.asmx
file, similar to the page class in the
.aspx file, is compiled on the
fly by the .NET Framework the first time the class is called. That
compiled version is then cached on the server for subsequent
requests. For a complete discussion of caching and performance, see
Chapter 18. For now, suffice it to say that the first time a web
service or web page is called, there will be a delay for inline code
while the class is compiled, while a code-behind implementation will
never experience that delay.
It is very easy to convert the Stock Ticker web service created so far in this chapter from an inline code model to a code-behind model. In this section, you will first do code-behind in a text editor, then in Visual Studio .NET.
In
a text editor, create a new file
called
vbStockTickerCodeBehind.asmx
for the VB.NET version, and
csStockTickerCodeBehind.asmx
for the C# version. Each file will consist
of a single line of code, as shown in Example 16-25 and Example 16-26.
Example 16-25. vbStockTickerCodeBehind.asmx
<%@ WebService Language="vb" Class="ProgAspNet.vbStockTicker" %>
Example 16-26. csStockTickerCodeBehind.asmx
<%@ WebService Language="c#" Class="ProgAspNet.csStockTicker" %>
This WebService
directive uses the same attributes for
code-behind as a normal web page, as described in Chapter 6. The Language
attribute specifies the language,
either VB, C#, or JS for VB.NET, C#, or JScript, respectively.
The Class
attribute specifies the
name of the code-behind class that implements the web service. In the
code in Example 16-25 and Example 16-26, the class specified is
ProgAspNet.vbStockTicker or ProgAspNet.csStockTicker, depending on
the language used. These are the fully qualified web service class
names used in the examples in this chapter. The class names
themselves have prepended to them the namespace
ProgAspNet
, from Example 16-1 and Example 16-2.
Herein lies a significant difference between using
code-behind and using inline code. With
code-behind, the code-behind class must be compiled prior to calling
the
.asmx
file (or .aspx file for normal
web pages; it works the same for both web pages and web services).
The compiled dll
then must be placed in a
in
subdirectory directly beneath the directory
containing the .asmx (or
.aspx) file. This is shown
schematically in Figure 16-6, using the names for
the C# implementation.
In order to create the code-behind file, follow these steps:
Save the inline .asmx file being
developed throughout this chapter as either
StockTickerCodeBehind.vb
or
StockTickerCodeBehind.cs
, depending on the
language.
Open this new code-behind file in an editor and delete the first line
in the file, the WebService
directive.
Save the new code-behind file.
The code-behind file can then be compiled into a
dll
. This is done using a language-specific
command from the command prompt.
In order for this (or any other .NET) command line to work, the path must be set to include the executable being called. To do this manually would not be trivial. Instead, there is an item in the Start menu:
ProgramsMicrosoft Visual Studio .NETVisual Studio .NET Tools Visual Studio .NET Command Prompt
that opens a command prompt window (what used to be known as a DOS prompt, for you old-timers) with the proper path set.
First change the current directory of the command window to be the directory containing the code-behind file. The command to do this is something like:
cd projectsprogramming asp.net
The generic syntax for the compiler is:
compilerExe
[parameters
]inputFile.ext
where compilerExe
is either
vbc
for VB.NET or csc
for C#.
This is followed by one or more parameters, which is then followed by
the name of the source code file being compiled.
For VB.NET, use the following single-line command to compile the DLL:
vbc /out:binvbStockTickerCodeBehind.dll /t:library /r:system.dll,system.web. dll,system.web.services.dll, system.data.dll,system.XML.dll StockTickerCodebehind.vb
and for C# use this single-line command:
csc /out:bincsStockTickerCodeBehind.dll /t:library /r:system.dll,system.web. dll,system.web.services.dll StockTickerCodebehind.cs
The command-line compilers have a large number of parameters available to them, three of which are used here. In order to see the complete list of parameters available, enter the following command at the command prompt:
compilerExe
/?
Table 16-2 lists the parameters used in the preceding command lines.
Table 16-2. Parameters used in commands to compile the dll
Parameter |
Short form |
Description |
---|---|---|
Output filename. If not specified, then the output filename is derived from the first source file. | ||
|
Build a library file. Alternative values for target are
| |
|
Reference the specified assembly files. If more than one file, either include multiple reference parameters or separate filenames with commas within a single reference parameter. Be certain not to include any spaces between filenames. |
Notice there is a correlation between the namespaces referenced in the source code and the files referenced in the compile command. Table 16-3 shows the correspondence.
Table 16-3. Correspondence of source code and compiler references
Source code reference |
Compiler reference |
Description |
---|---|---|
System |
|
Supplies fundamental classes and base classes. |
- |
|
Supplies classes and interfaces to enable client/server communications. Not necessary in source code because it is referenced automatically by the ASP.NET runtime. |
System.Web.Services |
|
Classes that enable web services. |
System.Collections |
- |
Provides classes and interfaces used by various collections,
including Arrays and ArrayLists. Not necessary in the compiler
reference because it is included in
|
Once the dll is created and located in the
proper bin
subdirectory (which the previous
command lines do for you), then the .asmx
file
can be tested in a browser or called by a client, just like any
other
.asmx
file.
Visual Studio .NET offers the programmer several advantages over a plain text editor, in addition to automating the creation of code-behind. Among them are color coding of the source code, integrated debugging, IntelliSense, integrated compiling, and full integration with the development environment. Use of the Visual Studio .NET IDE is covered in detail in Chapter 6.
Open Visual Studio .NET to create a web service using code-behind. Click on the New Project button to start a new project. You will be presented with the dialog box shown in Figure 16-7.
You can select a Project Type of the language of your choice. This example will use VB.NET.
Select the ASP.NET web service template.
The default name for the project will be
WebService1
. Change that to
StockTickerVB
, as shown in Figure 16-7. When you click the OK button, Visual Studio
.NET will cook for few a moments, and then it will open in design
mode.
Be careful if using any non-alpha characters in the project name. I originally named this project StockTicker-VB. Visual Studio .NET seemed to accept this, but on moving through the process, the hyphen was converted to an underscore under some circumstances and the project would not compile and run properly.
Pay particular attention to the Solution Explorer, located in the upper right quadrant of the screen by default, and shown in Figure 16-8.
The Solution Explorer shows most, but not all, of the files that comprise the project. In a desktop .NET application (i.e., not an ASP.NET project), all of the files in a project would be located in a subdirectory, named the same as the project name, located under the default project directory. One of these files has an extension of .sln . The .sln file tells Visual Studio .NET what files to include in the project. An associated file has an extension of .suo.
The default project location can be set by selecting Tools → Options... → Environment → Projects and Solutions and changing the directory in the edit box.
When creating an ASP.NET project, either a web page or a web service,
the project directory is still created under the default projects
directory. That directory still contains the
.sln
and .suo
files. If
these files are missing, then Visual Studio .NET cannot open the
project. However, all the other files comprising the project are
contained in the
virtual root directory of the
application, which is a subdirectory with the project name created
under the physical directory corresponding to localhost. On most
machines the physical directory corresponding to
localhost is c:inetpubwwwroot.
There are other files Visual Studio .NET does not display by default. You can force the Solution Explorer to show all files by clicking on the Show All Files icon in the Solution Explorer tool bar. (It is the second icon from the right, just below the word “StockTicker” in Figure 16-8.) Clicking on this icon and expanding all the nodes results in the Solution Explorer shown in Figure 16-9.
Under References, you can see all the namespaces
that are referenced by default. Under bin, you
can see any files contained in that subdirectory. (The IDE
automatically put the dll
and
pdb
files there on startup.)
The rest of the entries in the Solution Explorer are files contained in the virtual root directory of the application. For this application, that virtual root directory will typically be:
c:InetPubwwwrootStockTickerVB
AssemblyInfo.vb
contains versioning information and will
be covered in more detail in Chapter 20.
The global.asax file contains global configuration information for the entire application. It was covered in Chapter 6, and will be covered further in Chapter 20. Notice that it is now apparent that the global.asax file uses code-behind, with the actual Global class contained in the code-behind file (global.asax.vb for VB.NET projects, global.asax.cs for C# projects).
The .resx files are resource files created by the IDE which contain localization information.
The Service1
files contain the actual web service code.
They will be covered shortly.
The StockTickerVB.vsdisco is a discovery file, used by the consumer of the web service. Discovery will be covered in Chapter 17.
Web.config is another configuration file, which was covered in Chapter 6 and will be covered further in Chapter 20.
This leaves the Service1
files. Click on either
the Service1.asmx or the
Service1.asmx.vb files in the
Solution Explorer. Nothing appears in the design window. This is
because Visual Studio .NET displays design objects by default, and
web services do not use any. They are all code.
In order to see the contents of Service1.asmx
,
right-click on the file in the Solution Explorer, select Open
With..., and select Source Code (Text) Editor from the list of
choices. Looking at Service1.asmx, you will see that the file has
a single line of code, shown in Example 16-27.
Example 16-27. Service1.asmx in Visual Studio .NET
<%@ WebService Language="vb" Codebehind="Service1.asmx.vb" Class="StockTickerVB.Service1" %>
Compare this to the WebService directives in Example 16-1 and Example 16-2. The
Codebehind
attribute is used by Visual Studio .NET
to know where to find the code-behind file. The
Class
attribute points to the default code-behind
class defined in the code-behind file.
You can view any of the other files in the Solution Explorer by right-clicking on the file and selecting either Open or View Code, as appropriate. Once a code window is open in the design window, you can switch back and forth among the various files by clicking on the correct tab at the top of the design window.
Click on the Service1.asmx file, right-click, and select View Code from the context-sensitive menu. The contents of the code-behind file, Service1.asmx.vb, are displayed. Notice that there is already code in place, as shown in Figure 16-10.
In addition to all the other infrastructure Visual Studio puts into
the project, it includes the minimum necessary code to implement a
code-behind web service file. The Imports
statement necessary to allow the web service class, Service1, to
derive from the WebService base class is added, and that class is
defined, at least in skeleton form.
The class definition is followed by a collapsed region, indicated by the plus symbol along the left margin, which contains boilerplate code inserted by and necessary to the IDE.
Next comes some commented code demonstrating a very simple web method. You can delete the commented sample code.
Even though you have not yet added any custom code to this project,
you can prove to yourself that this is indeed a valid web service by
clicking on the Start icon, or pressing F5, to compile and run the
web service. After the code-behind class is compiled and
automatically placed in the bin
directory, a
browser will open with the familiar web service test page. However,
since there are not yet any web methods defined, there will be
nothing to test.
Now cut and paste the code from the code-behind file created earlier
in this chapter into the code-behind file in Visual Studio .NET,
Service1.asmx.vb. Be careful not
to duplicate the line importing System.Web.Services. Also, be sure to
put the Option
lines at the very beginning of the
file and the namespace
opening line before the
Service1 class definition with the end namespace
line at the very end of the file.
The beginning of the code-behind file now looks like Figure 16-11.
Notice that the
WebService
attribute’s Description property has been slightly
edited to clearly identify this as coming from Visual Studio .NET,
although the Name property remains unchanged as
“StockTicker.”
Before this will run correctly, the web service file,
Service1.asmx
, must be edited slightly to take
into account the addition of the namespace to the code-behind file.
Switch to that file and edit the Class
attribute
value by including the ProgAspNet namespace as part of the class
name. The contents of the modified Service1.asmx
should now look like Example 16-28.
Example 16-28. Modified Service1.asmx in Visual Studio .NET
<%@ WebService Language="vb" Codebehind="Service1.asmx.vb" Class="StockTickerVB. ProgAspNet.Service1" %>
Now run test the web service by clicking on the Run icon or pressing F5. You should get a browser window looking something like Figure 16-12.
Notice that the name of the web service reflects the name property
specified in the WebService
attribute’s Name property. Also, the new
WebService
attribute’s
Description property is displayed in the test. Everything else is
exactly the same as the web service
createdin
a
text
editor.
18.191.237.201