- •Contents
- •What Is C#?
- •C# Versus Other Programming Languages
- •Preparing to Program
- •The Program Development Cycle
- •Your First C# Program
- •Types of C# Programs
- •Summary
- •Workshop
- •C# Applications
- •Basic Parts of a C# Application
- •Structure of a C# Application
- •Analysis of Listing 2.1
- •Object-Oriented Programming (OOP)
- •Displaying Basic Information
- •Summary
- •Workshop
- •Variables
- •Using Variables
- •Understanding Your Computer’s Memory
- •C# Data Types
- •Numeric Variable Types
- •Literals Versus Variables
- •Constants
- •Reference Types
- •Summary
- •Workshop
- •Types of Operators
- •Punctuators
- •The Basic Assignment Operator
- •Mathematical/Arithmetic Operators
- •Relational Operators
- •Logical Bitwise Operators
- •Type Operators
- •The sizeof Operator
- •The Conditional Operator
- •Understanding Operator Precedence
- •Converting Data Types
- •Understanding Operator Promotion
- •For Those Brave Enough
- •Summary
- •Workshop
- •Controlling Program Flow
- •Using Selection Statements
- •Using Iteration Statements
- •Using goto
- •Nesting Flow
- •Summary
- •Workshop
- •Introduction
- •Abstraction and Encapsulation
- •An Interactive Hello World! Program
- •Basic Elements of Hello.cs
- •A Few Fundamental Observations
- •Summary
- •Review Questions
- •Programming Exercises
- •Introduction
- •Essential Elements of SimpleCalculator.cs
- •A Closer Look at SimpleCalculator.cs
- •Simplifying Your Code with Methods
- •Summary
- •Review Questions
- •Programming Exercises
- •Introduction
- •Lexical Structure
- •Some Thoughts on Elevator Simulations
- •Concepts, Goals and Solutions in an Elevator Simulation Program: Collecting Valuable Statistics for Evaluating an Elevator System
- •A Deeper Analysis of SimpleElevatorSimulation.cs
- •Class Relationships and UML
- •Summary
- •Review Questions
- •Programming Exercises
- •The Hello Windows Forms Application
- •Creating and Using an Event Handler
- •Defining the Border Style of the Form
- •Adding a Menu
- •Adding a Menu Shortcut
- •Handling Events from Menus
- •Dialogs
- •Creating Dialogs
- •Using Controls
- •Data Binding Strategies
- •Data Binding Sources
- •Simple Binding
- •Simple Binding to a DataSet
- •Complex Binding of Controls to Data
- •Binding Controls to Databases Using ADO.NET
- •Creating a Database Viewer with Visual Studio and ADO.NET
- •Resources in .NET
- •Localization Nuts and Bolts
- •.NET Resource Management Classes
- •Creating Text Resources
- •Using Visual Studio.NET for Internationalization
- •Image Resources
- •Using Image Lists
- •Programmatic Access to Resources
- •Reading and Writing RESX XML Files
- •The Basic Principles of GDI+
- •The Graphics Object
- •Graphics Coordinates
- •Drawing Lines and Simple Shapes
- •Using Gradient Pens and Brushes
- •Textured Pens and Brushes
- •Tidying up Your Lines with Endcaps
- •Curves and Paths
- •The GraphicsPath Object
- •Clipping with Paths and Regions
- •Transformations
- •Alpha Blending
- •Alpha Blending of Images
- •Other Color Space Manipulations
- •Using the Properties and Property Attributes
- •Demonstration Application: FormPaint.exe
- •Why Use Web Services?
- •Implementing Your First Web Service
- •Testing the Web Service
- •Implementing the Web Service Client
- •Understanding How Web Services Work
- •Summary
- •Workshop
- •How Do Web References Work?
- •What Is UDDI?
- •Summary
- •Workshop
- •Passing Parameters and Web Services
- •Accessing Data with Web Services
- •Summary
- •Workshop
- •Managing State in Web Services
- •Dealing with Slow Services
- •Workshop
- •Creating New Threads
- •Synchronization
- •Summary
- •The String Class
- •The StringBuilder Class
- •String Formatting
- •Regular Expressions
- •Summary
- •Discovering Program Information
- •Dynamically Activating Code
- •Reflection.Emit
- •Summary
- •Simple Debugging
- •Conditional Debugging
- •Runtime Tracing
- •Making Assertions
- •Summary
348 |
Day 15 |
Caution |
When you use the HTTP protocol (either GET or POST requests), all parame- |
|
ters must be passed by value, because HTTP doesn’t support call by refer- |
||
|
||
|
ence. In fact, the interface supplied by any ASP.NET Web service (the WSDL |
|
|
file) won’t contain any by reference method definitions. |
|
|
|
If you want to try passing a parameter by reference to a Web Service, add the following method to Listing 15.4:
[WebMethod]
public void GetTimeByRef(ref TimeStruct ts)
{
ts = GetTimeEx();
}
Next, change Line 14 of Listing 15.6 to the following:
timeServer.GetTimeByRef(ts);
The modified listings should produce the same result as before. If you need to write a method that modifies more than one parameter, simply make each parameter byref by adding the ref keyword.
Accessing Data with Web Services
Because the SOAP protocol allows you to send any kind of parameter and receive any kind of return value, you can send and receive ADO.NET datasets. By using datasets in combination with Web Services, you can create Web Services that provide many kinds of data services. This section will describe a sample Web Service that manages a task list using dataset objects and XML files.
The Shared Task Web Service
The sample Shared Task Web Service allows multiple users to add and change tasks in a central location. The service stores the tasks in an XML file and allows any number of users to add and update tasks in the list. Figure 15.1 shows an example of a client that uses the Shared Task Web Service.
Any Internet application that allows more than one user to update data records has to deal with data concurrency issues. These issues consist of problems that can happen when two or more users update the same data record at the same time. You handle this problem by using a record-locking policy.
Consuming a Web Service |
349 |
FIGURE 15.1
A Web form that uses |
15 |
|
|
the Shared Task Web |
|
Service. |
|
You can implement two kinds of record-locking policies to handle the simultaneous update problem: optimistic locking and pessimistic locking. Optimistic locking
means that users can overwrite each other’s changes. Pessimistic locking means that once a user obtains a lock on a data record, other users can’t update that record until the first user releases the lock. Let’s look at a scenario for each locking situation.
A typical pessimistic locking scenario might be the following:
1.User Joe reads a database record and sets a lock on the record.
2.User Sue reads the record but can’t get a lock for it.
3.Sue tries to update the record but gets an error such as The record
is locked by Joe.
4.Joe updates the record, saves changes, and releases the lock.
5.Sue can now update the record.
A typical optimistic locking scenario might be the following:
1.Joe reads a database record.
2.Sue reads the same record.
3.Sue updates the record.
4.Joe updates the record and overwrites Sue’s changes.
From these scenarios, you might think that optimistic locking is a bad idea. Joe and Sue don’t know that they overwrote each other’s changes.
However, pessimistic locking can make a Web Service or Web application extremely slow because record locking takes up valuable database resources and can prevent other users from accessing other records. Most databases that use record locking must keep a
350 |
Day 15 |
permanent database connection open to the client program to preserve the lock. Database connections are a scarce resource. If a server has too many open database connections, it will perform extremely slowly. To compound the problem, if your Internet application uses XML to store data, you have to implement your own record-locking logic, which can be quite complex.
One solution to the pessimistic locking problem involves using record versioning. One way of implementing record versioning is to attach a timestamp to each record. The scenario works like this:
1.Joe reads a database record. The record contains a field with a timestamp of the last time the record was modified.
2.Sue reads the same record, along with the same timestamp.
3.Sue updates the record. The timestamp is changed to the current time because the record has been modified.
4.Joe tries to update the record. His record’s timestamp doesn’t match the timestamp of the current record. The server detects this difference and gives Joe the option of refreshing his record to get the latest changes or simply overwriting the current record.
In this solution, Joe knows that he is about to overwrite a record whose value has changed.
Implementing Optimistic Locking
Let’s implement the Shared Task Web Service so that it uses this record versioning solution to the optimistic locking program. Our first task is to design the look of the XML file that stores the shared tasks. Listing 15.7 shows an example.
LISTING 15.7 TaskStore.XML: A Sample XML File Containing Shared Tasks
<?xml version=”1.0”?>
<sharedtasks xmlns=”http://localhost/TaskServer/TaskStore.xsd”> <task>
<name>Cross Reference Documentation</name> <due>2001-10-05T12:00:00</due> <owner>Joe</owner> <modified>2001-08-10T17:50:03</modified>
</task>
<task>
<name>Print Documentation</name> <due>2001-12-05T12:00:00</due> <owner>Sue</owner> <modified>2001-08-08T08:45:04</modified>
</task>
</sharedtasks>
Consuming a Web Service |
351 |
Each task contains a name, due date, owner name, and modified field. |
|
Next, create a schema for the XML file. As a first step, use Visual Studio to automatically |
15 |
generate the schema. Then you can modify the schema to make the name field a primary key and to establish the appropriate data types for each field. The name and owner fields should be strings, and the due and modified fields should be of type dateTime. If you are following along with the lesson, you can make these changes in the Design view of the XML Schema editor in Visual Studio. The lesson for Day 11, “Using ADO.NET and XML Together,” gave details on how to use the editor. The final XML Schema should look like Listing 15.8.
LISTING 15.8 TaskStore.XSD: A Schema Definition for the Shared Tasks XML File
1:<xsd:schema id=”sharedtasks_id”
2:targetNamespace=”http://localhost/TaskServer/TaskStore.xsd”
3:xmlns=”http://localhost/TaskServer/TaskStore.xsd”
4:xmlns:xsd=”http://www.w3.org/2001/XMLSchema”
5:xmlns:msdata=”urn:schemas-microsoft-com:xml-msdata”
6:attributeFormDefault=”qualified”
7:elementFormDefault=”qualified”>
8:<xsd:element name=”sharedtasks”
9: |
msdata:IsDataSet=”true” |
10: |
msdata:EnforceConstraints=”true”> |
11:<xsd:complexType>
12:<xsd:choice maxOccurs=”unbounded”>
13: |
<xsd:element name=”task”> |
14: |
<xsd:complexType> |
15: |
<xsd:sequence> |
16: |
<xsd:element name=”name” |
17: |
type=”xsd:string” |
18: |
minOccurs=”0” |
19: |
msdata:Ordinal=”0” |
20: |
msdata:AllowDBNull=”false” /> |
21: |
<xsd:element name=”due” |
22: |
type=”xsd:dateTime” |
23: |
minOccurs=”0” |
24: |
msdata:Ordinal=”1” /> |
25: |
<xsd:element name=”owner” |
26: |
type=”xsd:string” |
27: |
minOccurs=”0” |
28: |
msdata:Ordinal=”2” /> |
29: |
<xsd:element name=”modified” |
30: |
type=”xsd:dateTime” |
31: |
minOccurs=”0” |
32: |
msdata:Ordinal=”3” /> |
33: |
</xsd:sequence> |
34: |
</xsd:complexType> |
35: |
</xsd:element> |
352 |
Day 15 |
LISTING 15.8 Continued
36:</xsd:choice>
37:</xsd:complexType>
38:<xsd:unique name=”task_con”
39: msdata:PrimaryKey=”true”>
40:<xsd:selector xpath=”.//task” />
41:<xsd:field xpath=”name” />
42:</xsd:unique>
43:</xsd:element>
44:</xsd:schema>
Lines 38–42 define a primary key for the XML file using the unique element. The name field is defined as the primary key field using the selector (Line 40)
and field (Line 41) elements. Line 10 uses the EnforceConstraints attribute to make sure that ADO.NET uses the primary key when it reads the XML file. This way, ADO.NET will throw an exception if duplicated records with the same name field are added to the file.
Next, create a Web Service that allows users to add and modify tasks:
1.Create a new project using the Blank Web Project template. Name the project TaskServer.
2.Add a config.web file by choosing Add New Item from the Project menu.
3.Copy the TaskStore.xml file into the project directory. Add the file to the solution by choosing Add Existing Item from the Project menu.
4.Add the TaskStore.xsd file into the project.
5.Create a new typed dataset using the TaskStore.xsd file. To do so, select the TaskStore.xsd file from the Solution Explorer window. Then select the Generate DataSet option from the Schema menu.
6.Add a new Web Service file by using the Project menu’s Add Web Service option. Call the Web Service TaskServer.asmx.
Create a method called GetTasks to return all tasks in the TaskStore.xml file to the user. Listing 15.9 shows what the method should look like.
LISTING 15.9 Returning a Dataset Containing All Shared Tasks
1:[WebMethod]
2:public sharedtasks GetTasks()
3:{
4:sharedtasks ds = new sharedtasks();
Consuming a Web Service |
353 |
LISTING 15.9 Continued
15
6:return ds;
7:}
LISTING 15.10 Adding a New Shared Task
1:[WebMethod]
2:public bool AddTask(sharedtasks dsNewTask, ref String Error)
3:{
4:bool bSuccess = true;
5:sharedtasks.taskRow newRow = dsNewTask.task[0];
6:
7:sharedtasks ds = GetTasks();
8:try
9:{
10:ds.task.ImportRow(newRow);
11:ds.WriteXml(Server.MapPath(“TaskStore.xml”));
12:}
13:catch(ConstraintException ex)
14:{
15:bSuccess = false;
16:Error = “A task with the name “ + newRow.name + “ already exists.”;
17:}
18:catch(Exception ex)
19:{
20:bSuccess = false;
21:Error = “Error: “ + ex.Message;
22:}
23:return bRetVal;
24:}
Line 5 creates a reference to the row containing the data for the new task called newRow by selecting the row at index 0 from the task table. Line 7 gets the current
task list by calling the GetTasks method and assigns it the typed dataset called ds.
354 |
Day 15 |
Lines 8–12 try to add the new row to the dataset by calling the ImportRow method (Line 10). ImportRow will throw a ConstraintException object if the new record contains a duplicate name field. Line 11 will write the new version of the shared task list back to disk if the ImportRow call was successful.
Lines 13–17 deal with a ConstraintException, if it occurs. Line 16 creates an error message that client programs can display to the user.
Next, add a method to the TaskService that will modify a row in the shared tasks file. This method needs to check the “modified” field of the record and match it against the “modified” field in the current record. If the two match, the client’s original data was valid. If the two modification times are different, the client’s data is out of sync with the data on the server. In this case, the method should return an error message. Listing 15.11 shows an example of the ModifyTask method.
LISTING 15.11 Modifying a Task, Checking to See Whether Another Client Has Modified the Record
1:[WebMethod]
2:public bool ModifyTask(ref sharedtasks dsTask, ref String Error)
3:{
4:bool bSuccess = true;
5:
6: sharedtasks.taskRow modifiedRow = dsTask.task[0];
7:
8:sharedtasks ds = GetTasks();
9:sharedtasks.taskDataTable taskTable = ds.task;
10:DataRow[] matchingRows =
11:taskTable.Select(“name = ‘“ + modifiedRow.name + “‘“);
13:if(matchingRows.Length == 1)
14:{
15:sharedtasks.taskRow rowToModify =
16:(sharedtasks.taskRow) matchingRows[0];
18:if(rowToModify.modified == modifiedRow.modified)
19:{
20:modfiedRow.modified = DateTime.Now;
21:ds.Merge(dsTask);
22:ds.WriteXml(Server.MapPath(“TaskStore.xml”));
23:}
24:else
25:{
26:bSuccess = false;
27:Error = “This task has been changed by another user. “ +
28:“Please refresh your view of the data.”;
29:}
Consuming a Web Service |
355 |
LISTING 15.11 Continued |
|
30: } |
15 |
31:else
32:{
33:bSuccess = false;
34:Error = “No task named “ + dsTask.task[0].name + “ found.”;
35:}
36:return bSuccess;
1.Add a new project to the current solution using the Blank Web Project template (choose New Project from the File menu). Name the project Task Client.
2.Add a config.web file by using the Project menu’s Add New Item option.
3.Add a new Web reference from the TaskService by using the Add Web Reference option on the Project menu.
356 |
Day 15 |
4.Rename the proxy class created by Visual Studio to TaskServerProxy. To do so, right-click the localhost Web reference in the Solution Explorer window and select Rename.
5.Add a new Web form by using the Add Web Form option on the Project menu. Call the Web form TaskUI.asmx.
Next, create a user interface for the Web form. The form should contain a DataGrid to display the shared tasks and three buttons to add records, modify records, and refresh the grid. Also, add text boxes so that users can specify the name, due date, and owner of each new or modified record. Listing 15.12 shows an example of the Web form.
LISTING 15.12 TaskUI.aspx: A Web Form Client for the Task Service
1: |
<%@ Page language=”c#” Codebehind=”TaskUI.aspx.cs” |
2: |
Inherits=”TaskClient.TaskUI” %> |
3:<html>
4:<body>
5:<font face=”arial”>
6:<form runat=”server” ID=”Form1”>
7:<h3>Shared Tasks</h3>
8:<asp:DataGrid ID=”taskGrid” Runat=”server”
9: HeaderStyle-Font-Bold=”True”/>
10:<hr>
11:Task Name: <asp:TextBox Runat=”server” ID=”Name”/><br>
12:Due (dd/mm/yy): <asp:TextBox Runat=”server” ID=”Due”/><br>
13:Owner: <asp:TextBox Runat=”server” ID=”Owner”/><br>
14:<br>
15:<asp:Button Runat=”server” OnClick=”AddTask”
16: |
Text=”Add Task” /> |
17:<asp:Button Runat=”server” OnClick=”ModifyTask”
18: |
Text=”Modify Task” /> |
19:<asp:Button Runat=”server” OnClick=”OnRefresh”
20: Text=”Refresh View” />
21:<br>
22:<asp:Label Runat=”server” ID=”ErrMessage” ForeColor=”Red”/>
23:</form>
24:</font>
25:</body>
26:</html>
Lines 8–9 define a data grid for the tasks, called taskGrid. Lines 11–13 define new text box controls that allow users to specify the task name, due date, and
owner. Lines 15–20 define button controls to add tasks, modify tasks, and refresh data. Using the data grid for task editing could enrich this interface, but this example keeps things simple.
Consuming a Web Service |
357 |
Next, set up the infrastructure for the client Web form. The client should keep its version
of the task list in session state and contain methods to load the grid from the Web 15 Service on the first page hit. Listing 15.13 shows the infrastructure for the Web form.
LISTING 15.13 TaskUI.aspx.cs: The Infrastructure Code for the Web Form Client
1:using System;
2:using System.Data;
3:using System.Web.UI;
4:using System.Web.UI.WebControls;
5:using TaskClient.SharedTaskProxy;
7:namespace TaskClient
8:{
9:public class TaskUI : System.Web.UI.Page
10:{
11:protected DataGrid taskGrid;
12:protected Label ErrMessage;
13:protected TextBox Name;
14:protected TextBox Owner;
15:protected TextBox Due;
16:
17:private void Refresh()
18:{
19:TaskService ts = new TaskService();
20:sharedtasks ds = ts.GetTasks();
21:ds.EnforceConstraints = false;
22:Bind(ds);
23:}
24:private void Bind(sharedtasks ds)
25:{
26:Session[“Data”] = ds;
27:taskGrid.DataSource = ds.task;
28:taskGrid.DataBind();
29:}
30:protected void Page_Load(Object Sender, EventArgs e)
31:{
32:if(!IsPostBack)
33:{
34:Refresh();
35:}
36:else
37:{
38:sharedtasks ds = (sharedtasks) Session[“Data”];
39:Bind(ds);
40:}
41:}
42:protected void OnRefresh(Object sender, EventArgs e)
43:{
358
1:protected void AddTask(Object Sender, EventArgs e)
2:{
3:sharedtasks ds = (sharedtasks) Session[“Data”];
4:ds.AcceptChanges();
5:
6:sharedtasks.taskDataTable dt = ds.task;
7:dt.AddtaskRow(Name.Text, DateTime.Parse(Due.Text),
8: Owner.Text, DateTime.Now);
9:sharedtasks delta =
10:(sharedtasks) ds.GetChanges(DataRowState.Added);
12:TaskService ts = new TaskService();
13:String Error=””;
14:bool bOk = ts.AddTask(delta, ref Error);
16:if(bOk)
17:{
18:ds.AcceptChanges();
19:ErrMessage.Text = “”;
20:}
Consuming a Web Service |
359 |
LISTING 15.14 Continued
|
|
21: |
else |
15 |
|
|
|
22: |
{ |
|
|
|
|
23: |
|
ds.RejectChanges(); |
|
|
|
24: |
|
ErrMessage.Text = Error; |
|
|
|
25: |
} |
|
|
|
|
26: |
Bind(ds); |
|
|
|
|
27: } |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
LISTING 15.15 Modifying a Task by Calling the Remote Web Service
1:protected void ModifyTask(Object Sender, EventArgs e)
2:{
3:sharedtasks ds = (sharedtasks) Session[“Data”];
4:ds.AcceptChanges();
5:
6:DataRow [] matchingRows = ds.task.Select(“name = ‘“ + Name.Text + “‘“);
7:if(matchingRows.Length == 0)
8:{
9:ErrMessage.Text = “No task with this name exists”;
360 |
Day 15 |
LISTING 15.15 Continued
10:return;
11:}
12:sharedtasks.taskRow modifiedRow =
13:(sharedtasks.taskRow) matchingRows[0];
15://don’t change modified time - server will do this on success;
16://add stuff for get changes.
17:modifiedRow.due = DateTime.Parse(Due.Text);
18:modifiedRow.owner = Owner.Text;
19:
20:sharedtasks delta =
21:(sharedtasks) ds.GetChanges(DataRowState.Modified);
22:String Error=””;
23:TaskService ts = new TaskService();
24:bool bOk = ts.ModifyTask(ref delta, ref Error);
25:
26:if(bOk)
27:{
28:modifiedRow.modified = delta.task[0].modified;
29:ds.AcceptChanges();
30:ErrMessage.Text = “”;
31:}
32:else
33:{
34:ds.RejectChanges();
35:ErrMessage.Text = Error;
36:}
37:Bind(ds);
38:}
Lines 6–13 contain code to find the row in the local dataset to modify by matching what the user entered into the name text box with records in the local dataset.
Lines 17–18 modify the fields in the row to match what the user entered in the Due and Owner text boxes.
Lines 20–21 create a new dataset, called delta, that contains the modified records. Lines 22–24 send the delta dataset to the Web Service by calling the service’s ModifyTask method.
If the modification proceeds without any errors, the timestamp for the modified record is updated (Line 28), and Line 29 calls the AcceptChanges method. If the server returns an error, the RejectChanges method is called on Line 34.