Web Service #3 : Stateless

    ในเมื่อ Microsoft ออกแบบ Web Service ให้อิงกับ Web Server ของ Microsoft ซึ่งก็คือ IIS นั้น ความสามารถต่างๆ มันจึงถูกจำกัดโดยความสามารถของ IIS ด้วย เรามาลองดูตัวอย่างนี้กัน

    สมมุติว่าคุณต้องการเขียน Web Service ทำหน้าที่เป็น Server ของเกมส์ทายตัวเลข Server จะ Random ค่าหนึ่งเก็บไว้ และโปรแกรมของฝั่ง client จะทำการทายตัวเลขต่างๆ ถ้าถูกก็บอกว่าถูก แต่ถ้าผิดก็จะตอบกลับไปว่ามากไป หรือน้อยไป ทั้งนี้ Server ก็เก็บค่าไว้ด้วยว่าผู้เล่นได้ทายไปกี่ครั้งแล้ว เกมส์แบบนี้เป็นเกมส์ที่ผมหัดเขียนเป็นโปรแกรมแรกๆ ในชีวิตเมื่อเกือบ 20 ปีที่ผ่านมา

    ผมก็เลขเขียน Web Service Class แบบนี้ครับ มาช่วยกันดูหน่อย 

sguess.asmx

<%@ WebService Language="C#" class="SGuess" %>
using System;
using System.Web.Services;
public class SGuess : WebService
{
	private int ptimes;
	private int pnumber;	
	public enum GUESS_TYPE {
		TooLow,
		TooHigh,
		Correct
	};
			
	[WebMethod] 
	public void Reset(int max)
	{
		Random rand = new Random();
		ptimes = 0;
		pnumber = rand.Next() % max + 1;
	}
	
	[WebMethod] 
 	public GUESS_TYPE Play(int num)
	{
		++ptimes;
		
		if (pnumber == num) {
			return GUESS_TYPE.Correct;
		} else if (pnumber < num) {
			return GUESS_TYPE.TooHigh;
		} else {
			return GUESS_TYPE.TooLow;
		}
	}
	
	[WebMethod] 
 	public int Times()	{
		return ptimes;
	}
}

    ลองเอา file นี้เก็บบน web server IIS 5 ที่มี .Net Framework Install อยู่ แล้วลองดูว่า เรียกใช้ file นี้ดู ดังนี้ http://localhost/sguess.asmx ถ้าไม่มี error มันจะแสดงวิธีใช้ ทั้งหมดมีอยู่ 3 methods มีคำอธิบายดังนี้

    คุณสามารถลองเล่นโดยการเรียกใช้ click link ในหน้า web page sguess.asmx หรือคุณสามารถสร้าง proxy   client ดังนี้

DOS Prompt

C:\cs>webserviceutil /pa:http://localhost/sguess.asmx?sdl /protocol:soap /out:sgu
ess.cs /command:proxy
Microsoft (R) Web Services Utility
[Microsoft .NET Framework Version 1.0.2204.21]
Copyright (C) Microsoft Corp 2000. All rights reserved.

sguess.cs

C:\cs> _

และเอาไปใช้กับ client class ดังนี้

cguess.asmx

using System;
class CGuess
{ 	
	public static void Main()
	{ 		
		int number;
		GUESS_TYPE result;
		string str;
		
		SGuess guess = new SGuess();
		
		do {
			guess.Reset(1000); 			
			do {
				Console.Write("Guess a number between 1 to 1000? ");
				number  = Console.ReadLine().ToInt32();
				result = guess.Play(number);
				if (result == GUESS_TYPE.Correct) {
					Console.WriteLine("Correct");
				} else if (result == GUESS_TYPE.TooLow) {
					Console.WriteLine("Too Low");
				} else {
					Console.WriteLine("Tow High");
				}
			} while (result != GUESS_TYPE.Correct);
			Console.WriteLine("You guess in {0} times", guess.Times());
			Console.Write("Try Again (y/n)? ");
			str = Console.ReadLine();
		} while (str[0] == 'y' || str[0] == 'Y');
		Console.WriteLine("Good bye!");
	}
}

compile โปรแกรมทั้งสองรวมกัน และ run ดังนี้ครับ

DOS Prompt

C:\cs>csc /out:guess.exe cguess.cs sguess.cs /r:system.xml.serialization.dll;sy
tem.web.services.dll
Microsoft (R) Visual C# Compiler Version 7.00.9030 [CLR version 1.00.2204.21]
Copyright (C) Microsoft Corp 2000. All rights reserved.


