Skip to content

9. Додаток [SimuPaie] – версія 5 – ASP.NET / веб-сервіс


Рекомендована література: довідник [2], «Вступ до C# 2008», розділ 10 «Веб-сервіси»


9.1. Нова архітектура додатка

Багатошарова архітектура додатка Pam наразі має такий вигляд:

Ми будемо її вдосконалювати таким чином:

У попередній архітектурі шари [web], [metier], [dao] виконувалися в одній віртуальній машині.NET, у новій архітектурі шар [web] виконуватиметься в іншій віртуальній машині, ніж шари [metier] та [dao]. Це буде мати місце, зокрема, якщо рівень [web] знаходиться на машині M1, а рівні [metier] та [dao] — на машині M2. Тут маємо архітектуру «клієнт-сервер»:

  • сервер складається з рівнів [metier] та [dao]. Оскільки це веб-сервіс, для його роботи потрібен веб-сервер № 2.
  • Клієнт складається з шару [web]. Для роботи йому потрібен веб-сервер № 1.
  • Клієнт і сервер взаємодіють через мережу TCP/IP за допомогою протоколу HTTP / SOAP. Для цього до архітектури потрібно додати два нових рівні:
    • шар [S], який буде веб-сервісом. Веб-сервіс отримує запити від віддалених клієнтів і використовує рівні [metier] та [dao] для їх виконання. Існує безліч способів побудови TCP/IP-сервісу. Перевага веб-сервісу подвійна:
      • він використовує протокол HTTP, який пропускають брандмауери підприємств та державних установ
      • він використовує стандартний підпротокол HTTP / SOAP, реалізований на багатьох платформах розробки: .Net, Java, PHP, Flex тощо Таким чином, веб-сервіс може «використовуватися» (це загальноприйнятий термін) клієнтами .Net, Java, PHP, Flex тощо
    • Рівень [C], який виступатиме клієнтом віддаленого веб-сервісу. Його завданням буде взаємодія з веб-сервісом [S].

Цю нову архітектуру можна без особливих зусиль вивести з попередніх:

  • шари [metier] та [dao] залишаються без змін
  • шар [web] дещо змінюється, головним чином для посилання на сутності, такі як Employe, FeuilleSalaire, які стали сутностями клієнтського шару [C]. Ці сутності аналогічні сутностям з рівнів [metier] або [dao], але належать до різних просторів імен.
  • Серверний рівень [S] — це клас, який реалізує інтерфейс IPamMetier рівня [metier]. Ця реалізація обмежується викликом відповідних методів рівня [metier]. Методи, реалізовані серверним шаром [S], будуть «виставлені» для віддалених клієнтів, які зможуть їх викликати.
  • Клієнтський рівень [C] буде згенеровано Visual Studio.

Принципи нової архітектури такі:

  • шар [web] продовжує взаємодіяти з шаром [metier] так, ніби той є локальним. Для цього клієнтський шар [C] реалізуєінтерфейс IPamMetier реального шару [metier] і представляється шару [web] як локальний шар [metier]. Окрім згаданої раніше проблеми з просторами імен, рівень [web] не змінюється. У цьому полягає перевага роботи з рівнями. Якби ми створили однорівневий додаток, його довелося б дуже суттєво переробляти.
  • Клієнтський рівень [C] прозоро для рівня [web] передає його запити до віддаленого веб-сервісу [S]. Вона бере на себе весь аспект «мережевої комунікації». Вона отримує відповідь від віддаленого веб-сервісу, яку форматує, щоб передати її шару [web] у тому вигляді, якого той очікує.
  • На стороні сервера веб-сервіс [S] отримує команди від своїх віддалених клієнтів. Він обробляє їх, щоб викликати методи інтерфейсу IPamMetier шару [metier]. Отримавши відповідь від шару [metier], він обробляє її для передачі через мережу клієнту [C]. Рівні [metier] та [dao] не потребують змін.

9.2. Проєкт Visual Web Developer для веб-сервісу

