|
download source code
Introduction
This is the fourth part in a series of hands-on tutorials that will take you through all the steps to build a complete application using the new SQL Server 2005 database and Visual Studio 2005 IDE tools. The tutorial is finished by creating a Web Service to host the data and then consume the Web Service in Windows, Web, and from the Business layer.
The .NET WebLinks project is a collection of Internet sources for information about .NET 2.0, Visual Studio 2005 and SQL Server 2005. Links to these articles are collected into a searchable database and categorized by subject. The project will allow all the .NET enthusiasts to add to the database, so that it will grow to become a very valuable tool for the .NET community.
Part 1 defined the database using SQL Server 2005 and business logic using datasets.
Part 2 is a Windows interface to the database, allowing individuals to import links from their Favorites directory, edit the links and upload to the master database.
Part 3 is a Web based interface to allow the general community access to search the database for the desired links.
Part 4 defines the Web services for the project, how to construct and consume them.
Part 4 - Creating and Consuming Web Services
There are a couple different options when it comes to building Web Services. SQL Server 2005 has the capability of providing its own endpoints to provide a Web Service direct from the database. This may be demonstrated in a future Hands-On-Lab on SQL Server, but this time, use Visual Studio to create the Web Service.
Creating an ASP.NET Web Service
Creating a Web service in Visual Studio is similar to creating a Web page. In this walkthrough, you will create the Web service that will be consumed by both the ASP.NET Web project and the Windows Smart Client project.
Note: VS 2005 has an internal hosting service for development but not to host the service long term, you may need Microsoft Internet Information Services (IIS) installed locally on the hosting server.
Open Visual Studio 2005.
Open the Weblinks solution created in Part 1.
Right click on the solution and click Add, New Web Site.
From the New Web Site dialog box, select ASP.NET Web Service template.
You can choose between using the File System (using VS built-in hosting) or HTTP or FTP (using IIS as the hosting system). In this case, select File System
Click the Browse button and create a folder under the WebLinks solution called WeblinksWS.
-
Select Visual Basic as you language and click OK
If you wish to use IIS instead of the VS hosting system, follow these instructions instead of steps 5-6.
Click the Browse button beside the filename.
Click Local IIS from the left panel.
Click Default Web Site.
Click Create New Web Application (first icon in the upper right corner)
Visual Web Developer creates a new IIS Web application.
Type the name WebLinksWS.
-
Click Open.
The New Web Site dialog box appears, with the name of the new Web site
in the rightmost Location list. The location includes the protocol
(http://) and location (localhost). This indicates that you are working with a local IIS Web site.
In the Language list, select Visual Basic.
-
Click OK.
Visual Web Developer creates the new Web service and opens a new class named Service, which
is the default Web service. However, in the following procedure you will create a new Web service with a
specified name and you will use the Service class only for testing.
Close the Service class.
In Solution Explorer, right-click the Web site name (http://localhost/WebLinksWS), and then click Add New Item.
Under Visual Studio installed templates, click Web Service, and then in the Name box, accept the default of WebLinksWS.ASMX.
-
Make sure that the Place code in separate file check box is selected, and then click Add.
Visual Studio creates a new Web service that is made up of two files. The WebLinksWS.asmx file is the file that can be invoked to call Web service methods, and it points to the code for the Web service. The code itself is in a class file (WebLinksWS.vb) in the App_Code folder. The code file contains a template for a Web service and a sample Web service method that returns "Hello World" as shown here:
Imports System.Web
Imports System.Web.Services
Imports System.Web.Services.Protocols
<WebService(Namespace:="http://tempuri.org/")> _
<WebServiceBinding(ConformsTo:=WsiProfiles.BasicProfile1_1)> _
<Global.Microsoft.VisualBasic.CompilerServices.DesignerGenerated()> _
Public Class Service
Inherits System.Web.Services.WebService
<WebMethod()> _
Public Function HelloWorld() As String
Return "Hello World"
End Function
End Class
Most of the code needed to get data from the dataset has already been written in the WebLinksDataSet. Right-click on the WeblinksWS project and select Add Reference.
In the dialog, select the Projects tab and the Biz project. Click OK to add the reference to the web service project.
-
In the code stub that is built, replace the Hello World example method with the following method. Note that we are using the WebLinksDataSet created in the Biz project and returning it in our web service. This is a shortcut that we will work around later, but if the consumer of the service has the same DataSet, this will work.
Private WithEvents dsWebLinks As Biz.WebLinks
<WebMethod()> _
Public Function GetDataSet() As Biz.WebLinks
dsWebLinks.FillAll()
Return dsWebLinks
End Function
In the above example, the web service is returning the dataset created in the business logic, filled with all the data. This is not a good idea for a couple reasons: it returns much more data than will actually be used and therefore wastes bandwidth; and it is type specific so only users with the exact same .NET dataset will be able to consume it. The more logical way is to provide several different web methods that will meet the needs of the presentation layer as data is displayed to the user, with the minimum amount of web traffic.
There is no real automated way to create these web methods--it will take writing some code. Most of the Data Access logic has already been built by the DataSet in the Business layer, so now we just need to wrap each data access method as a web method. First, instantiate the table adapters as fields in the class to gain access to the Data Access logic.
Dim taTopic As New Biz.WebLinksDataSetTableAdapters.TopicTableAdapter()
Dim taLink As New Biz.WebLinksDataSetTableAdapters.LinkTableAdapter()
Dim taTopicLink As New Biz.WebLinksDataSetTableAdapters.TopicLinkTableAdapter()
Dim taLinkType As New Biz.WebLinksDataSetTableAdapters.LinkTypeTableAdapter()
Each table adapter has specific methods for getting and updating its table. There is at least one GetData method and may have multiple if extra queries have been constructed in the DataSet. For each table that is not read-only, the table adapter will have an Update method which covers Inserting, Editing, and Deleting. By passing a DataTable back to the Web Service with the rows that need updating, you will need fewer Web Methods.
-
Now, add the following code for the web methods:
'Topic Table
<WebMethod()> _
Public Function GetTopic() As Biz.WebLinksDataSet.TopicDataTable
Return taTopic.GetData
End Function
<WebMethod()> _
Public Function GetTopicByTopLevelID(ByVal TopLevelID) _
As Biz.WebLinksDataSet.TopicDataTable
Return taTopic.GetByTopLevelID(TopLevelID)
End Function
<WebMethod()> _
Public Function UpdateTopic(ByVal TopicDUpdates _
As Biz.WebLinksDataSet.TopicDataTable) As Integer
Return taTopic.Update(TopicDUpdates)
End Function
'Link Table
<WebMethod()> _
Public Function GetLinkByLinkID(ByVal LinkID) _
As Biz.WebLinksDataSet.LinkDataTable
Return taLink.GetDataByLinkID(LinkID)
End Function
<WebMethod()> _
Public Function GetLinkByTopicID(ByVal TopicID) _
As Biz.WebLinksDataSet.LinkDataTable
Return taLink.GetDataByTopicID(TopicID)
End Function
<WebMethod()> _
Public Function UpdateLink(ByVal LinkUpdates _
As Biz.WebLinksDataSet.LinkDataTable) As Integer
Return taLink.Update(LinkUpdates)
End Function
'TopicLink Table
<WebMethod()> _
Public Function GetTopicLinkByTopicID(ByVal TopicID) _
As Biz.WebLinksDataSet.TopicLinkDataTable
Return taTopicLink.GetDataByTopicID(TopicID)
End Function
<WebMethod()> _
Public Function UpdateTopicLink(ByVal TopicLinkUpdates _
As Biz.WebLinksDataSet.TopicLinkDataTable) As Integer
Return taTopicLink.Update(TopicLinkUpdates)
End Function
'LinkType Table
<WebMethod()> _
Public Function GetLinkType() As Biz.WebLinksDataSet.LinkTypeDataTable
Return taLinkType.GetData()
End Function
-
One last step may be needed to make this work correctly; the Web.Config in the WeblinksWS project needs to have the connection strings added. Since there is not a properties editor for Web Services like there is on other projects, you will need to edit the Web.Config file manually. Open the App.Config file in the Biz project and copy the ConnectionStrings section and add it to the Web.Config file in the Web Services project.
<connectionStrings>
<add name="MySettings.WebLinksConnectionString"
connectionString="Data Source=.\sqlexpress;Initial Catalog=WebLinks;
Integrated Security=True;Connect Timeout=60"
providerName="System.Data.SqlClient" />
</connectionStrings>
-
Save and close the Web.Config file.
-
Save and Build the project.
Note how structured the code is and consider that any code generator that can read the metadata from a dataset could easily generate this code for you. If you plan to implement a lot of these, you might want to invest in a code generation system such as CodeSmith or My Generation.
-
The last step is to Publish the Web Service so others can use it. Right click on the project and select Publish Website.
The dialog will ask where you want it published and offer the chance to set several parameters.
Click OK and the Web Service will be available to your users.
Consuming Web Services
There are a couple different ways to consume Web Services. Both Windows and Web Interfaces have wizards to add a Web Reference which will grant access to the functions. In the next two sections of this tutorial will show how to add these web references in a Windows and Web interface, but the third section explores a preferred option of calling the Web Service from the Business layer instead. The advantage to this method is that the User Interface will be the same, isolated of how the data is retrieved.
You may follow the first two sections to become familiar with the procedures, but the third section is the one to formally implement so
you may want to skip to there if you're already familiar with the steps involved.
Consuming the Web Service in Windows Forms
You will be consuming the service from both the Smart Client and the Web front ends. In Windows Forms, one of the Data Source types is Web Service which creates a Web Reference and allows the Web Methods to be accessible to return table data.
-
In the Solution Explorer, if you do not already have a Windows project added, right click on the Solution and Add, New Project, Visual Basic, Windows Application. Give it the name Win.
-
Create a new form in the project by right clicking the Win Project and Add, New Item, Windows Form giving it a name of TestWS (or you could just rename the existing Form1 created by VS.)
-
Open the Data Sources panel (from the Data Menu) and right click, Add New Data Source (or again from the Data Menu).
-
Choose the Web Service option and click next.
-
VS will open the Web Reference dialog and step you through adding a new Web Reference
-
Click the "Web Service in this solution" option. If you are using a published web service, use the URL textbox to navigate to the desired web site.
-
Select the WebLinksWS from the WebLinksWS folder.
-
The service gives itself a default name of Localhost if found in the current solution. You can rename it or leave it as the default. Click the Add Reference button to finish
-
When finished, you will have a listing for the new Data Source in the Data Sources panel. Expanding it out will show a copy of the database with the four tables.
-
From the Data Sources window, you can now use these tables to create controls on the form in the same way as normal.
-
As an example, drag the Topic Table to the TestWS form to create a grid.
-
As you can see, the wizard creates an instance of the DataSet, a Binding Source for the Link table, and adds a Binding Navigator all linked to the new grid.
-
Open the Solution Explorer and click the Show All Files Icon at the top. Open the Designer code for the new form and you will see that the dataset that has been instantiated belongs to the web reference rather than from the Biz project.
Friend WithEvents WebLinks As Win.localhost2.WebLinks
-
If you expand the Web Reference, you will see a copy of the DataSet created there. While this makes using Web Services simple, it is not the best practice for developing n-tiered applications. It would be better to use the dataset in the Biz project and let it figure out how to get the data from the Web Service.
-
All that is left to get this to run is to instantiate the web service and make a call to the get data method and merge it into the dataset.
Dim ws As New localhost2.WebLinksWS
Private Sub TestWS_Load(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles MyBase.Load
WebLinksDataSet.Merge(ws.GetTopic())
End Sub
Consuming Web Services from Web Interface
Getting data from a Web Service in a Web application is much the same.
-
In the Web project, right click on the References and Add a New Web Reference. The wizard has the same interface as shown in the previous section.
-
Follow the same steps as in 5-8 in the previous section.
-
You will end up with an App_WebReference to the WebLinksWS service which has reference to the WebLinksDataSet, along with the DISCO and WSDL for the web service.
-
In the code behind the Default page, add a global declaration and instantiation of the web service.
Dim ws As New localhost.WebLinksWS
-
Now instead of getting the data from the Biz dataset, use the instance of the dataset from the Localhost instead. The problem here is that the dataset from the web service will not convert easily to the dataset from the Biz project. Therefore, you must first cast it as a normal DataTable and then back to the typed data table from the dataset.
dt = CType(CType(ws.GetTopic(), DataTable), Biz.WebLinksDataSet.TopicDataTable)
-
The other option is to convert all instances of the dataset from the Biz dataset to the WS dataset. The problem with that is the WS version only has the schema and does not carry the special code written in the partial class.
-
In the Design view of the form, select the first ObjectDataSource and right click, Configure.
-
From the list of business objects, choose localhost.WebLinksDataSet.
-
On the next screen, select the localhost Select method of GetTopicLinkByTopicID.
-
On the last screen, select to link the data to the SelectedValue of the Treeview controls.
-
Finish the wizard and the grid will now get its data from the web service instead of directly from the database.
-
Repeat the procedure for the next ObjectDataSource, using the Select method of GetLinkByLinkID method and link it to the Selected Value of the Gridview.
Consuming Web Services in the Business Layer
Both Windows and Web can consume the web service, with a fair amount of editing from the direct connection. Since this exercise uses a 3-tiered business layer, making the connection to the web service there would allow the Windows and Web interfaces to continue to function unchanged. The Business layer can decide whether or not to use the web service. Since the web service is a bit slower than the direct connection, there is some advantage to having the option to connect directly if available instead of through the web service.
-
In the Biz project, right click on References and select Add Web Reference
-
Follow the same procedures in 5-8 above to create the Web Reference.
-
Open the WebLinksDataSet.vb, the partial class where most of the business logic resides
-
At the beginning of the class, add an instantiation of the Web Service and a public property to switch between using Web Service and direct access.
Partial Class WebLinksDataSet
Private Shared ws As New localhost.WebLinksWS
Friend Shared _UseService As Boolean
Public Property UseService() As Boolean
Get
Return _UseService
End Get
Set(ByVal value As Boolean)
_UseService = value
End Set
End Property
-
Now for each of the methods, add an If statement to check for the flag and call the corresponding methods. If the flag has been set, call the Get method from the Web Service and if not, call the table adapter method. There are also several attributes that need to be added to each method to connect to the Web Service. The code should now look like this:
Protected Shared taTopic As New ta.TopicTableAdapter
Protected Shared taTopicLink As New ta.TopicLinkTableAdapter
Protected Shared taLink As New ta.LinkTableAdapter
Protected Shared taLinkType As New ta.LinkTypeTableAdapter
Protected Shared taSP As New ta.QueriesTableAdapter
Public Sub FillAll()
LinkType.Fill()
Link.Fill()
Topic.Fill()
TopicLink.Fill()
End Sub
Public Function SubmitRating(ByVal LinkID As Integer, _
ByVal NewRating As Integer) As Long
Dim AvgRating As Long = 0 'output parameter returned
If _UseService Then
ws.UpdateRating(LinkID, NewRating, AvgRating)
Else
AvgRating = taSP.LinkRatingVote(LinkID, NewRating, AvgRating)
End If
'update the avg rating in dataset
Dim row As LinkRow = Link.FindByLinkID(LinkID)
If Not row Is Nothing Then
row.AvgRating = AvgRating
row.EndEdit()
row.AcceptChanges() 'no need to pass to the database
End If
End Function
#Region "Data Tables"
<System.ComponentModel.DataObjectAttribute(True)> _
Partial Class LinkDataTable
<System.ComponentModel.DataObjectMethodAttribute(System.ComponentModel.DataObjectMethodType.[Select], True)> _
Public Sub Fill()
If _UseService Then
Me.Merge(ws.GetLink)
Else
taLink.Fill(Me)
End If
End Sub
<System.ComponentModel.DataObjectMethodAttribute(System.ComponentModel.DataObjectMethodType.[Select], True)> _
Public Sub FillByLinkID(ByVal LinkID As Integer)
If _UseService Then
Me.Merge(ws.GetLinkByLinkID(LinkID))
Else
taLink.FillByLinkID(Me, LinkID)
End If
End Sub
<System.ComponentModel.DataObjectMethodAttribute(System.ComponentModel.DataObjectMethodType.[Select], True)> _
Public Sub FillByTopicID(ByVal TopicID As Integer)
If _UseService Then
Me.Merge(ws.GetLinkByTopicID(TopicID))
Else
taLink.FillByTopicID(Me, TopicID)
End If
End Sub
Public Function GetByLinkID(ByVal LinkID As Integer) As LinkDataTable
If _UseService Then
Me.Clear()
Me.Merge(ws.GetLinkByLinkID(LinkID))
Return Me
Else
Return taLink.GetDataByLinkID(LinkID)
End If
End Function
Public Sub Update()
If _UseService Then
ws.UpdateLink(Me.GetChanges())
Else
taLink.Update(Me)
End If
End Sub
End Class
<System.ComponentModel.DataObjectAttribute(True)> _
Partial Class TopicDataTable
<System.ComponentModel.DataObjectMethodAttribute(System.ComponentModel.DataObjectMethodType.[Select], True)> _
Public Sub Fill()
If _UseService Then
Me.Merge(ws.GetTopic())
Else
taTopic.Fill(Me)
End If
End Sub
<System.ComponentModel.DataObjectMethodAttribute(System.ComponentModel.DataObjectMethodType.[Select], True)> _
Public Sub FillByTopID(ByVal TopLevelID As Integer)
If _UseService Then
Me.Merge(ws.GetTopicByTopLevelID(TopLevelID))
Else
taTopic.FillByTopLevelID(Me, TopLevelID)
End If
End Sub
<System.ComponentModel.DataObjectMethodAttribute(System.ComponentModel.DataObjectMethodType.[Select], True)> _
Public Function GetData() As TopicDataTable
If _UseService Then
Me.Clear()
Me.Merge(ws.GetTopic())
Return Me
Else
Return taTopic.GetData()
End If
End Function
<System.ComponentModel.DataObjectMethodAttribute(System.ComponentModel.DataObjectMethodType.[Select], True)> _
Public Sub Update()
If _UseService Then
ws.UpdateTopic(Me.GetChanges())
Else
taTopic.Update(Me)
End If
End Sub
End Class
<System.ComponentModel.DataObjectAttribute(True)> _
Partial Class TopicLinkDataTable
<System.ComponentModel.DataObjectMethodAttribute(System.ComponentModel.DataObjectMethodType.[Select], True)> _
Public Sub Fill()
If _UseService Then
Me.Merge(ws.GetTopicLink)
Else
taTopicLink.Fill(Me)
End If
End Sub
<System.ComponentModel.DataObjectMethodAttribute(System.ComponentModel.DataObjectMethodType.[Select], True)> _
Public Sub FillByTopicID(ByVal TopicID As Integer)
If _UseService Then
Me.Merge(ws.GetTopicLinkByTopicID(TopicID))
Else
taTopicLink.FillByTopicID(Me, TopicID)
End If
End Sub
<System.ComponentModel.DataObjectMethodAttribute(System.ComponentModel.DataObjectMethodType.[Select], True)> _
Public Function GetByTopicID(ByVal TopicID As Integer) As TopicLinkDataTable
If _UseService Then
Me.Clear()
Me.Merge(ws.GetTopicLinkByTopicID(TopicID))
Return Me
Else
Return taTopicLink.GetDataByTopicID(TopicID)
End If
End Function
<System.ComponentModel.DataObjectMethodAttribute(System.ComponentModel.DataObjectMethodType.[Select], True)> _
Public Sub Update()
If _UseService Then
ws.UpdateTopicLink(Me.GetChanges())
Else
taTopicLink.Update(Me)
End If
End Sub
<System.ComponentModel.DataObjectMethodAttribute(System.ComponentModel.DataObjectMethodType.[Select], True)> _
Public Function GetByKeyWords(ByVal KeyWords As String) As TopicLinkDataTable
If _UseService Then
Me.Clear()
Me.Merge(ws.GetTopicLinkByKeywords(KeyWords))
Return Me
Else
taTopicLink.FillByKeyWords(Me, KeyWords)
Return Me
End If
End Function
End Class
<System.ComponentModel.DataObjectAttribute(True)> _
Partial Class LinkTypeDataTable
<System.ComponentModel.DataObjectMethodAttribute(System.ComponentModel.DataObjectMethodType.[Select], True)> _
Public Sub Fill()
If _UseService Then
Me.Merge(ws.GetLinkType())
Else
taLinkType.Fill(Me)
End If
End Sub
End Class
#End Region
The attributes added to each method helps the ObjectDataSource in the Web interface identify the methods as Select and Update methods to use
-
Save and Build the Biz project.
-
Open the ImportFavorites form in the Windows project.
-
Select the dsWebLinks dataset object in the Components tray. In the properties panel, you will see the new property that you created.
-
Change the property to true. Click the green arrow in the toolbar (or press F5) to run the Windows application.
The application should run the same, although slightly slower. If you have extra time, you might want to put some timers on the load methods to see the exact difference in time.
-
In the Web project, bring up the Default.aspx form and the Default.aspx.vb code behind file.
-
At the beginning of the Form_Load method, add the following line of code:
ds.UseService = True
-
Save and switch back to the Default.aspx page, right click and select "View in Browser".
Again, the web page should show as before, only slightly slower.
Conclusion
Web Services enhance an application and allow much more freedom in accessing data remotely. The current trend in the computer industry is toward Service Oriented Architecture (SOA), a very basic definition of which is providing Web Services as the communication tool between applications and the back-end data it needs.
This tutorial attempts to demonstrate the importance of n-tiered architecture where a business layer isolates the presentation layer from the need to worry about how to get access to the data.
Series Conclusion
It is my sincere hope that these tutorials have been a benefit to you. If you have successfully completed
these tutorials, I would like to hear from you. Or if you have problems or have ideas to improve the
tutorial I would also like to hear from you. Please send me a message at
David.Catherman (at) hotmail (dot) com.
About the Author
David Catherman - CMI Solutions
Email: DCatherman (at) CMiSolutions (dot) com
David Catherman has 20+ years designing and developing database applications with specific concentration for the last 4-5 years on Microsoft .NET and SQL Server. He is currently Application Architect and Senior Developer at CMI Solutions using Visual Studio 2005 and SQL Server 2005. He has several MCPs in .NET and is pursuing MCSD.
|