Уеб услуги с asp. Net



страница9/9
Дата24.03.2017
Размер0.86 Mb.
#17693
1   2   3   4   5   6   7   8   9

Дебъгване на уеб услуги


По отношение на дебъгването уеб услугите по нищо не се различават от уеб приложенията. Те се дебъгват по абсолютно аналогичен начин. Visual Studio .NET ни дава възможността както да стартираме направо услугата в режим Debug, така и да се прикачим към работния процес, обслужващ ASP.NET. Тези двата подхода за дебъгване са подробно описани в темата "Уеб приложения с ASP.NET" [TODO: link].

Моделът на изпълнение на уеб услугите в ASP.NET


Уеб услугите са създадени за да бъдат използвани от други приложения. Те нямат графичен потребителски интерфейс и когато потребителят иска да се възползва от някоя уеб услуга, той трябва да се обърне към някое приложение, което я използва.

Често пъти клиенти на уеб услугите са ASP.NET уеб приложения. Моделът на изпълнение при такива системи можем да представим схематично по следния начин:





        1. Клиентът изпраща заявка към уеб сървъра за определена .aspx страница.

        2. Уеб сървърът извиква ISAPI библиотеката на .NET Framework – aspnet_isapi.dll, която се грижи за понататъшната обработка на заявката. .NET Framework парсва .aspx страницата, компилира я и я изпълнява (Това става в случай, че заявка към тази страница се подава за първи път. Ако не е така, направо се изпълнява вече зареденият код).

        3. По време на изпълнението на компилирания код, ASP.NET приложе­нието се обръща към методи на междинния (proxy) клас, който е генериран и компилиран заедно с приложението. Тези методи прие­мат същите параметри и връщат същия резултат както и реализира­ните в уеб услугата.

        4. Междинните (proxy) методи от своя страна конструират SOAP съоб­щения и ги изпращат до уеб услугата. Това става по същия начин, по който се извикват .aspx страници ­– отново чрез HTTP заявка към уеб сървъра и извикване на ISAPI филтъра за ASP.NET.

        5. Уеб услугата има свой собствен модел на вътрешно изпълнение. SOAP съобщението се парсва и се извиква съответният метод. В резултат се връща някакъв резултат, който отново се сериализира в SOAP формат и се връща на уеб приложението.

        6. Междинният (proxy) метод десериализира SOAP съобщението и връ­ща резултата като .NET обект.

        7. ASP.NET уеб приложението довършва изпълнението си и връща отговор на заявката на клиента под формата на HTML страница.

В стъпка номер 5 от изпълнението на показания модел споменахме за вътрешния модел на изпълнение за уеб услугата. Той описва процеса, който се изпълнява при извикване на уеб услуга от момента на постъпване на SOAP заявката до момента, в който се връща отговор. За всяка заявка към уеб услуга ASP.NET изпълнява следното:

          1. Инстанцира се класът на уеб услугата.

          2. Заделя се отделна нишка от общия пул с нишки.

          3. Изпълнява се заявката в тази нишка – извиква определения метод.

          4. Връща се резултата към клиента – отново под формата на SOAP съобщение.

          5. Нишката се връща обратно в общия пул с нишки.

          6. Оставя инстанцията на класа на услугата да бъде унищожена от системата за почистване на паметта (garbage collector).

Естеството на уеб услугите се състои в това те да могат да бъдат използвани от много приложения едновременно, което води до необходи­мостта много заявки да се обслужват едновременно. Нишките за изпълне­ние, както всички останали ресурси в реалния свят, са краен брой. При достатъчно голяма натовареност на уеб услугата, заявките стават повече от наличните нишки. В такъв случай ASP.NET поставя заявките в опашка и когато се освободи нишка, в нея започва изпълнението на следващата заявка от опашката.

Асинхронно извикване на уеб услуги


Когато създаваме приложения, искаме те винаги да отговарят на потреби­телското взаимодействие, независимо, че може приложението да извър­шва някоя тежка и времеот­немаща операция. Ако една такава операция съдържа в себе си извикване на уеб услуга (особено, ако това става през Интернет, а не на локалната машина), изпълнението може да отнеме голямо количество време.

Прак­тиката показва, че забавяния от порядъка на 200 милисекунди до цяла секунда, са често срещани. Това може и да не изглежда много, но за потребителите това е сериозно дразнение, понеже потреби­телският ин­терфейс напълно спира да функционира за този период.

Потребителският интерфейс обикновено блокира докато се извиква уеб метод, защото той, заедно с уеб метода работят в една и съща нишка. Докато уеб методът не върне резултат, нишката блокира изпълнението си и така потребителският интерфейс не се обновява.

Изпълнението на всеки метод, който предизвиква значително забавяне, в основната нишка предизвиква блокиране на потребителския интерфейс. Това трябва да се избягва. Такова поведение на приложението може да накара потребителя да започне да хвърля клавиатури, да троши мони­тори, или още по-лошо – никога повече да не купува вашия софтуер.


Методи за асинхронно извикване в междинния (proxy) клас


Когато създаваме междинен (proxy) клас за дадена уеб услуга, .NET Framework генерира в него методи, напълно аналогични на тези в уеб услугата. Ако отворим сорс кода освен стандартните методи обаче ще забележим и такива, чиито имена започват с "Begin" и "End" и завършват с името на уеб метод. Например ако уеб услугата има уеб метод с име CalculatePayment(), в междинния (proxy) клас ще има съответно метод CalculatePayment(), а също така и два метода BeginCalculatePayment() и EndCalculatePayment(). Последните се използват при асинхронното извикване на уеб услуги.

Тъй като при асинхронното извикване се заделя допълнителна нишка, независима от основната, е необходимо да дефинираме метод, който да бъде извикан обратно при приключване на работата на допълнителната нишка. Този метод задължително трябва да приема параметър от тип IAsyncResult.

За да демонстрираме асинхронно извикване на уеб метод, ще използваме дефинирания в предната демонстрация уеб метод - SlowCalculation(). от услугата TypesService.

За целта създаваме ново конзолно приложение с име AsynWSCallDemo и в него добавяме файла AsynWSCallDemo.cs и уеб референция към създаде­ната в предната демонстрация уеб услуга (TypesService). Ето сорс кода на примерното приложение:



AsynWSCallDemo.cs

using System;

using AsynWSCallDemo.MyServices;


namespace AsynWSCallDemo

{

class AsynWSCallDemo



{

private static TypesService mService = new TypesService();


public static void Main()

{

AsyncCallback cb = new AsyncCallback(CallFinished);



IAsyncResult ar =

mService.BeginSlowCalculation(cb, mService);

Console.WriteLine("Async call started.");

Console.Write("Loading.");

int cycleCounter = 0;

while(!ar.IsCompleted)

{

cycleCounter++;



}

Console.WriteLine("Cycles Passed: " + cycleCounter);

Console.ReadLine();

}
private static void CallFinished(IAsyncResult aAsyncResult)

{

Console.WriteLine("Async call completed.");



int result = mService.EndSlowCalculation(aAsyncResult);

Console.WriteLine("Result = {0}", result);

}

}

}