Ми створюємо новий проєкт за допомогою Visual Web Developer:

  • у [1] ми обираємо веб-проект на C#
  • у [2] ми обираємо «Веб-сервісний додаток ASP.NET»
  • у [3] ми даємо назву веб-проекту
  • у [4] вказуємо місце розташування цього проекту
  • у [1] — згенерований проект. Це класичний веб-проект із такими деталями:
    • було вказано, що проект належить до типу «веб-сервіс». Веб-сервіс не надсилає своїм клієнтам веб-сторінок HTML, а дані у форматі XML. Тому сторінка [Default.aspx], яка зазвичай генерується, не була створена.
  • У [2] було згенеровано файл [Service1.asmx] із таким вмістом:

<%@ WebService Language="C#" CodeBehind="Service1.asmx.cs" Class="pam_v5_webservice.Service1" %>
  • (продовження)
    • - тег WebService вказує, що [Service.asmx] є веб-сервісом
    • - атрибут CodeBehind вказує на розташування вихідного коду цього веб-сервісу
    • - атрибут Class вказує на ім’я класу, що реалізує веб-сервіс у вихідному коді

Вихідний код [Service.asmx.cs] веб-сервісу, згенерований за замовчуванням, має такий вигляд:


using System.Web.Services;

namespace pam_v5_webservice
{
  /// <summary>
  /// Короткий опис Service1
  /// </summary>
  [WebService(Namespace = "http://tempuri.org/")]
  [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
  [System.ComponentModel.ToolboxItem(false)]
  // Щоб дозволити виклик цієї веб-служби зі скрипта за допомогою ASP.NET AJAX, видаліть позначки коментарів із наступного рядка. 
  // [System.Web.Script.Services.ScriptService]
  public class Service1 : System.Web.Services.WebService
  {

    [WebMethod]
    public string HelloWorld()
    {
      return "Hello World";
    }
  }
}
  • рядок 8: анотація WebService, завдяки якій клас Service1 із рядка 13 буде оприлюднений як веб-сервіс. Веб-сервіс належить до певного простору імен, щоб уникнути ситуації, коли два веб-сервіси у світі матимуть однакову назву. Згодом нам доведеться змінити цей простір імен.
  • рядок 13: клас Service1 походить від класу WebService фреймворку .NET.
  • рядок 16: анотація WebMethod забезпечує, що метод, позначений цією анотацією, буде доступний віддаленим клієнтам, які зможуть його викликати.
  • рядки 17–20: метод HelloWorld є демонстраційним. Пізніше ми його видалимо. Він дозволяє нам провести перші тести та ознайомитися з інструментами Visual Studio, а також з деякими аспектами, які слід знати про веб-сервіси.
  • у [1] ми запускаємо веб-сервіс [Service.asmx]
  • VS Web Developer запустив свій вбудований веб-сервер і налаштував його на прийом запитів на випадковому порту, у даному випадку 1599. Потім до веб-сервера було надіслано запит за адресою [2]. Це адреса тестової сторінки веб-сервісу.
  • [3] — посилання, за яким можна переглянути файл опису веб-сервісу. Цей файл, який називається WSDL (WebService Description Language) через своє розширення (.wsdl), є файлом XML, що описує методи, які надає веб-сервіс. Саме на основі цього файлу WSDL клієнти можуть дізнатися:
    • простір імен веб-сервісу
    • перелік методів, що надаються веб-сервісом
    • параметри, які очікуються для кожного з них
    • відповідь, що повертається кожним із них
  • у файлі [4] — єдиний метод, що надається веб-сервісом .
  • у [5] — вміст файлу WSDL, отриманого за посиланням [3]. Слід звернути увагу на URL та [6]. Знання про них є необхідним для користувачів веб-сервісу.
  • в [7], сторінка, отримана за посиланням [4], дозволяє викликати метод [HelloWorld] веб-сервісу
  • у [8], отриманий результат: відповідь XML. Зверніть увагу на URL [9] методу.

Вивчення попередніх сторінок дозволяє зрозуміти, як викликається метод веб-сервісу та який тип відповіді він повертає. Це дає змогу писати клієнти HTTP, здатні взаємодіяти з веб-сервісом. Більшість сучасних IDE дозволяють автоматично генерувати цей клієнт HTTP, що позбавляє розробника необхідності писати його самостійно. Це, зокрема, стосується Visual Studio Express.

Перш ніж продовжувати роботу над цим проєктом, ми змінимо простір імен, який використовується за замовчуванням під час генерації класів:

Коли ми відкриваємо властивості проєкту (правий клік на проєкті / Властивості), у полі [1] ми бачимо назву проєкту l'assembly, а у полі [2] — його простір імен за замовчуванням.

Після цього