C:\cs>guess
Guess a number between 1 to 1000? 500
Tow High
Guess a number between 1 to 1000? 250
Tow High
Guess a number between 1 to 1000? 125
Tow High
Guess a number between 1 to 1000? 75
Tow High
Guess a number between 1 to 1000? 30
Tow High
Guess a number between 1 to 1000? 15
Tow High
Guess a number between 1 to 1000? 5
Tow High
Guess a number between 1 to 1000? 0
Correct
You guess in 0 times
Try Again (y/n)? y
Guess a number between 1 to 1000? 0
Correct
You guess in 0 times
Try Again (y/n)?
n
C:\cs> _

    จะเห็นได้ว่าไม่ว่าคุณเรียกใช้งาน Web Method ผ่าน Browser หรือต่อกับ client โดยใช้ WebServiceUtil.exe มัน Random ค่าออกมาได้ 0 เสมอ ซ้ำร้ายจำนวนครั้งในการทาย ก็เป็น 0 ด้วย มันเกิดอะไรขึ้น

    เรื่องนี้ตอนแรกผมก็มึนเหมือนกัน เลยต้องถามอ้อ ปาริชาต ก็ได้คำตอบมาว่าปัญหาเรื่องนี้มันเกิดจากคำว่า stateless คือ IIS มันไม่เก็บสถานะใดๆ ของ process ที่มันใช้รับการติดต่อจาก browser ของคุณ นั่นก็หมายความว่า เมื่อคุณเรียก web method ใดๆ มาใช้ เมื่อมันทำงานเสร็จ method นั้น มันก็จะตายไป เมื่อคุณเรียกใช้อีก 1 method ถึงแม้ว่าจะเป็น browser ใน session เดิม มันก็จะ new() class นั้นใหม่อยู่ดี ทำให้ตัวแปรที่เป็น Local ทั้งหมดใช้งานไม่ได้ ทางออกที่อ้อแนะนำคือ เอาค่า local ของเราไปฝากไว้เป็นตัวแปร session เหมือนกับที่ทำกับพวก Active Server Page (ASP) แต่คุณบอก Web Service ให้รองรับ session ด้วยโดยการเพิ่ม attribute  ผลลัพธ์ของ sguess.asmx จึงเป็นดังนี้ครับ

sguess.asmx

<%@ WebService Language="C#" class="SGuess" %>
using System;
using System.Web.Services;
public class SGuess : WebService
{
	public enum GUESS_TYPE {
		TooLow,
		TooHigh,
		Correct
	};
			
	[WebMethod(Description="Reset Everything to Zero", EnableSession=true)] 
	public void Reset(int max)
	{
		Random rand = new Random();
		Session["ptimes"] = 0;
		Session["pnumber"] = rand.Next() % max + 1;
	}
	
	[WebMethod(EnableSession=true)] 
 	public GUESS_TYPE Play(int num)
	{
		Session["ptimes"] = (int)Session["ptimes"] + 1;
		if ((int)Session["pnumber"] == num) {
			return GUESS_TYPE.Correct;
		} else if ((int)Session["pnumber"] < num) {
			return GUESS_TYPE.TooHigh;
		} else {
			return GUESS_TYPE.TooLow;
		}
	}
	
	[WebMethod(EnableSession=true)] 
 	public int Times() 
	{
		return (int)Session["ptimes"];
	}
}

    จะเห็นได้ว่าจาก attribute [WebMethod] กลายเป็น [WebMethod(EnableSession=true)]  ยิ่งเป็น WebMethod ของ Reset ยังใส่ Description ด้วยจะได้แสดงเมื่อเราเรียก browser ไปดู sguess.asmx 

    เมื่อเราบอกให้แต่ละ method รองรับ Session แล้วเราก็ใช้ตังแปร Session เหมือนใน ASP เพื่อเก็บค่าต่างๆ ตัวแปร Session นั้นไม่ได้เก็บข้อมูลได้เฉพาะตัวแปรธรรมดาเท่านั้นนะครับ object ก็เก็บได้ หรือข้อมูลทั้ง database เช่น dataset ของ ADO.Net ก็เก็บได้เช่นกัน 

    คราวนี้คุณลองทดสอบโปรแกรมผ่าน browser ดูว่ามันทำงานถูกต้องแล้วหรือยัง ถ้าถูกแล้วเรามาลองทำ client ใหม่ดังนี้

DOS Prompt

C:\cs>webserviceutil /pa:http://localhost/sguess.asmx?sdl /protocol:soap /out:sgu
ess.cs /command:proxy
Microsoft (R) Web Services Utility
[Microsoft .NET Framework Version 1.0.2204.21]
Copyright (C) Microsoft Corp 2000. All rights reserved.

sguess.cs

C:\cs>csc /out:guess.exe cguess.cs sguess.cs /r:system.xml.serialization.dll;sy
tem.web.services.dll
Microsoft (R) Visual C# Compiler Version 7.00.9030 [CLR version 1.00.2204.21]
Copyright (C) Microsoft Corp 2000. All rights reserved.

C:\cs>guess
Guess a number between 1 to 1000? 500
Tow High
Guess a number between 1 to 1000? 250
Too Low
Guess a number between 1 to 1000? 375
Tow High
Guess a number between 1 to 1000? 315
Too Low
Guess a number between 1 to 1000? 340
Too Low
Guess a number between 1 to 1000? 355
Tow High
Guess a number between 1 to 1000? 350
Too Low
Guess a number between 1 to 1000? 353
Tow High
Guess a number between 1 to 1000? 352
Tow High
Guess a number between 1 to 1000? 351
Correct
You guess in 10 times
Try Again (y/n)? n
Good bye!

C:\cs>

    มันทำงานถูกแล้ว แต่คุณอาจจะไม่ค่อยชอบมาทำไมต้องเป็นแบบนี้ ผมเองก็ไม่ชอบเหมือนกัน แต่ในเมื่อมันอิงเข้ากับ Web Server มันก็ต้องจำกัดด้วยความสามารถของ Web Server รอดูใน version ต่อไปว่าจะมีอะไรพัฒนากว่านี้อีก

    สรุปข้อจำกัดของ Web Service