В основния метод Main(…) създаваме обект от тип AsyncCallback, на който в конструктора подаваме метода, който да бъде извикан при приключване на работата на допълнителната нишка. Този обект трябва да се създаде, за да бъде подаден след това като параметър на метода, извикващ асинхронно уеб метода на уеб услугата.

Инстанцията на уеб услугата в примера е mService. Извикваме метода на mService BeginSlowCalculation(…) като му подаваме множество пара­метри. В случая уеб методът на услугата няма входни параметри затова подаваме задължителните cb (обекта от тип AsyncCallback) и mService.

Ако на уеб метода се подават някакви параметри, тогава на BeginSlowCalculation(…) първо се подават те и след това AsyncCallback и WebService обектите.

Като резултат BeginSlowCalculation(…) връща обект от тип IAsyncResult, който може да използваме за проследяване на състоянието на изпълнение на уеб метода. В случая само увеличаваме стойността на брояч, като след приключване на изпълнението на уеб метода извеждаме стойността му на екрана. Чрез полето IsCompleted на обекта, върнат от извикването на метода BeginSlowCalculation(…), проверяваме дали е приключило асинхронното изпълнение на уеб метода.

Методът CallFinished(…) приема аргумент също от тип IAsyncResult, като този обект се използва при извикването на втория метод EndSlowCalculation(…), с който получаваме резултата, върнат от асинхронното извикване. Резултатът от изпълнението на горния пример е следният:


Уеб услуги и работа с данни


Много често уеб услугите се ползват като междинен слой в трислойните приложения. В един такъв сценарий тяхната основна задача е извлича­нето и обработката на данни от базата данни, както и приемането на данни и вкарването им обратно в базата. Чрез тях най-често се реализира основната бизнес логика на приложенията.

Възможността на уеб услугите да приемат и връщат почти всякакъв тип данни, предоставя изключително голямо разнообразие от области на при­ложение. В зависимост от приложенията консуматори, най-често се прех­върлят DataSet обекти или масиви от данни. Използването на DataSet, обаче, е свързано най-вече с .NET приложенията и рядко се ползва при хетерогенни системи.

При прило­жения, които не поддържат обекти от тип DataSet, за пренос на данни се използват така наречените обекти за пренос на данни (DTO –Data Transfer Objects). При преноса на данни, тези обекти се сериали­зират под формата на XML, а самият клас на обекта се дефинира в WSDL описанието на услугата. В действителност се сериализират всички публични полета и свойства на обекта.

Когато Visual Studio .NET създава междинен (proxy) клас за услугата, той генерира и клас от типа на обектите за пренос на данни, който е без методи, а се състои само от публични полета. По същия начин, ако услугата се използва не от.NET, а от Java, Perl, PHP или от друга платформа, съответните DTO обекти се преобразуват от XML към обекти от съответния език за програмиране и платформа.


Уеб услугите и работа с данни в .NET


Типичният модел на работа с данни при .NET уеб услуга и .NET клиент е илюстриран схематично на следната фигура:

Типичният сценарий включва следните стъпки:



  1. Клиентското приложение извиква метод от уеб услугата.

  2. В уеб услугата се изпълняват поредица от логически операции и евентуално SQL заявка към базата от данни.

  3. Резултатът от SQL заявката се връща на уеб услугата, където той се оформя във вид на DataSet обект или обект за пренос на данни.

  4. Този обект се сериализира в XML и в такава форма се връща на клиента.

  5. Клиентското приложение извършва промяна на данните и отново под формата на XML ги връща обратно на уеб услугата за запис.

  6. Услугата, от своя страна, изпълнява друга заявка към базата от данни, с която записва променените данни.

Уеб услугите и работа с данни в .NET – пример


За да демонстрираме работата с данни в уеб услугите, ще създадем проста уеб услуга, съдържаща само два уеб метода. Единият ще служи за извличане на данните от базата, а другия за тяхната промяна. След това ще създадем и клиент за уеб услугата, който чете данните, прави промени по тях и връща промените обратно в уеб услугата

Уеб услуга за работа с данни в .NET


При изграждането уеб услугата ще използваме един SqlDataAdapter за достъп и промяна на данните от таблицата "Categories" на базата данни "Northwind" в MS SQL Server.

Чрез Drag and Drop на таблицата "Categories" създаваме SqlDataAdapter и го именуваме sqlDataAdapterCategories. Visual Studio .NЕТ автома­тично генерира за нас всички команди, които са нужни за достъп до съответната таблица в базата данни. От контекстното меню на sqlDataAdapterCategories създаваме DataSet клас DataSetCategories.

Вече сме готови с DataSet обекта и адаптера за таблицата Categories. Създаваме и два уеб метода GetCategories() и UpdateCategories(…), като вторият метод приема като параметър DataSet, съдържащ промените, нанесени от клиента:

[WebMethod(Description="Gets all category entries as DataSet.")]

public DataSet GetCategories()

{

DataSetCategories dsCategories = new DataSetCategories();



sqlDataAdapterCategories.Fill(dsCategories);

return dsCategories;

}
[WebMethod(Description="Updates the category entries by given change list.")]

public void UpdateCategories(DataSet aDatasetCategoriesChanges)

{

sqlDataAdapterCategories.Update(aDatasetCategoriesChanges);



}

Така уеб услугата вече е готова и можем да я стартираме чрез натискане на [F5] и да извикаме метода GetCategories(). Като резултат той връща сериали­зиран DataSet обект, съдържащ таблицата Categories, като заедно с него идва и описваща го XSD схема.

Клиент за работа с данни в .NET