  • у [Service1.asmx.cs] змінюємо простір імен класу:

using System.Web.Services;

namespace pam_v5
{
...
  public class Service1 : System.Web.Services.WebService
  {
...
  }
}
  • у [Service.asmx] ми також змінюємо простір імен, що використовується для класу [Service1] (правий клік / переглянути розмітку):

<%@ WebService Language="C#" CodeBehind="Service1.asmx.cs" Class="pam_v5.Service1" %>

Повернемося до архітектури нашого додатка:

  • Рівень [S] — це веб-сервіс. Він лише надає методи рівня [metier] віддаленим клієнтам. Саме цей рівень ми зараз створюємо.
  • Рівень [C] є клієнтом HTTP веб-сервісу. Саме цей рівень IDE вміють генерувати автоматично.
  • Рівень [web] сприймає рівень [C] як локальний рівень [metier], якщо забезпечити, щоб рівень [C] реалізувавінтерфейс віддаленого шару [metier].

Нижче ми бачимо, що наш веб-сервіс:

  • надавати методи шару [metier]
  • взаємодіяти з нею, а той, у свою чергу, буде взаємодіяти з шаром [dao].

Отже, проект повинен використовувати DLL з рівнів [metier] та [dao]. Він розвивається наступним чином:

  • до [1], додаються посилання на проект
  • у [2] вибираються звичайні DLL з папки [lib]. Слід переконатися, що у всіх них властивість «Локальна копія» встановлена на True. Вибрані DLL — це ті, що реалізують шари [metier] та [dao] із підтримкою NHibernate.

Веб-додаток типу «веб-сервіс ASP.NET» може мати глобальний клас додатка «Global.asax», як і класичний додаток «веб-сайт ASP.NET». Ми вже розглянули переваги такого класу:

  • вона ініціалізується під час запуску додатка і залишається в пам’яті
  • таким чином вона може зберігати дані, спільні для всіх клієнтів і доступні лише для читання. У нашому додатку вона, як і в попередніх, зберігатиме спрощений список співробітників. Це дозволить уникнути необхідності отримувати цей список із бази даних щоразу, коли клієнт його запитує.
  • у [1] клацніть правою кнопкою миші на проекті
  • у [2], виберіть опцію [Ajouter un nouvel élément]
  • у [3], вибрати [Classe d'application globale]
  • у [4] файл [Global.asax] було додано до проекту

Вміст файлу [Global.asax] такий:


<%@ Application Codebehind="Global.asax.cs" Inherits="pam_v5.Global" Language="C#" %>

Вміст файлу [Global.asax.cs] такий:


using System;

namespace pam_v5
{
  public class Global : System.Web.HttpApplication
  {

    protected void Application_Start(object sender, EventArgs e)
    {

    }
...
  }
}

Що нам потрібно зробити в методі Application_Start? Точно те саме, що й у попередніх веб-додатках. Повернімося до архітектури додатка та розмістимо в ній клас [Global]:

На схемі вище

  • клас [Global] створюється під час запуску веб-сервісу. Він залишається в пам’яті, доки веб-сервіс активний.
  • клас [Global] створює екземпляри шарів [metier] та [dao] у своєму методі [Application_Start]
  • для підвищення продуктивності клас [Global] зберігає спрощений список співробітників у внутрішньому полі. Він надаватиме список співробітників саме з цього поля.
  • Веб-сервіс інстанціюється при кожному запиті клієнта. Він зникає після обробки цього запиту. Він звертатиметься не безпосередньо до шару [metier], а до класу [Global]. Цей клас реалізовуватиме інтерфейс шару [metier].

Клас [Global] аналогічний тому, що вже був створений для попередніх додатків:


using System;
using Pam.Dao.Entites;
using Pam.Metier.Entites;
using Pam.Metier.Service;
using Spring.Context.Support;

namespace pam_v5
{
  public class Global : System.Web.HttpApplication
  {
    // --- статичні дані додатка ---
    public static Employe[] Employes;
    public static IPamMetier PamMetier = null;

    protected void Application_Start(object sender, EventArgs e)
    {
      // інстанціювання шару [metier]
      PamMetier = ContextRegistry.GetContext().GetObject("pammetier") as IPamMetier;
      // отримуємо спрощений масив співробітників 
      Employes = PamMetier.GetAllIdentitesEmployes();
    }

    // спрощений список співробітників
    static public Employe[] GetAllIdentitesEmployes()
    {
      return Employes;
    }

    // зарплата співробітника
    static public FeuilleSalaire GetSalaire(string SS, double heuresTravaillées, int joursTravailles)
    {
      return PamMetier.GetSalaire(SS, heuresTravaillées, joursTravailles);
    }
  }
}

Клас [Global] реалізує інтерфейс [IPamMetier], але це не вказано явно в оголошенні:


  public class Global : System.Web.HttpApplication, IPamMetier

Дійсно, методи GetAllIdentitesEmployes (рядок 24) та GetSalaire (рядок 30) є статичними, тоді як методи інтерфейсу IPamMetier такими не є. Тому клас Global не може реалізувати інтерфейс IPamMetier. Крім того, неможливо оголосити методи GetAllIdentitesEmployes та GetSalaire нестатичними. Адже доступ до них здійснюється через ім’я класу, а не через його екземпляр.

  • рядок 15: метод Application_Start аналогічний методам класів [Global], розглянутих у попередніх версіях. Він створює екземпляр шару [metier] (рядок 18), а потім ініціалізує (рядок 20) масив співробітників із рядка 12.
  • рядок 24: метод GetAllIdentitesEmployes просто повертає масив співробітників із рядка 12. У цьому й полягає користь того, що його було збережено в пам’яті ще на початку роботи програми.
  • рядок 30: метод GetSalaire викликає однойменний метод з шару [metier].

Для створення екземпляра шару [metier] (рядок 18) клас [Global] використовує фреймворк Spring. Він налаштовується за допомогою файлу [Web.config], який є ідентичним до файлу з попереднього проєкту: він налаштовує Spring та NHibernate для створення екземплярів шарів [metier] та [dao] веб-сервісу.

Повернемося до архітектури нашого клієнт-серверного додатка:

На стороні сервера залишається лише написати сам веб-сервіс [S]. Повернемося до архітектури додатка:

ми бачимо, що на стороні сервера всі шари, що передують шару [metier], реалізують його інтерфейс IPamMetier. Це не є обов’язковим, але такий підхід видається логічним. Це міркування можна застосувати й на стороні клієнта, до клієнта [C] веб-сервісу [S]. Таким чином, усі рівні, що відокремлюють рівень [web] від рівня [metier], реалізують інтерфейс IPamMetier. Отже, можна сказати, що ми повернулися до трирівневої архітектури:

  • рівень представлення [web] [1]
  • шар [metier] [2]
  • шар доступу до даних [3]

Реалізація веб-сервісу [Service1.asmx.cs] може виглядати наступним чином:


using System.Web.Services;
using Pam.Dao.Entites;
using Pam.Metier.Entites;
using Pam.Metier.Service;

namespace pam_v5
{
  [WebService(Namespace = "http://st.istia.univ-angers.fr/")]
  [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
  [System.ComponentModel.ToolboxItem(false)]
  public class Service1 : System.Web.Services.WebService, IPamMetier
  {

    // список усіх ідентифікаторів співробітників 
    [WebMethod]
    public Employe[] GetAllIdentitesEmployes()
    {
      return Global.GetAllIdentitesEmployes();
    }

    // ------- розрахунок заробітної плати 
    [WebMethod]
    public FeuilleSalaire GetSalaire(string ss, double heuresTravaillees, int joursTravailles)
    {
      return Global.GetSalaire(ss, heuresTravaillees, joursTravailles);
    }
  }
}
  • рядок 8: клас анотується атрибутом [WebService], і ми присвоюємо ім’я простору імен веб-сервісу
  • рядок 11: клас [Service1] успадковує клас [WebService] та реалізує інтерфейс [IPamMetier]
  • рядки 15 та 22: кожен метод класу анотується атрибутом [WebMethod], щоб бути доступним для віддалених клієнтів. За замовчуванням усі публічні методи веб-сервісу є доступними. Отже, атрибути в рядках 15 і 22 тут є необов’язковими. Для реалізації інтерфейсу [IPamMetier] кожен метод просто викликає однойменний метод класу [Global].

Ми готові до запуску веб-сервісу:

  • у [1] проект перегенерується
  • у [2] вибираємо веб-сервіс [Service1.asmx] і відображаємо його у браузері [3]
  • у [4] — відображена веб-сторінка. На ній представлені методи веб-сервісу.
  • у [4] переходимо за посиланням [GetAllIdentitesEmployes] і отримуємо у [5] тестову сторінку цього методу.
  • на [6], URL цього методу
  • за посиланням [7] — кнопка [Appeler], що дозволяє протестувати метод. Цей метод не вимагає жодних параметрів.
  • у [8] — XML-результат, повернутий веб-сервісом. У ньому значущими є лише властивості SS, Nom, Prenom об’єктів Employe є значущими, оскільки метод [GetAllIdentitesEmployes] запитує лише ці властивості. Однак цей метод повертає масив об’єктів Employe. У [8] видно, що числові властивості Id, Version містяться у поверненому XML-потоці, але властивості зі значенням null відсутні: Adresse, Ville, CodePostal, Indemnites.

У нас є активний веб-сервіс. Тепер ми напишемо для нього клієнт на C#. Для цього нам знадобиться URI із файлу WSDL веб-сервісу. Ми отримуємо його на сторінці, яка спочатку відображається під час виконання [Service.asmx]:

  • у [1] — URI веб-сервісу
  • на [2], посилання, яке веде до його файлу WSDL
  • в [3] — значення цього посилання

9.3. Проект на C# для клієнта веб-сервісу NUnit

Ми створюємо проект на C# (за допомогою Visual C#, а не Visual Web Developer) для клієнта веб-сервісу. Це буде тестовий клієнт NUnit. Також проект буде типу «Бібліотека класів».

  • у [1] ми створюємо проект на C# типу «Бібліотека класів»
  • у [2] ми даємо назву проекту
  • у [3] — проект. Видаляємо [Class1.cs].
  • у [4] — новий проєкт.
  • у властивостях проекту, на вкладці [Application] [5], ми встановлюємо простір імен проекту. Кожен клас, згенерований IDE, буде розміщений у цьому просторі.

Зберігаємо наш новий проєкт у зручному для нас місці:

 

Зробивши це, ми генеруємо клієнт віддаленого веб-сервісу. Щоб зрозуміти, що ми будемо робити, потрібно повернутися до архітектури «клієнт/сервер», яку ми зараз будуємо:

IDE згенерує клієнтський рівень [C] на основі URI з файлу WSDL веб-сервісу [S]. Нагадаємо, що URI цього файлу було зазначено раніше. Ми виконуємо операцію « » наступним чином:

  • у [1], клацнути правою кнопкою миші на гілці References і додати посилання на службу
  • у [2], вказати URL файлу WSDL веб-сервісу, зазначеного раніше. Якщо він ще не запущений, його слід запустити попередньо.
  • у [3], запустити пошук веб-сервісу за допомогою його файлу WSDL
  • у файлі [4] — виявлений веб-сервіс
  • у [5] — методи, що надаються веб-сервісом.
  • у [6] — простір імен, у якому потрібно розмістити класи та інтерфейси клієнта, що буде згенеровано.
  • підтверджуємо роботу майстра

  • у [1] — згенерований клієнтський файл « ». Двічі клацніть на ньому, щоб переглянути його вміст.
  • У [2], в оглядачі об’єктів, відображаються класи та інтерфейси простору імен Client.WsPam. Це простір імен згенерованого клієнта.
  • У [3] — клас, що реалізує клієнт веб-сервісу.
  • У [4] — методи, реалізовані клієнтом [Service1SoapClient]. Тут містяться два методи віддаленого веб-сервісу: [5] та [6].
  • У [2] містяться зображення сутностей шарів:
    • [metier]: FeuilleSalaire, ElementsSalaire
    • [dao]: Employe, Cotisations, Indemnites

Далі слід пам’ятати, що ці зображення віддалених об’єктів знаходяться на стороні клієнта та у просторі імен PamV5Client.WsPam.

Розглянемо методи та властивості, що надаються одним із них:

  • у [1] вибирається локальний клас [Employe]
  • у [2] ми бачимо властивості віддаленої сутності [Employe], а також приватні поля, що використовуються для власних потреб локальної сутності.

Повернемося до нашого додатка на C#. Додамо до нього тестовий клас NUnit:

  • у [1] додано клас [NUnit]. Клас [NUnit] потребуватиме фреймворку NUnit, а отже, і посилання на DLL з цього фреймворку. Тут ми припускаємо, що фреймворк NUnit було встановлено на комп’ютері (http://nunit.org/).
  • У [2] додаємо посилання на проект
  • на вкладці [3] .NET, яка об’єднує DLL, зареєстровані на комп’ютері, вибираємо [4], DLL та [nunit.framework] (мінімальна версія 2.4.6).

Крім того, ми будемо використовувати Spring для створення екземпляра локального клієнта [C] веб-сервісу [S]:

Посилання на DLL у Spring можна додати так само, як це було зроблено з фреймворком NUnit, якщо DLL були попередньо зареєстровані на комп’ютері (http://www.springframework.net/download.html).

Ми діємо інакше. Ми використовуємо папку [lib] з попередніх проєктів, яка містила необхідні для Spring файли DLL, і додаємо посилання на Spring до проєкту:

Повернемося до архітектури клієнта, який зараз створюється:

Вище ми бачимо, що тестовий клієнт [1] взаємодіє з розширеним шаром [metier] [2]. Цей шар має ті самі методи, що й віддалений шар [metier]. Отже, ми можемо використати тестовий клас, з яким ми вже знайомилися під час тестування шару [metier] у проєкті C# [pam-metier-dao-nhibernate]:


using NUnit.Framework;
using Pam.Dao.Entites;
using Pam.Metier.Entites;
using Pam.Metier.Service;
using Spring.Context.Support;

namespace Pam.Metier.Tests {

    [TestFixture()]
    public class NunitTestPamMetier : AssertionHelper {

        // шар [metier], що підлягає тестуванню 
        private IPamMetier pamMetier;

        // конструктор
        public NunitTestPamMetier() {
            // інстанціювання шару [dao]
            pamMetier = ContextRegistry.GetContext().GetObject("pammetier") as IPamMetier;
        }


        [Test]
        public void GetAllIdentitesEmployes() {
            // перевірка кількості працівників 
            Expect(2, EqualTo(pamMetier.GetAllIdentitesEmployes().Length));
        }

        [Test]
        public void GetSalaire1() {
            // розрахунок відомості про заробітну плату 
            FeuilleSalaire feuilleSalaire = pamMetier.GetSalaire("254104940426058", 150, 20);
            // перевірки 
            Expect(368.77, EqualTo(feuilleSalaire.ElementsSalaire.SalaireNet).Within(1E-06));
            // платіжна відомість неіснуючого працівника 
            bool erreur = false;
            try {
                feuilleSalaire = pamMetier.GetSalaire("xx", 150, 20);
            } catch (PamException) {
                erreur = true;
            }
            Expect(erreur, True);
        }

    }
}

Необхідно внести кілька змін:

  • у рядку 18 створюється екземпляр шару [metier] за допомогою фреймворку Spring. Клас у цих двох випадках різний. Тут локальний шар [metier] є екземпляром класу [PamV5Client.WsPam.Service1SoapClient], який генерується IDE. Тому Spring налаштований наступним чином у файлі [app.config] проекту C#:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>

    <configSections>
        <sectionGroup name="spring">
            <section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core" />
            <section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
        </sectionGroup>
    </configSections>

    <spring>
        <context>
            <resource uri="config://spring/objects" />
        </context>
        <objects xmlns="http://www.springframework.net">
            <object id="pammetier" type="PamV5Client.WsPam.Service1SoapClient, pam-v5-client-csharp-webservice"/>
        </objects>
    </spring>


    <system.serviceModel>
...
  • у рядку 16 вище об’єкт [pammetier] є екземпляром класу [PamV5Client.WsPam.Service1SoapClient], який знаходиться в збірці (assembly) [pam-v5-client-csharp-webservice]. Щоб отримати першу інформацію, достатньо повернутися до визначення класу [Service1SoapClient] в об’єктному оглядачі (розділ 9.3):
  • у [2] — клас реалізації локального шару [metier], а в [1] — його простір імен
  • на [3], у властивостях проєкту — ім’я збірки, друга інформація, необхідна для конфігурації об’єкта Spring [pammetier].

Повернемося до коду інстанціювання локального шару [metier] у [NUnit.cs]:


        // шар [metier], що підлягає тестуванню 
        private IPamMetier pamMetier;

        // конструктор
        public NunitTestPamMetier() {
            // інстанціювання шару [dao]
            pamMetier = ContextRegistry.GetContext().GetObject("pammetier") as IPamMetier;
        }

У рядку 7 віддалений шар [metier] мав тип IPamMetier. Тут шар [metier] має тип [Service1SoapClient]:


public class Service1SoapClient : System.ServiceModel.ClientBase<Service1Soap>

Ми бачимо, що клас Service1SoapClient не реалізує інтерфейс IPamMetier, хоча й надає методи з однаковими іменами. Тому нам потрібно написати інстанціювання локального шару [metier] наступним чином:


        // шар [metier], що підлягає тестуванню 
        private Service1SoapClient pamMetier;

        // конструктор
        public NunitTestPamMetier() {
            // інстанціювання шару [metier]
            pamMetier = ContextRegistry.GetContext().GetObject("pammetier") as Service1SoapClient;
}

Ще одна зміна, яку потрібно внести:


        [Test]
        public void GetSalaire1() {
...
            try {
                feuilleSalaire = pamMetier.GetSalaire("xx", 150, 20);
            } catch (PamException) {
                erreur = true;
            }
            Expect(erreur, True);
        }

У наведеному вище коді в рядку 6 використовується тип PamException, якого не існує на стороні клієнта. Його слід замінити на його батьківський клас — тип Exception.


        [Test]
        public void GetSalaire1() {
...
            try {
                feuilleSalaire = pamMetier.GetSalaire("xx", 150, 20);
            } catch (Exception) {
                erreur = true;
            }
            Expect(erreur, True);
        }

Нарешті, імпортовані простори імен більше не збігаються:


using System;
using PamV5Client.WsPam;
using NUnit.Framework;
using Spring.Context.Support;

Після цього можна згенерувати проєкт типу «Бібліотека класів». Створюється такий файл: DLL:

  • у [1], папка [bin/Release] проекту C#
  • у [2], а DLL — у проекті.

Потім тест NUnit виконується фреймворком NUnit (для виконання тесту має бути активною база MySQL dbpam_nhibernate):

  • у [3] та [4] база DLL [2] завантажується у тестовий додаток NUnit
  • у [5] вибирається та виконується тестовий клас [6]
  • у [7] — результати успішного тесту

Тепер у нас є робочий веб-сервіс.