Нека сега създадем клиентско Windows Forms приложение, което да извиква методите на уеб услугата. За целта създаваме нов Windows Forms проект, към който добавяме уеб референция към създадената уеб услуга (http://localhost/NorthwindService/NorthwindService.asmx). Преименуваме ге­нерираната форма на MainForm. Като частно поле на формата приба­вяме инстанция на уеб услугата:

private MyServices.NorthwindService mNorthwindService =

new MyServices.NorthwindService();



Във формата добавяме бутона за зареждане на данните, бутон за записване на данните и DataGrid контрола. Кръщаваме ги съответно buttonLoad, buttonSave и dataGridCategories. Към събитието Click на двата бутона прикачваме съответно методите buttonLoad_Click и buttonSave_Click, които извикват съответно LoadData() и SaveData(). При събитието Load на MainForm извикваме също LoadData(). Методите LoadData() и SaveData() изпълняват следния код:

private void LoadData()

{

try



{

// Load the categories table from the Web service

mDsCategories = mNorthwindService.GetCategories();
// Bind the data to the DataGrid control

dataGridCategories.DataSource = mDsCategories;

dataGridCategories.DataMember = "Categories";

}

catch (Exception)



{

MessageBox.Show(

"Can not retrive the categories from the server.",

"Error");

}

}
private void SaveData()



{

try


{

DataSet dsCategoriesChanges = mDsCategories.GetChanges();

if (dsCategoriesChanges != null)

{

// Commit the changes to the Web service



mNorthwindService.UpdateCategories(dsCategoriesChanges);
MessageBox.Show("Categories updated succesfully.", "Info");

}

}



catch (Exception)

{

MessageBox.Show(



"Can not update the categories.", "Error");

}
// Refresh the categories table

LoadData();

}


Методът LoadData() извиква метода на уеб услугата GetCategories(), който връща DataSet обект с таблицата "Categories". Резултатът се при­своява на DataSource свойството на dataGridCategories и на свойството DataMember се присвоява символен низ с името на таблицата – "Categories". Така се извършва свързване на таблицата с DataGrid контролата (data binding). Целият този код е заграден в try-catch блок, в който се прихващат евентуално възникналите изключения.

Методът SaveData() първо взема DataSet, който съдържа променените данни и с този DataSet извиква метода UpdateCategories(…) на уеб услугата и накрая вика отново LoadData(). На услугата се предават само направените промени (ако има). След това се извлича цялата таблица наново, за да се работи с актуални данни. Това зарежда промените, които други потребители междувременно са нанесли в базата данни.




Поддръжка на сесии


По отношение поддръжка на сесии уеб услугите са абсолютно аналогични на уеб приложенията. Сесиите при уеб услугите също представляват инстанции на класа HttpSessionState както и в уеб приложенията. Същи са и обектите за достъп до сесии – Session за достъп до текущата сесия и Application за достъп до контекст на цялото приложение.

Точно както в уеб приложенията и уеб услугите могат да бъдат настрой­вани от уеб конфигурационния файл. Настройките включват дали да се използват Cookies, къде да се държи сесията – в паметта, в SQL Server, или на друго място, след колко време да изтече сесийното Cookie и т. н.

Ако искаме да не използваме cookie, задаваме true в атрибута cookieless на тага sessionState. Трябва да се има предвид, че при използването на cookieless сесия се получават известни проблеми в клиентските прило­жения. Малко по-късно ще се спрем на тях, когато описваме консумацията на уеб услуги, използващи сесии.

За да се разреши на даден уеб метод да използва сесията, е необходимо единствено да се зададе на полето EnableSession на атрибута WebMethod стойност true. По подразбиране всички уеб методи не поддържат сесия.


Сесии в уеб услуги – пример


За да демонстрираме как се използват сесии в уеб услугите, ще създадем примерна уеб услуга, която да използва обектите Session и Application. Тя ще се състои от два уеб метода – GetSessionCounter() и GetApplicationCounter():

[WebMethod(EnableSession=true, Description="Returns the next value of the local session counter.")]

public int GetSessionCounter()

{

int counter = 0;



if (Session["counter"] != null)

{

counter = (int) Session["counter"];



}

counter++;

Session["counter"] = counter;

return counter;

}
[WebMethod(Description="Returns the next value of the global application counter.")]

public int GetApplicationCounter()

{

Application.Lock();



try

{

int counter = 0;



if (Application["counter"] != null)

{

counter = (int) Application["counter"];



}

counter++;

Application["counter"] = counter;

return counter;

}

finally


{

Application.UnLock();

}

}


Методът GetSessionCounter() инициализира един брояч при първо извик­ване, а ако той вече се съдържа в локалната сесия, неговата стойност се взема от там. След това стойността на брояча се увеличава с единица, отново се поставя в сесията и се връща като резултат от извикването на метода. Целта е да се демонстрира, че в сесията може да се държи информация, която се запазва между отделните извиквания.

Тъй като Application обектът е общ за всички инстанции на уеб услугата, чиито методи се изпълняват понякога едновременно, е необходимо достъпът до него да е синхронизиран. Методът GetApplicationCounter() първо извършва точно това – забранява достъпа до обекта от други инстанции на услугата и след това прави абсолютно същото както и GetSessionCounter(), като накрая отново разрешава достъпа към Application обекта.


Сесии в уеб услуги – клиентско приложение


В зависимост от вида на сесията на уеб услугата се разрешава и използ­ването на сесии в клиентско приложение. Ако сесията на услугата използва Cookies, единственото, което трябва да се направи при клиента, е да се добави System.Net.CookieContainer към уеб услугата:

mSessionService = new SessionService();

mSessionService.CookieContainer =

new System.Net.CookieContainer();


Нека създадем просто Windows Forms приложение, чиято форма се състои само от две текстови полета – textBoxNextSessionCounter и textBoxNextAppCounter и два бутона – buttonNextSessionCounter и buttonNextAppCounter. При събитието Click на бутоните се изпълняват съответно методите:

private void buttonNextSessionCounter_Click(object sender, System.EventArgs e)

{

int nextValue = mSessionService.GetSessionCounter();



textBoxNextSessionCounter.Text = nextValue.ToString();

}
private void buttonNextAppCounter_Click(object sender, System.EventArgs e)

{

int nextValue = mSessionService.GetApplicationCounter();



textBoxNextAppCounter.Text = nextValue.ToString();

}


Обработчиците за натискане на бутон извикват създадените в уеб услу­гата методи и поставят резултата в съответните текстови полета.

Ще тестваме приложението, като стартираме две негови инстанции чрез натискане на [Ctrl+F5] от VS.NET два пъти последователно. Като натис­каме последователно бутона за Next Session Counter на двата прозоре­ца, виждаме, че стойностите нарастват последователно и в двата прозо­реца. Обаче, ако направим същото с бутона Next Application Counter, стойностите не нарастват последователно. Всяка стойност върната от GetApplicationCounter() е с единица по-голяма от предишната, незави­симо кой извиква метода. Причината е в това, че този метод ползва Application обекта, който е общ за всички инстанции на уеб услугата:




Cookieless сесии към уеб услуга


При уеб услуга със сесии без Cookies ситуацията става по-сложна. Това, за което по принцип се използват Cookies, е в тях да се съхранява уника­лен идентификатор на сесията. При уеб приложенията, ако сесията не използва Cookies, този идентификатор се пренася чрез URL адреса на приложението. При извикването на уеб услугата от клиентско приложение се изпълнява проста HTTP заявка, в резултат на което уеб услугата съвсем нормално приема, че приложението в същност е Browser и в отговор връща стандартен HTTP отговор "302 Found", а не "200 ОК", както очаква клиентското приложение. В резултат на това се хвърля WebException изключение.

Как да решим този проблем? Една от възможностите е да прихващаме изключението и го обработваме по подходящ начин:



private Uri mWebServiceUrl;
// Some Code
private void buttonNextSessionCounter_Click(object sender, System.EventArgs e)

{

if(mWebServiceUrl == null)



{

mWebServiceUrl = new Uri(mSessionService.Url);

}

else


{

mSessionService.Url = mWebServiceUrl.AbsoluteUri;

}

int nextValue = 0;



try

{

nextValue = mSessionService.GetSessionCounter();



textBoxNextSessionCounter.Text = nextValue.ToString();

}

catch(WebException webException)



{

HttpWebResponse httpResponse =

webException.Response as HttpWebResponse;

if ( httpResponse != null )

{

if(httpResponse.StatusCode == HttpStatusCode.Found)



{

mWebServiceUrl = new Uri(mWebServiceUrl,

httpResponse.Headers["Location"]);

buttonNextSessionCounter_Click(sender, e);

}

else


{

throw webException;

}

}

else



{

throw webException;

}

}

}



За да решим проблема използваме една външна променлива mWebServiceUrl, в която държим променения адрес на уеб услугата. В началото на метода се проверява дали тази променлива е null и ако е, тя се създава с текущия адрес на уеб услугата. Ако не е, то уеб услугата не използва адреса си по подразбиране, а друг, зависещ от идентификатора на сесията на услугата. Тогава, адресът на уеб услугата, се заменя с адреса от променливата mWebServiceUrl. Хвърленото изключение има свойство Response, което държи HTTP отговора, върнат от уеб сървъра. Ако в действителност се окаже, че отговорът е "302 Found", т. е. неговият StatusCode е HttpStatusCode.Found, тогава от него се взема новият адрес и се извиква отново функцията.

Разбира се, това решение не е стандартно и трябва да се избягва! То е породено от създадения проблем. Принципно рядко се използват сесии в уеб услугите, които да са cookieless.


Ръчна реализация на сесии чрез параметри


Съществува възможност и ръчно да се реализира управлението на сесията. Това може да стане, като се дефинира допълнителен уеб метод CreateSession() в уеб услугата, който да връща уникален идентификатор на нова сесия. След това този идентификатор може да се изисква да бъде подаван като параметър при извикването на всички останали методи и той да се използва като ключ в Application обекта за съхранение на данните от сесията на различните потребители. Трябва, обаче да се помисли и за сигурността ­– активните сесии трябва автоматично да се изтриват при продължителна липса на активност (примерно 5 минути), трябва да бъдат достатъчно случайно генерирани, за да бъде трудно откриването им и т.н. Този подход ще демонстрираме в практическия проект в последната тема от книгата (вж. "Практически проект" [TODO: link]).

Сигурност на уеб услугите


Уеб услугите се използват в много различни ситуации. От съвсем прости двуслойни приложения, състоящи се от едно клиентско приложение, което да осигурява потребителския интерфейс и уеб услуга, която реализира цялата бизнес логика и достъпа до базата, до съвсем сложни архитектури, изградени изцяло на базата на уеб услуги, взаимодействащи една с друга.

Както всяка една услуга, вие можете да предложите вашите уеб услуги на ваши клиенти през Интернет. Достъпът в такъв случай може да бъде позволен само срещу заплащане. В такива ситуации сигурността е изклю­чител­но важна. Проблемът как да ограничим достъпа до някои уеб методи само за оторизирани потребители има няколко решения. Нека ги разгле­даме и обясним техните силни и слаби страни.


Сигурност чрез SSL/HTTPS


Един от простите начини да защитим надеждно цялата комуникация, извършвана между уеб услуга и клиентско приложение, е да използваме криптиран канал (SSL tunnel) за пренасяните данни. Така отговорността за автентикацията и за криптирането на трафика не е на уеб услугата, а на уеб сървъра, върху който тя е публикувана.

Този подход е лесен за имплементация и осигурява много високо ниво на сигурност. Възможно е да се използва автентикация с цифров сертификат, както от страна на сървъра пред своите клиенти, така и от страна на клиентите пред сървъра. Един от проблемите е, че цифровите сертифи­кати струват скъпо, а ако се използват саморъчно подписани (self-signed) сертификати, сигурността може да бъде компрометирана.


Сигурност чрез предаване на допълнителни параметри


Този подход е свързан с подаване на допълнителни параметри към уеб метода. Това може да са потребителско име и парола, уникален за потре­бителя идентификатор и др. При извикване на всеки уеб метод, той ще трябва да проверява дали подадените му параметри са коректни. Този подход може да се прилага, когато уеб методите, изискващи оторизиран достъп, не са много на брой, но крие известни рискове. Тъй като съобще­нията, които се предават са в XML и няма криптиране, тогава става много лесно някой да прочете тези допълнителни данни, които се подават към уеб метода.

Възможно е посоченият метод да се модифицира, така че паролата да не пътува в чист вид, а автентикацията да се извършва по сигурен начин по схемата "Challenge/Response" (вж. http://en.wikipedia.org/wiki/Challenge-response_authentication).

Възможно е също автентикацията да се извършва еднократно, при което клиентът да получава специален идентификатор (ticket), с който да се автентикира след това пред уеб методите (да го подава като допълни­телен параметър на всеки уеб метод). Този идентификатор може да служи едновременно и за поддръжка на клиентска сесия.

Сигурност чрез сесии


Сигурността чрез сесии е един значително по-удобен за прилагане метод, отколкото чрез допълнителни параметри. При него автентикацията на потребителя не става при всяко извикване на всеки уеб метод, а само при извикване на конкретен уеб метод (например Login()), отговарящ точно за това. Той проверява дали потребителското име и паролата са валидни и ако е така, отбелязва това в ASP.NET сесията (HttpSessionState обекта) по някакъв начин, например като постави в нея потребителското име под някакъв ключ. Когато се извика някой уеб метод от потребителя, този уеб метод проверява дали в сесията е отбелязана автентикацията на потребителя и ако е така продължава изпълнението си.

За разлика от първия метод, този не изисква всеки път да се подават допълнителни параметри за автентикация, което доста улеснява клиент­ското приложение.

От гледна точка на сигурността, обаче, не е кой знае колко по-сигурно. Фактът, че не при всяко извикване на метод се подава конфиденциална информация, до някъде затруднява достъпа до нея на хакери, но не решава проблема. При извикване на основния метод за автентикация все пак се предават в чист текст потребителското име и парола.

При автоматично управление на сесията е много по-трудно да се реали­зира Challenge/Response схемата за автентикация, която защитава паро­лата на клиента от "подслушване по пътя".


Сигурност чрез сесии – примерна услуга


За да демонстрираме този подход, ще направим съвсем проста уеб услуга, служеща си с ASP.NET сесията за осигуряване на достъп с автентикация до някои от уеб методите:

const string USER_NAME_SESSION_KEY = "UserName";
[WebMethod(EnableSession=true, Description =

"Checks given credentials and establishes a session.")]

public void Login(string aUserName, string aPassword)

{

if (IsLoginValid(aUserName, aPassword))



{

Session[USER_NAME_SESSION_KEY] = aUserName;

}

else


{

throw new AuthenticationException("Ivalid credentials!");

}

}
[WebMethod(EnableSession=true, Description =



"Terminates the active session (if any).")]

public void Logout()

{

Session.Abandon();



}
[WebMethod(EnableSession=true, Description =

"Returns some protected data. Requires active session.")]

public string GetProtectedData()

{

CheckSecurity();



return "This data is protected!";

}
[WebMethod(Description="Returns some data. Does not require a session.")]

public string GetNotProtectedData()

{

return "This data is not protected.";



}
private void CheckSecurity()

{

string currentUser = (string) Session[USER_NAME_SESSION_KEY];



if (currentUser == null)

{

throw new AuthenticationException(



"Access denied! Please login first!");

}

}


private bool IsLoginValid(string aUserName, string aPassword)

{

// Just for the demo check if the user and password are equal



return (aUserName == aPassword);

}
public class AuthenticationException : ApplicationException

{

public AuthenticationException(string aMessage) :



base(aMessage) {}

}


В началото декларираме една константа от тип string, която ще ползваме като ключ, под който ще записваме в сесията името на текущия автенти­киран потребител. Основният метод за автентикация е Login(…). Той извиква метода IsLoginValid(…) с подадените му потребителско име и парола. В зависимост какво върне IsLoginValid(…) или се поставя в сесията потребителското име или се хвърля AuthenticationException.

Методът IsLoginValid(…) е изключително опростен. Той само проверява дали потребителското име съвпада с паролата. В реална ситуация провер­ката може да се извърши в база данни или по друг начин.

В горния пример има два метода, които демонстрират достъпа до защитени методи и до незащитени методи. В началото на метода GetProtectedData() се проверява дали потребителят има право на достъп. Методът CheckSecurity() в действителност проверява дали обекта в сесията с ключ "UserName" е null. Ако е така, се хвърля AuthenticationException. За да се имплементира функционалността на излизане от системата, в примера има метод Logout(), който премахва активната ASP.NET сесия.

Сигурност чрез сесии – примерен клиент за услугата


Нека сега да построим едно съвсем елементарно Windows Forms клиентско приложение, демонстриращо работата на представената по-горе уеб услу­га. Потребителският му интерфейс ще представлява две текстови полета, в които ще се въвеждат потребителско име и парола, и четири бутона – по един за всеки уеб метод на услугата. При стартиране на приложението, при зареждане на главната му форма, се инстанцира променлива за уеб услугата и й се присвоява контейнер за cookies:

private void MainForm_Load(object sender, System.EventArgs e)

{

// Instantiate the Web Service proxy class



mSecuredService = new Services.SecuredService();
// Add cookies container to the service proxy

mSecuredService.CookieContainer =

new System.Net.CookieContainer();

}


При натискане на бутона [Login] се вземат стойностите на текстовите полета, извиква се уеб методът Login(…) и ако изпълнението му мине успешно, се извежда съобщение за успех. В противен случай се извежда съобщение за грешка:

private void buttonLogin_Click(

object sender, System.EventArgs e)

{

string user = textBoxUserName.Text;



string pass = textBoxPassword.Text;

try


{

mSecuredService.Login(user, pass);

MessageBox.Show("Login successfull.", "Info");

}

catch (SoapException se)



{

MessageBox.Show(se.Message, "Error");

}

}


При натискане на бутона [Get Protected Data] преди да се е автенти­кирал успешно клиентът, излиза съобщение за грешка. Ако клиентът, обаче се е автентики­рал преди това, уеб методът се изпълнява успешно и върнатият от него резултат се визуализира:

private void buttonGetProtectedData_Click(

object sender, System.EventArgs e)

{

try


{

string data = mSecuredService.GetProtectedData();

MessageBox.Show(data, "Info");

}

catch (SoapException se)



{

MessageBox.Show(se.Message, "Error");

}

}


При натискане на бутона [Get Not Protected Data] се извиква съответ­ният уеб метод и се извежда съобщение с резултата:

private void buttonGetNotProtectedData_Click(

object sender, System.EventArgs e)

{

string data = mSecuredService.GetNotProtectedData();



MessageBox.Show(data, "Info");

}


Бутонът [Logout] извиква уеб метод, който прекратява активната сесия:

private void buttonLogout_Click(

object sender, System.EventArgs e)

{

mSecuredService.Logout();



MessageBox.Show("Logout successfull.", "Info");

}


Ето как изглежда клиентското приложение в действие:


Сигурност чрез средствата на Web Service Enhancements (WSE)


Този подход ни предлага най-добро обезпечаване на сигурността при изграждане и използване на уеб услуги. WSE е голяма библиотека от класове, отговорни за приемането и предаването на SOAP пакети между приложенията и уеб услугите. WSE всъщност представлява разширение на .NET Framework и може безплатно да се изтегли от сайта на Майкрософт. След инсталация се интегрира във VS.NET. Очаква се в бъдещи версии да бъде добавен като стандартна част от .NET Framework и VS.NET.

По отношение на сигурността WSE предлага множество от начини за защита на предаваната информация. Чрез WSE много лесно може да се реализира криптиране на трафика, цифрово подписване, автентикация с парола и чрез цифрови сертификати без да се налага да се пише код за това.


Изключенията в уеб услугите


Обработката на изключенията в уеб услугите не е тривиална. Както вече разгледахме, SOAP стандартът дефинира начин за указване на въз­ник­на­ла­та при изпълнение на услугата грешка, като информацията за нея се поставя в елемента fault на SOAP тялото.

Жизненият цикъл на едно SOAP съобще­ние


За да си изясним как точно ASP.NET обработва изключенията, нека да се спрем малко по-подробно на жизнения цикъл на SOAP съобщенията:

На фигурата може да проследим жизнения цикъл на едно SOAP съобще­ние. Фазите, през които минава то, са следните:



  • Фаза 1 – прокси класът, разположен при клиента, сериализира напра­ве­ната заявката във валидно SOAP съобщение и го изпраща към сървъра.

  • Фаза 2 – ASP.NET десериализира полученото съобщение и изпълнява съответния метод от уеб услугата.

  • Фаза 3 – полученият резултат от изпълнението на услугата се сериа­лизира в SOAP съобщение и се изпраща към клиента.

  • Фаза 4 – отново прокси класът получава съобщението, десериа­ли­зи­ра го и подава получения резултат на извикващия метод от клиент­ското приложение.

Всички изключения се заместват с SoapException


Когато на сървъра възникне изключение, ASP.NET автоматично го прихва­ща и го обработва вътрешно. Този процес включва конструиране на ва­лидно SOAP съобщение и сериализиране на информация за възникна­ла­та грешка във fault елемента на SOAP пакета.

Когато съобщението стигне до клиента, прокси класът автоматично парсва неговото тяло и спрямо информацията във fault елемента конструира SoapException.

Всичко изглежда идеално на пръв поглед, но както и в реалния живот, всъщност не е така. При конструирането на SoapException единстве­ното, което се запазва от възникналото изключение на сървъра, е свойството му Message, което съдържа единствено и само текстово описание на греш­ката. Всякаква друга информация за изключението е безвъзвратно изгу­бена – губи се както оригиналният тип на изключението, така и вложе­ни­те изключения (свойството InnerException има стойност null).

Например, ако даден уеб метод хвърли DivideByZeroException, клиентът вместо да получи изключение от същия тип, получава SoapException. Това сериозно възпрепятства използването на пълната мощ на изклю­че­ни­ята като съвременно средство за обработка на грешки и проблемни ситуации.


Решението на проблема с изключенията


Не си мислете, че ще оставим този проблем отворен. Напротив, ще ви пред­ложим две негови решения.

Първото решава проблема по малко заобиколен начин (workaround), като прави услугата и клиента зависими. За разлика от него, второто предос­тавя прозрачен начин за запазване на възникналото изключение, но самото е по-сложно и изисква добро познаване на уеб услугите и техно­логиите, свързани с тях.


Решение 1 – чрез код за грешка


Ще използваме следния подход: Нашата цел е да върнем информация към клиента, ако на сървъра възникне проблем. За целта дефинираме изборен тип (enum), който съдържа всички възможни грешки, които могат да възникнат при изпълнението на даден уеб метод.

След това в уеб метода, където очакваме да възникне изключението, го прихващаме и връщаме съответния му код на грешка – инстанция на изброения тип, който сме дефинирали.

Когато съобщението пристигне при клиента, той може да провери кода на грешката и да извърши някакво действие спрямо него. Няма да се спи­раме подробно на това решение, защото неговата реализация може да се разгледа подробно в практическия проект (вж. темата "Практически проект" [TODO: link]).

Ще отбележим само някои от недостатъците на този подход. Основният от тях е, както вече споменахме, че клиентът и услугата стават тясно зави­сими, защото клиентът е длъжен да проверява всеки път възникналия код за грешка. Друг недостатък е загубата на всякакви вложени изклю­чения. Много сериозен проблем е липсата на възможност за дефиниране код на грешка за изключения от общ тип, като например: ArithmeticException, ArgumentException, IndexOutOfRangeException и др. Губи се и възмож­ност­та грешките да се обработват на много нива. На практика този подход ни връща в епохата на процедурното програмиране, при което функциите връщат код на грешка.


Решение 2 – чрез SOAP разширение


Второто решение преодолява проблемите на първото, като се възползва от разширяемата структура на SOAP стандарта и предоставените ни за целта класове от .NET Framework.

Накратко идеята, която ще реализираме е следната: с помощта на разширение тип SoapExtension ще прихванем получените в уеб услугата изклю­че­ния и ще ги сериализираме в изходя­щото SOAP съобщение.

При клиента ще дефинираме SoapInputFilter (входящите и изходящите филтри са една от функционалностите предоставени ни от WSE), който ще десе­риализира изключението и ще го хвърля локално в клиентското при­ложение. Нека анализираме проблемите, свързани с реализацията на тази идея, и предложим конкретна имплементация.

Сървърна част


Както вече споменахме, на сървъра ще използваме SoapExtension, благо­да­рение на който ще сериализираме изключението. Класът SoapExtension ни предлага функционалността да разширим SOAP съобщението, като се намесим в различните стадии от неговата обработка на сървъра.

Етапите, които можем да прихванем, са следните: BeforeDeserialize, AfterDeserialize, BeforeSerialize и AfterSerialize, като името на всеки указва кога точно се изпълнява (припомнете си картинката с жизне­ния цикъл на SOAP съобщението). Ето примерен код, който дефинира нашето разширение SerializedExceptionExtension и осигурява сериали­зиране на възникналото изключение:



public class SerializedExceptionExtension : SoapExtension

{

Stream mOldStream;



Stream mNewStream;
public override Stream ChainStream(Stream aStream)

{

mOldStream = aStream;



mNewStream = new MemoryStream();

return mNewStream;

}
public override void ProcessMessage(SoapMessage aMessage)

{

if (aMessage.Stage == SoapMessageStage.AfterSerialize)



{

mNewStream.Position = 0;

if (aMessage.Exception != null)

{

if (aMessage.Exception.InnerException != null)



{

InsertDetailIntoOldStream(

aMessage.Exception.InnerException);

}

}



else

{

CopyStream(mNewStream, mOldStream);



}

}

else if (aMessage.Stage ==



SoapMessageStage.BeforeDeserialize)

{

CopyStream(mOldStream, mNewStream);



mNewStream.Position = 0;

}

}


private void InsertDetailIntoOldStream(Exception aException)

{

XmlDocument doc = new XmlDocument();



doc.Load(mNewStream);

XmlNode detailNode = doc.SelectSingleNode("//detail");


try

{

detailNode.InnerXml =



GetSerializedExceptionXmlElement(aException);

}

catch (Exception exception)



{

// Unable to serialize the exception

detailNode.InnerXml = exception.Message;

}
XmlWriter writer =

new XmlTextWriter(mOldStream, Encoding.UTF8);

doc.WriteTo(writer);

writer.Flush();

}
private string GetSerializedExceptionXmlElement(

Exception aException)

{

StringWriter stringWriter = new StringWriter();



XmlWriter xmlWriter = new XmlTextWriter(stringWriter);
xmlWriter.WriteStartElement("Serialized");

xmlWriter.WriteString(SerializeException(aException));

xmlWriter.WriteEndElement();

return stringWriter.ToString();

}
private string SerializeException(Exception aException)

{

MemoryStream stream = new MemoryStream();



IFormatter formatter = new SoapFormatter();

formatter.Serialize(stream, aException);

stream.Position = 0;

return Encoding.UTF8.GetString(stream.GetBuffer());

}
private void CopyStream(Stream aFrom, Stream aTo)

{

TextReader reader = new StreamReader(aFrom);



TextWriter writer = new StreamWriter(aTo);

writer.WriteLine(reader.ReadToEnd());

writer.Flush();

}
// SoapExtension methods implementation

public override object GetInitializer(LogicalMethodInfo

aMethodInfo, SoapExtensionAttribute aAttribute)

{

return null;



}
public override object GetInitializer(Type aServiceType)

{

return null;



}
public override void Initialize(object aInitializer)

{

}



}

За да дефинираме клас, който може да се използва като разширение (extension) на SOAP, трябва да наследим абстрактния клас SoapExtension, който се намира в пространството от имена (namespace) System.Web. Services.Protocols. Наследявайки SoapExtension трябва да припокрием (override) абстрактните му методи: Initialize(…), GetInitializer(…) и ProcessMessage(…). Първият от тях (той има две декларации с различни параметри) се използва за инициализиране на данни, които ще се използват вътрешно в SOAP разширението. В нашия случай няма да гo използваме. Вторият метод ProcessMessage(…) е най-същественият. В него се извършва обработката на съобщението.

Преди да преминем към по-детайлното му разглеждане, нека да обърнем внимание на метода ChainStream(…). Той е деклариран като виртуален в SoapExtension и е единственият начин, чрез който можем да получим поток към текущото SOAP съобщение. Тъй като искаме да модифицираме този поток, трябва да запазим референция към него (mOldStream) и да направим нов поток (mNewStream), в който ще запишем нашето модифици­рано съобщение.

Да преминем към ключовия метод ProcessMessage(…). В него трябва да направим две неща: първо, ако етапът от обработката, в който се намира съобщението, е BeforeDeserialize (току що получено и още не десериа­лизирано), копираме входящия поток (mOldStream) в конструирания нов поток (mNewStream) чрез помощния метод CopyStream(…).

Ако етапът от обработката е AfterSerialize (изходящото съобщение е сериализирано и е готово за изпращане към клиента), проверяваме за възникнало изключение. Ако няма такова, просто копираме новия поток обратно в стария. Ако пък е възникнало изключение, намираме detail елемента в SOAP грешката (fault), която се е генерирала, и в него сериализираме изключението (припомнете си, че detail елемента може да съдържа XML). Отново, вече промененото съобщение записваме в стария поток.

Обърнете внимание, че използваме бинарна сериализация и класа SoapFormatter, а не XmlSerializer. Бинарната сериализация позволява да се сериализират не само публичните полета на изключението, а цялото му състояние (включително и частните вътрешни полета). SOAP форма­терът позволява резултатът от сериализацията да е във вид на XML.

Класът System.Runtime.Serialization.Formatters.Soap.SoapFormatter е дефиниран в асемблито System.Runtime.Serialization.Formatters. Soap.dll и преди да се използва трябва да добавим ръчно към проекта референция към това асембли.





Имайте предвид, че при сериализацията на изключението, всички негови свойства се предават в текстов вид. Ако тази информация е конфиденциална, тялото на SOAP съ­общението може да се криптира.

След като нашето SOAP разширение е готово, нека да разгледаме по какъв начин може да го приложим в уеб услугата. Вариантите са два: чрез атрибути да укажем към кои уеб методи да се прилага или чрез конфигу­ра­ционна настройка да го приложим върху всички уеб методи. Ще разгле­даме и двата варианта.

Настройка в Web.config файла


За да добавим SOAP разширението към всички уеб методи, трябва да добавим следните редове в конфигурационния файл на ASP.NET уеб услугата (Web.config), в секцията system.web:





priority="1" group="0" />







Атрибутът type указва, кой е класът, който ще се използва, и в кое асембли се намира той. Другите два атрибута priority и group указват приоритета и последователността на изпълнение на SOAP разширенията (ако има повече от едно).

Настройка чрез атрибут


Нека да разгледаме и втория начин за прилагане на SOAP разширението, което дефинирахме. Този вариант е по-гъвкав, защото ни дава възмож­ността да приложим разширението само върху конкретни уеб методи, а не върху всички. Дефинираме клас (потребителски атрибут), наследник на SoapExtensionAttribute, който да припокрие неговите абстрактни свой­ства Property и ExtensionType. Ето неговият сорс код:

[AttributeUsage(AttributeTargets.Method)]

public class SerializedExceptionExtensionAttribute : SoapExtensionAttribute

{

public override Type ExtensionType



{

get


{

return typeof(SerializedExceptionExtension);

}

}
public override int Priority



{

get { return 0; }

set { }

}

}



На по-късен етап, ако приложим към даден уеб метод този атрибут, той ще включи за него SOAP разширението.

Ето накрая и кода на уеб метода върху, към който сме приложили атрибута [SerializedExceptionExtension]. В този метод съвсем умиш­лено пре­дизвиква­ме изключение DivideByZeroException, за да илюстри­раме неговата сериализация и предаване към клиента на уеб услугата:



[WebMethod]

[SerializedExceptionExtension]

public int ThrowException()

{

int zero = 0;



return 100 / zero;

}

Тестване на SOAP разширението


Ако сега сложим точка на прекъсване (breakpoint) в нашето SOAP разширение и извикаме уеб метода на услугата през тестовата страница на услугата от Internet Explorer, SOAP Extension класът няма да се изпъл­ни. Причината за това е в начина, по който се извиква услугата.



При извикване на уеб услуга през нейната тестващата уеб страница (която се показва при стартиране услугата от VS.NET) уеб методите се изпълняват с директна HTTP GET заявка, а не чрез SOAP заявка. Това е причината SOAP разширенията да не се изпълняват при извикването на уеб методи по този начин.

Ако искаме да тестване уеб услугата заедно с прикаченото към нея SOAP раз­ширение, трябва да напишем клиентско приложение, което я консу­мира.

Клиентска част


След като вече сме сериализирали възникналото изключение във валидно SOAP съобщение, нека да разгледаме какво ни трябва при клиента, за да направим целия процес напълно прозрачен. Нашата цел е да прихванем съобщението, преди то да е стигнало при клиента, да проверим за възникнала грешка и ако има такава, да я десериализираме и да я хвърлим като локално изключение.

Както вече споменахме, ще използваме една от функционалностите на WSE – входя­щи­те филтри. (Повече подробности относно WSE може да намерите на адрес: http://msdn.microsoft.com/webservices/, където може да се свали и актуалната им версия). На следващата картинка можем да видим опростената архитектура, на която се базират WSE, а именно прилагане на дадени филтри върху изходящите съобщения и обратното им налагане върху входящите.



Ако вече имаме инсталирана версия на WSE, можем да преминем към създаването на нашия филтър. Но преди да преминем към кода, нека първо да разрешим използването на WSE в нашия проект, който в случая е просто конзолно приложение. За целта с десния бутон на мишката щракаме върху проекта и от появилото се контекстно меню избираме WSE Settings X …, където X е текущо инсталираната версия на WSE. От появилия се диалогов прозорец маркираме Enable this project for Web Services Enchantments и натискаме [OK]:



WSE автоматично добавят конфигурационен файл за приложението (ако няма такъв), разполагат своите настройки в него и добавят референция към тяхното асембли в проекта.

Да преминем към кода на входящия филтър, който ще десериализира възникналото изключение, ако има такова:

public class DeserializeExceptionInputFilter : SoapInputFilter

{

public override void ProcessMessage(SoapEnvelope aEnvelope)



{

if (aEnvelope.Fault != null)

{

XmlDocument doc = new XmlDocument();



doc.LoadXml(aEnvelope.InnerXml);
XmlNode detailNode = doc.SelectSingleNode("//detail");

if (detailNode != null)

{

string serialized =



GetNodeText(detailNode, "Serialized");

if (serialized != string.Empty)

{

Exception exception =



DeserializeException(serialized);

if (exception != null)

{

throw exception;



}

}

}



}

}
private string GetNodeText(XmlNode aParent, string aNodeName)

{

XmlNode node = aParent.SelectSingleNode(aNodeName);



if (node != null)

{

return node.InnerText;



}

return string.Empty;

}
private Exception DeserializeException(

string aSerializedException)

{

byte[] buffer =



Encoding.UTF8.GetBytes(aSerializedException);

MemoryStream stream = new MemoryStream(buffer);


IFormatter formatter = new SoapFormatter();

return formatter.Deserialize(stream) as Exception;

}

}


За да може даден клас да се използва като входящ филтър, той трябва да наследява абстрактния клас SoapInputFilter. Отново ключовият метод е ProcessMessage(…), като в него проверяваме дали има SOAP грешка. Ако има такава, намираме "detail" елемента, взимаме съдържанието на него­вия поделемент "Serialized" (споменете си, че като коренов елемент при сериализацията на сървъра, създадохме елемент именно с това име). Съдържанието на елемента всъщност е сериализираното изключение. Десериализираме го и го хвърляме локално в клиентското приложение.

За да добавим така създадения филтър към филтрите на нашия проект отново от менюто на WSE избираме етикета Customized filters и доба­вяме нашия филтър към входящите такива:



Това може да се направи и от конфигурационния файл на приложението (това е файлът App.config за VS.NET проекти). Нека да разгледаме какво са добавили WSE в него:









"Microsoft.Web.Services2.Configuration.

WebServicesConfiguration, Microsoft.Web.Services2,

Version=2.0.0.0, Culture=neutral,

PublicKeyToken=31bf3856ad364e35" />





















Въпреки, че в горния пример името на класа, който имплементира филтъра и името на асемблито, в което този клас е дефиниран, са на различни редове, това е само защото не се събират на един ред. В реална ситуация стойността на атрибута type не трябва да се пренася между редовете.

Остана да разгледаме последната част при клиента – добавяне на референция към уеб услугата. Добавянето не се различава по нищо от обикновеното, единственото по-различно е, че щом WSE са разрешени за проекта, те автоматично генерират нови прокси класове XxxWse, където Xxx е името на прокси класа създаден от нас. Тези класове са нужни за да могат чрез тях да се наложат дефинираните филтри. Ето и кода на клиента, който използва прокси класа създаден от WSE:



class Client

{

static void Main()



{

ExceptionServiceWse service = new ExceptionServiceWse();

try

{

service.ThrowException();



}

catch (DivideByZeroException)

{

Console.WriteLine( "Bravo!" );



}

}

}



Ако сме направили всичко успешно трябва да видим дългоочаквания резултат:

Той показва, че клиентът е прихванал DivideByZeroException, който е подаден от сървъра.


Упражнения


  1. Какви модели за разпределени приложения познавате? Каква е разликата между тях?

  2. Какво представляват уеб услугите? Какъв проблем решава тази технология?

  3. Какво представлява инфраструктурата на уеб услугите? От какво се състои?

  4. Какво представляват UDDI директориите и за какво служат?

  5. Какво е DISCO и за какво служи?

  6. Какво е WSDL и за какво се използва?

  7. Какво е SOAP? От какво се състои? За какво се използва?

  8. Опишете типични сценарии за използване на уеб услуги при .NET базирани приложения.

  9. Без да използвате VS.NET създайте проста уеб услуга (.asmx файл), която по зададена дата връща деня от седмицата (на български език). Инсталирайте уеб услугата на IIS. Тествайте с Internet Explorer. Разгледайте WSDL описанието.

  10. Без да използвате VS.NET създайте клиент (конзолно приложение) за уеб услугата от предходната задача.

  11. С помощта на VS.NET създайте уеб услуга, която приема 2 символни низа и връща колко пъти първият се среща във втория. Дефинирайте уникален namespace за уеб услугата. Задайте подходящо описание на уеб метода й.

  12. С помощта на VS.NET създайте клиент за уеб услугата от предходната задача.

  13. В едно училище учат ученици, разпределени в различни класове. Всички ученици от даден клас изучават някаква съвкупност от учебни предмети и имат по няколко оценки от изпитванията по всеки предмет. Проектирайте релационна схема, която да съхранява информация за учениците, класовете, учебните предмети и оценките. Реализирайте уеб услуга, която изпълнява следните операции (чрез SQL команди към БД):

    • добавяне/изтриване на клас

    • добавяне/изтриване/промяна на ученик, извличане на учениците (от даден клас)

    • добавяне/изтриване на учебен предмет, извличане на учебните предмети (за даден клас)

    • добавяне/изтриване/извличане на оценки (на даден ученик по даден предмет)

Използвайте свързания модел от ADO.NET.

  1. Създайте Windows Forms клиент за уеб услугата от предходната задача. Приложението трябва да визуализира класовете и да позво­лява навигация сред тях. При избор на даден клас трябва да се показват учениците, които го съставят и учебните предмети, които тези ученици изучават. Трябва да се позволява редактиране на учениците и учебните предмети за текущия избран клас. При избор на ученик трябва да се позволява редактиране на оценките му по всеки от учебните предмети. При всяка редакция трябва да се извиква уеб метод от услугата чрез който измененията да се нанасят в базата данни. При промяна на текущия избран клас, трябва да се извличат наново учениците и предметите. При промяна на избрания ученик трябва оценките му да се зареждат наново от уеб услугата.

  2. Създайте уеб услуга, която по зададено цяло число p (p e UInt32) намира и връща броя прости числа в интервала [1…p]. Услугата би трябвало да работи бавно при големи стойности на p. Създайте Windows Forms приложение, което съдържа текстово поле, бутон и списък. При въвеждане на число в текстовото поле и натискане на бутона трябва да се извиква асинхронно уеб услугата за пресмятане на простите числа между 1 и p. При завършване на асинхронно извикване резултатът трябва да се добавя в списъка във формат "Primes in range [1…p] are XXX". Не забравяйте, че асинхронните извиквания използват нишки от Thread Pool-а на .NET Framework, които не трябва да достъпват директно потребителския интерфейс.

  3. Проектирайте релационна схема от таблици в MS SQL сървър, която описва потребители и правата им за достъп в дадена система. Всеки потребител се характеризира с име, login и парола и може да има достъп до подмножество от функциите на системата. Всяка функция в системата си има име и може да е достъпна от подмножество на потребителите. Създайте уеб услуга, която чрез използване на несвър­зания модел на достъп до данни в ADO.NET реализира уеб методи за извличане на данните (във вид на DataSet) и за обновяване на променени данни (съдържащи се в DataSet). Реализирайте Windows Forms приложение, което позволява редактиране на потребителите и техните права използвайки уеб услугата.

  4. Реализирайте системата за управление на потребители и техните права от предходната задача като добавите автентикация в уеб услугата и защитите методите за достъп до данните чрез ASP.NET сесията. Уеб услугата трябва да позволява достъп до защитените методи само на потребителя с име "admin". Първоначално създайте този потребител директно в базата данни на ръка.

Използвана литература


  1. Светлин Наков, Web услуги с ASP.NET – http://www.nakov.com/dotnet/ lectures/Lecture-20-Web-Services-v1.0.ppt

  2. Стоян Йорданов, Web услуги – http://www.nakov.com/dotnet/2003/ lectures/Web-Services.doc

  3. MSDN Training, Developing XML Web Services Using Microsoft® ASP.NET (MOC 2524B)

  4. Keith Ballinger, .NET Web Services: Architecture and Implementation, Addison Wesley, 2003, ISBN 0321113594

  5. Scott Short, Building XML Web Services for the Microsoft .NET Platform, Microsoft Press, 2002, ISBN 0735614067

  6. Damien Foggon, Daniel Maharry, Chris Ullman and Karli Watson , Programming Microsoft .NET XML Web Services, Microsoft Press, 2004, ISBN 0735619123

  7. Building the Next Generation of Service-based Software Systems, MSDN Library – http://msdn.microsoft.com

  8. The ASP Column - Using SOAP Extensions in ASP.NET, MSDN Library – http://msdn.microsoft.com

  9. Consuming a DataSet from an XML Web Service, MSDN Library – http://msdn.microsoft.com

  10. MSDN Library – http://msdn.microsoft.com





Сподели с приятели:
1   2   3   4   5   6   7   8   9




©obuch.info 2024
отнасят до администрацията

    Начална страница