H Уроки WCF. Создание простого сервиса в черновиках Tutorial

.NET, C#

Введение



Данная статья открывает курс уроков по технологии WCF и рассчитана в первую очередь на новичков в программировании веб-сервисов.


Оглавление


  1. Содержание урока
  2. Создание проекта сервиса
  3. Размещение сервиса
  4. Публикация сервиса
  5. Вызов сервиса

  6. Исходники и полезные ссылки



1. Содержание урока

вернуться к оглавлению

В данном уроке мы создадим простой WCF-сервис «калькулятор», реализующий четыре базовые арифметические операции, научимся размещать сервис на IIS и публиковать на сервере. Кроме того, расскажем о том, как подключиться к сервису из другого приложения на C# или с помощью бесплатной программы SoapUI, а также добавим функционал для вызова сервиса через веб (например, из браузера или Ajax).

Урок рассчитан на начинающих программистов C#, решивших освоить технологию WCF. Опытным разработчикам может быть полезна информация из раздела WebInvoke, посвященного созданию конечных точек для Ajax.

2. Создание проекта сервиса

вернуться к оглавлению

В работе используется Visual Studio 2013 Professional, версия фреймворка 4.5.1. Для заметки: за некоторыми небольшими исключениями, разницы между версиями 4.0. и 4.5.1 нет, но использование последней предпочтительней, т.к. добавляет полезную функцию генерации WSDL одним файлом, но об этом ниже.

Итак, запускаем студию и создаем проект типа WCF Service Application в соответствующем разделе, называем его SampleService.


После создания проекта рассмотрим структуру решения в обозревателе решений:


1. IService1.cs содержит описание интерфейса сервиса, т.е. набор методов, которые наш сервис предоставляет.
2. Service1.svc состоит из двух частей — реализация сервиса (Service1.svc.cs) и разметка (Markup), доступная в меню по клику правой кнопкой мыши.
3. Web.config — конфигурация сервиса.
4. Папка App_Data нам пока не нужна — удаляем.

Для начала переименуем сервис и его интерфейс, придав им имена Calculator и ICalculator соответственно, а также изменим строчку в разметке сервиса (файл Calculator.svc):
<%@ ServiceHost Language="C#" Debug="true" Service="SampleService.Calculator" CodeBehind="Calculator.svc.cs" %>


Теперь опишем сам интерфейс нашего калькулятора. Объявим стандартные арифметические операции, такие как сложение (Addition), вычитание (Subtraction), умножение (Multiplication) и деление (Division). Также добавим дополнительный метод TestConnection, возвращающий строку. С помощью этого метода клиенты смогут проверить, что сервис функционирует, т.к. стандартного «пинга» WCF не предоставляет. Все методы интерфейса должны быть помечены атрибутом [OperationContract], иначе они не будут видны клиентам.

В итоге, интерфейс примет следующий вид:
ICalculator.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Text;

namespace SampleService
{
    [ServiceContract]
    public interface ICalculator
    {
        #region Common Methods

        /// <summary>
        /// проверка соединения
        /// </summary>
        /// <returns> OK </returns>
        [OperationContract]
        string TestConnection();

        #endregion

        #region Arithmetic

        /// <summary>
        /// сложение
        /// </summary>
        /// <param name="a"> слагаемое 1 </param>
        /// <param name="b"> слагаемое 2 </param>
        /// <returns> сумма </returns>
        [OperationContract]
        double Addition(double a, double b);

        /// <summary>
        /// вычитание
        /// </summary>
        /// <param name="a"> уменьшаемое </param>
        /// <param name="b"> вычитаемое </param>
        /// <returns> разность </returns>
        [OperationContract]
        double Subtraction(double a, double b);

        /// <summary>
        /// умножение
        /// </summary>
        /// <param name="a"> множитель 1 </param>
        /// <param name="b"> множитель 2 </param>
        /// <returns> произведение </returns>
        [OperationContract]
        double Multiplication(double a, double b);

        /// <summary>
        /// деление
        /// </summary>
        /// <param name="a"> делимое </param>
        /// <param name="b"> делитель </param>
        /// <returns> частное </returns>
        [OperationContract]
        double Division(double a, double b);

        #endregion
    }
}



Теперь реализуем интерфейс в классе сервиса:
Calculator.svc.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Text;

namespace SampleService
{
    public class Calculator : ICalculator
    {
        #region Common Methods

        /// <summary>
        /// проверка соединения
        /// </summary>
        /// <returns> OK </returns>
        public string TestConnection()
        {
            return "OK";
        }

        #endregion

        #region Arithmetic

        /// <summary>
        /// сложение
        /// </summary>
        /// <param name="a"> слагаемое 1 </param>
        /// <param name="b"> слагаемое 2 </param>
        /// <returns> сумма </returns>
        public double Addition(double a, double b)
        {
            return a + b;
        }

        /// <summary>
        /// вычитание
        /// </summary>
        /// <param name="a"> уменьшаемое </param>
        /// <param name="b"> вычитаемое </param>
        /// <returns> разность </returns>
        public double Subtraction(double a, double b)
        {
            return a - b;
        }

        /// <summary>
        /// умножение
        /// </summary>
        /// <param name="a"> множитель 1 </param>
        /// <param name="b"> множитель 2 </param>
        /// <returns> произведение </returns>
        public double Multiplication(double a, double b)
        {
            return a * b;
        }

        /// <summary>
        /// деление
        /// </summary>
        /// <param name="a"> делимое </param>
        /// <param name="b"> делитель </param>
        /// <returns> частное </returns>
        public double Division(double a, double b)
        {
            return a / b;
        }

        #endregion
    }
}



Логика сервиса описана, теперь необходимо его разместить на хостинге.

3. Размещение сервиса

вернуться к оглавлению

Сам по себе сервис представляет библиотеку (в нашем случае файл SampleService.dll), и для ее запуска в виде сервиса необходимо воспользоваться одним из предоставляемых WCF методов:

  • 1. Хостинг на IIS.
  • 2. Запуск в виде службы Windows.
  • 3. Self hosting (сервис выполнен в виде консольного приложения, запускающего сервис).


Принципиального различия в этих методах нет, поэтому выберем простейший вариант — размещение на IIS. Для этого откроем конфигурацию проекта и выберем вкладку «Web». Параметры по умолчанию вы можете наблюдать у себя, а вот так выглядят настройки для нашего сервиса:


Раздел Start Action определяет, что произойдет при запуске проекта в студии:

  • Current Page (по умолчанию) – запуск последней открытой страницы в браузере. У нашего сервиса пока нет настроенных url-адресов методов, поэтому этот вариант не подходит.
  • Specific Page – запуск конкретной страницы.
  • Start External program – запуск указанного исполняемого файла, который инициирует сервис, например, вызовет метод, нуждающийся в отладке.
  • Start URL – открытие указанного в поле URL адреса в браузере.
  • Don't open a page – ничего не открывать, но ждать запроса от стороннего приложения.


Во всех случаях сервис «запускается» в режиме отладки и ведет себя как обычное приложение в студии, т.е. реагирует на контрольные точки. Последний вариант удобен тем, что ничего не открывается в браузере, т.к. отладка с помощью браузера — не лучший вариант для WCF сервиса, но об этом ниже.

Раздел Servers определяет, где будет размещен сервис. По умолчанию выбран IIS Express (строенный в студию вариант IIS), но мы будем продвинутыми и разместим сервис на нормальном IIS. Разумеется, для этого необходимо установить IIS в панели управления и включить необходимые компоненты (ASP .NET, Basic-авторизация и другие).

Под спойлером указан один из вариантов набора компонентов для работы сервисов.
Компоненты IIS



После указания адреса в настройках необходимо нажать кнопку «Create Virtual Directory». Затем можно скомпилировать проект и запустить диспетчер служб IIS. Наше приложение уже должно появиться в списке приложений сайта по умолчанию (Default Web Site), а также ему должен быть назначен пул (DefaultAppPool). Рекомендуется создать отдельный пул для ваших сервисов, указав в качестве платформы .NET 4.0:


После создания пула, назначьте его вашему приложению (Default Web Site > Lesson1 > Дополнительные параметры > Пул приложений).

Последний штрих — проверка вашего приложения. Для этого откройте в браузере строку, указанную в конфигурации проекта, добавив в конце имя сервиса, в нашем случае это Calculator.svc: http://localhost/Lesson1/Calculator.svc. Итог должен быть примерно следующим:



Внимание! Если в качестве платформы вы выбрали .NET 4.0 и при этом на машине, где размещается сервис установлен .NET 4.0, ссылки на WSDL одним файлом (http://localhost/Lesson1/Calculator.svc?singleWsdl) не будет. Поэтому рекомендуется установить на сервер фреймворк версии не ниже 4.5, даже если сам сервис скомпилирован на .NET 4.0. Это достаточно забавная особенность, но за эту ссылку отвечает именно фреймворк на сервере, а не тот, для которого компилировался сервис. Это, пожалуй, единственное существенное отличие .NET 4.0 от .NET 4.5 с точки зрения интерфейса сервиса.

Теперь немного о WSDL. По сути это XML, содержащая описание сервиса, его методов и точек подключения. Чтобы сторонние интеграторы смогли подключиться к вашему сервису, им нужна ссылка на WSDL. WSDL может быть разбита на части (по умолчанию) или быть одним файлом (см. выше). Важно помнить, что не все клиенты (например, SoapClient на PHP) могут работать с WSDL, разбитой на части, поэтому наличие однофайловой WSDL приветствуется.

4. Публикация сервиса

вернуться к оглавлению

Под публикацией сервиса подразумевается его компиляция для дальнейшего выкладывания сборки на сервер. Рассмотрим на конкретном примере, выложив сервер на демонстрационный сервер компании.

Для начала, необходимо создать профиль публикации, для этого кликнем правой кнопкой мыши на имени проекта и выберем пункт Publish...:


Мастер предложит нам несколько вариантов публикации, выберем Custom, профиль назовем Demo.
Затем выберем метод публикации File System и укажем путь, куда будет сохраняться наша сборка:


Проект готов к публикации, но желательно еще перейти к следующей вкладке (Settings) и отметить галочку «Delete all existing files prior to publish» (удалять все файлы перед публикацией) и выбрать конфигурацию.


Выполняем публикацию и смотрим, что получилось в итоге:


Чтобы разместить сервис на вашем сайте, скопируйте папку на сервер и подключите к IIS как приложение, как если бы это был обычный сайт (Default Web Site > Add Application...)


Готово! Сервис размещен по адресу http://dszss.proitr.ru/WCFLesson1/Calculator.svc.

5. Вызов сервиса

вернуться к оглавлению

В данном разделе мы рассмотрим три способа использования нашего сервиса: вызов через приложение на C#, обращение через SoapUI и запрос посредством URL (WebInvoke).

Клиент на C#

вернуться к оглавлению

Для начала рассмотрим обращение к нашему сервису через клиент на C#. Так как WCF интегрирован в Visual Studio, создать клиент не составит труда. Все, что нам надо знать — это адрес WSDL сервиса, в которому мы хотим подключиться.

Создадим в нашем решении еще один проект типа Console Application и назовем его TestClient. Назначим его исполняемым и добавим ссылку на сервис:


В открывшемся окне введем адрес локальный сервиса, по желанию укажем имя пространства имен и дополнительные настройки. Здесь же можно посмотреть, какие методы предоставляет наш сервис:


После создания ссылки на сервис, в проекте появится папка «Service References», в которой будет находиться сгенерированный сутидей клиент. Его код при желании вы можете посмотреть самостоятельно, надо только включить отображение скрытых файлов или перейти по F12 внутрь кода клиента.

Теперь напишем код для подключения к сервису и вызов арифметических операций, предоставляемых им:
Тестовый клиент
Program.cs
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

// ссылка на пространство имен сервиса
using TestClient.RemoteService;

namespace TestClient
{
    class Program
    {
        static void Main(string[] args)
        {
            // помощник вывода в консоль
            ConsoleWriter writer = new ConsoleWriter();

            // создадим клиент сервиса
            CalculatorClient client = new CalculatorClient("BasicHttpBinding_ICalculator");

            try
            {
                // проверка соединения
                writer.Write("Проверка соединения с сервисом... ");
                if (!string.Equals(client.TestConnection(), "OK", StringComparison.InvariantCultureIgnoreCase))
                {
                    throw new Exception("Проверка соединения не удалась");
                }
                writer.WriteLineSuccess();
                writer.WriteLine();

                // лямбда-функция проверки метода
                var CheckArithmeticOperation = new Action<Func<double, double, double>, string, double, double, double>
                (
                    (operation, operationName, arg1, arg2, expectedResult) =>
                    {
                        writer.Write("Проверка операции '");
                        writer.Write(ConsoleColor.White, operation.Method.Name);
                        writer.Write("', {0} {1} {2} = ", arg1.ToString(CultureInfo.InvariantCulture), operationName, arg2.ToString(CultureInfo.InvariantCulture));
                        double result = operation(arg1, arg2);
                        if (result == expectedResult)
                        {
                            // проверка пройдена
                            writer.Write("{0} ", result.ToString(CultureInfo.InvariantCulture));
                            writer.WriteLineSuccess();
                        }
                        else
                        {
                            // ошибка
                            throw new Exception(string.Format("Ошибка проверки метода '{0}': {1} {2} {3} != {4}",
                                operation.Method.Name, arg1.ToString(CultureInfo.InvariantCulture), operationName, arg2.ToString(CultureInfo.InvariantCulture), expectedResult.ToString(CultureInfo.InvariantCulture)));
                        }
                    }
                );

                // проверка метода Addition
                CheckArithmeticOperation(client.Addition, "+", 2.5, 5, 2.5 + 5);

                // проверка метода Subtraction
                CheckArithmeticOperation(client.Subtraction, "-", 2.5, 5, 2.5 - 5);

                // проверка метода Multiplication
                CheckArithmeticOperation(client.Multiplication, "*", 2.5, 5, 2.5 * 5);

                // проверка метода Division
                CheckArithmeticOperation(client.Division, "/", 2.5, 5, 2.5 / 5);

                // в конце работы закрываем клиент
                client.Close();
            }
            catch (Exception ex)
            {
                // в случае ошибки необходимо принудительно закрыть клиент методом Abort()
                client.Abort();

                // выводим информацию об ошибке
                writer.WriteLine();
                writer.WriteLineError("Ошибка: {0}", ex.Message);
            }

            Console.WriteLine();
            Console.WriteLine("Нажмите любую клавишу для продолжения...");
            Console.ReadKey();
        }
    }
}


ConsoleWriter.cs
using System;

namespace TestClient
{
    /// <summary>
    /// консольный писатель
    /// </summary>
    public class ConsoleWriter
    {
        #region Declarations

        private ConsoleColor _successColor;     // цвет сообщений для успешных операций
        private ConsoleColor _errorColor;       // цвет сообщений об ошибках
        private ConsoleColor _warningColor;     // цвет сообщений-предупреждений

        private string _successText;            // текст сообщений для успешных операций
        private string _errorText;              // текст сообщений об ошибках
        private string _warningText;            // текст сообщений-предупреждений

        #endregion
        #region Properties

        /// <summary>
        /// цвет сообщений для успешных операций
        /// </summary>
        public ConsoleColor SuccessColor { get { return _successColor; } set { _successColor = value; } }

        /// <summary>
        /// цвет сообщений об ошибках
        /// </summary>
        public ConsoleColor ErrorColor { get { return _errorColor; } set { _errorColor = value; } }

        /// <summary>
        /// цвет сообщений-предупреждений
        /// </summary>
        public ConsoleColor WarningColor { get { return _warningColor; } set { _warningColor = value; } }

        /// <summary>
        /// текст сообщений для успешных операций
        /// </summary>
        public string SuccessText { get { return _successText; } set { _successText = value; } }

        /// <summary>
        /// текст сообщений об ошибках
        /// </summary>
        public string ErrorText { get { return _errorText; } set { _errorText = value; } }

        /// <summary>
        /// текст сообщений-предупреждений
        /// </summary>
        public string WarningText { get { return _warningText; } set { _warningText = value; } }

        /// <summary>
        /// цвет текста
        /// </summary>
        public ConsoleColor ForegroundColor { get { return Console.ForegroundColor; } set { Console.ForegroundColor = value; } }

        /// <summary>
        /// цвет фона
        /// </summary>
        public ConsoleColor BackgroundColor { get { return Console.BackgroundColor; } set { Console.BackgroundColor = value; } }

        #endregion

        #region Constructors

        /// <summary>
        /// конструктор
        /// </summary>
        public ConsoleWriter()
        {
            _successColor = ConsoleColor.Green;
            _errorColor = ConsoleColor.Red;
            _warningColor = ConsoleColor.Blue;

            _successText = "OK";
            _errorText = "ERROR";
            _warningText = "WARNING";
        }

        #endregion

        #region Private methods
        #endregion
        #region Protected methods
        #endregion
        #region Public methods

        #region Write | WriteLine

        /// <summary>
        /// записать сообщение в консоль
        /// </summary>
        /// <param name="value"> сообщение </param>
        public void Write(string value)
        {
            Console.Write(value);
        }

        /// <summary>
        /// записать сообщение в консоль
        /// </summary>
        /// <param name="color"> цвет сообщения </param>
        /// <param name="value"> сообщение </param>
        public void Write(ConsoleColor color, string value)
        {
            ConsoleColor oldColor = Console.ForegroundColor;
            Console.ForegroundColor = color;
            Console.Write(value);
            Console.ForegroundColor = oldColor;
        }

        /// <summary>
        /// записать сообщение в консоль
        /// </summary>
        /// <param name="format"> строка формата </param>
        /// <param name="args"> аргументы </param>
        public void Write(string format, params object[] args)
        {
            Console.Write(string.Format(format, args));
        }

        /// <summary>
        /// записать сообщение в консоль
        /// </summary>
        /// <param name="color"> цвет сообщения </param>
        /// <param name="format"> строка формата </param>
        /// <param name="args"> аргументы </param>
        public void Write(ConsoleColor color, string format, params object[] args)
        {
            ConsoleColor oldColor = Console.ForegroundColor;
            Console.ForegroundColor = color;
            Console.Write(string.Format(format, args));
            Console.ForegroundColor = oldColor;
        }

        /// <summary>
        /// записать перевод строки в консоль
        /// </summary>
        public void WriteLine()
        {
            Console.WriteLine();
        }

        /// <summary>
        /// записать сообщение в консоль
        /// </summary>
        /// <param name="value"> сообщение </param>
        public void WriteLine(string value)
        {
            Console.WriteLine(value);
        }

        /// <summary>
        /// записать сообщение в консоль
        /// </summary>
        /// <param name="color"> цвет сообщения </param>
        /// <param name="value"> сообщение </param>
        public void WriteLine(ConsoleColor color, string value)
        {
            ConsoleColor oldColor = Console.ForegroundColor;
            Console.ForegroundColor = color;
            Console.WriteLine(value);
            Console.ForegroundColor = oldColor;
        }

        /// <summary>
        /// записать сообщение в консоль
        /// </summary>
        /// <param name="format"> строка формата </param>
        /// <param name="args"> аргументы </param>
        public void WriteLine(string format, params object[] args)
        {
            Console.WriteLine(string.Format(format, args));
        }

        /// <summary>
        /// записать сообщение в консоль
        /// </summary>
        /// <param name="color"> цвет сообщения </param>
        /// <param name="format"> строка формата </param>
        /// <param name="args"> аргументы </param>
        public void WriteLine(ConsoleColor color, string format, params object[] args)
        {
            ConsoleColor oldColor = Console.ForegroundColor;
            Console.ForegroundColor = color;
            Console.WriteLine(string.Format(format, args));
            Console.ForegroundColor = oldColor;
        }

        #endregion
        #region WriteSuccess | WriteLineSuccess

        /// <summary>
        /// записать сообщение в консоль
        /// </summary>
        public void WriteSuccess()
        {
            Write(_successColor, _successText);
        }

        /// <summary>
        /// записать сообщение в консоль
        /// </summary>
        /// <param name="value"> сообщение </param>
        public void WriteSuccess(string value)
        {
            Write(_successColor, value);
        }

        /// <summary>
        /// записать сообщение в консоль
        /// </summary>
        /// <param name="format"> строка формата </param>
        /// <param name="args"> аргументы </param>
        public void WriteSuccess(string format, params object[] args)
        {
            Write(_successColor, string.Format(format, args));
        }

        /// <summary>
        /// записать сообщение в консоль
        /// </summary>
        public void WriteLineSuccess()
        {
            WriteLine(_successColor, _successText);
        }

        /// <summary>
        /// записать сообщение в консоль
        /// </summary>
        /// <param name="value"> сообщение </param>
        public void WriteLineSuccess(string value)
        {
            WriteLine(_successColor, value);
        }

        /// <summary>
        /// записать сообщение в консоль
        /// </summary>
        /// <param name="format"> строка формата </param>
        /// <param name="args"> аргументы </param>
        public void WriteLineSuccess(string format, params object[] args)
        {
            WriteLine(_successColor, string.Format(format, args));
        }

        #endregion
        #region WriteError | WriteLineError

        /// <summary>
        /// записать сообщение в консоль
        /// </summary>
        public void WriteError()
        {
            Write(_errorColor, _errorText);
        }

        /// <summary>
        /// записать сообщение в консоль
        /// </summary>
        /// <param name="value"> сообщение </param>
        public void WriteError(string value)
        {
            Write(_errorColor, value);
        }

        /// <summary>
        /// записать сообщение в консоль
        /// </summary>
        /// <param name="format"> строка формата </param>
        /// <param name="args"> аргументы </param>
        public void WriteError(string format, params object[] args)
        {
            Write(_errorColor, string.Format(format, args));
        }

        /// <summary>
        /// записать сообщение в консоль
        /// </summary>
        public void WriteLineError()
        {
            WriteLine(_errorColor, _errorText);
        }

        /// <summary>
        /// записать сообщение в консоль
        /// </summary>
        /// <param name="value"> сообщение </param>
        public void WriteLineError(string value)
        {
            WriteLine(_errorColor, value);
        }

        /// <summary>
        /// записать сообщение в консоль
        /// </summary>
        /// <param name="format"> строка формата </param>
        /// <param name="args"> аргументы </param>
        public void WriteLineError(string format, params object[] args)
        {
            WriteLine(_errorColor, string.Format(format, args));
        }

        #endregion
        #region WriteWarning | WriteLineWarning

        /// <summary>
        /// записать сообщение в консоль
        /// </summary>
        public void WriteWarning()
        {
            Write(_warningColor, _warningText);
        }

        /// <summary>
        /// записать сообщение в консоль
        /// </summary>
        /// <param name="value"> сообщение </param>
        public void WriteWarning(string value)
        {
            Write(_warningColor, value);
        }

        /// <summary>
        /// записать сообщение в консоль
        /// </summary>
        /// <param name="format"> строка формата </param>
        /// <param name="args"> аргументы </param>
        public void WriteWarning(string format, params object[] args)
        {
            Write(_warningColor, string.Format(format, args));
        }

        /// <summary>
        /// записать сообщение в консоль
        /// </summary>
        public void WriteLineWarning()
        {
            WriteLine(_warningColor, _warningText);
        }

        /// <summary>
        /// записать сообщение в консоль
        /// </summary>
        /// <param name="value"> сообщение </param>
        public void WriteLineWarning(string value)
        {
            WriteLine(_warningColor, value);
        }

        /// <summary>
        /// записать сообщение в консоль
        /// </summary>
        /// <param name="format"> строка формата </param>
        /// <param name="args"> аргументы </param>
        public void WriteLineWarning(string format, params object[] args)
        {
            WriteLine(_warningColor, string.Format(format, args));
        }

        #endregion

        #endregion
    }
}




Результат исполнения:


В целом, вызов методов сервиса ничем не отличается от вызова методов обычного интерфейса C#, а т.к. клиент и сервис находятся в пределах одного решения, вы можете поставить контрольные точки в методах сервиса, которые сработают при их вызове. Также вы можете запустить отдельный проект сервиса в режиме ожидания запроса от стороннего приложения (см. выше).

Технически, при обращении к сервису клиент формирует Soap сообщение (обычная XML), которое предается по HTTP-каналу и может быть перехвачено сниффером. Разумеется, в арсенале WCF существуют и другие методы передачи, а также возможность шифрования и/или подписи сообщений, но это уже тема другого урока.

Переходим к другому способу вызова сервиса.

SoapUI

вернуться к оглавлению

SoapUI – бесплатная программа для тестирования Soap-сервисов. Хороша тем, что позволяет заглянуть во внутреннее устройство сообщений, что может быть очень полезно в разрешении сложных проблем, когда клиент и сервис используют разные платформы. В любом случае, SoapUI — альтернативный клиент, знакомство с которым необходимо, если вы решили серьезно заняться разработкой сервисов.

Запускаем программу и создаем новый проект. В поле адреса сервиса указываем на WSDL и вводим название проекта:


Как видим, SoapUI извлек интерфейсы сервиса и создал тестовые запросы для каждого из методов:


Осталось заполнить числовые значения и вызвать сервис:


Возможно, вы обратили внимание на странное пространство имен xmlns=«tempuri.org». Это результат того, что настройки пространств имен сервиса нами не были заданы в самом начале. Т.к. оставлять tempuri.org крайне не рекомендуется, изменим его прямо сейчас. Для этого пропишем желаемое нами пространство имен в атрибуте интерфейса сервиса ServiceContract:

[ServiceContract(Namespace = "http://dszss.proitr.ru/WCF")]
public interface ICalculator
{
    ...
}


После компиляции сервиса, тестовый клиент и SoapUI станут выдавать ошибку «Сообщение с Action „tempuri.org/ICalculator/Addition“ не может быть обработано на стороне получателя из-за несоответствия ContractFilter на EndpointDispatcher». В тестовом клиенте необходимо обновить конфигурацию сервиса (RemoteService > Update Service Reference), а в SoapUI – обновить описание (Update Definition), нажав F5 на имени точки подключения BasicHttpBinding_ICalculator и обновить запрос.

Запрос-ответ после изменения пространства имен:


С остальными возможностями SoapUI предлагаем ознакомиться самостоятельно.

WebInvoke

вернуться к оглавлению

WebInvoke – это возможность вызова метода WCF-сервисов «по ссылке», например с помощью Ajax.

Для начала, изменим конфигурацию:
Web.config
<?xml version="1.0"?>
<configuration>

  <appSettings>
    <add key="aspnet:UseTaskFriendlySynchronizationContext" value="true" />
  </appSettings>
  
  <system.web>
    <compilation debug="true" targetFramework="4.5.1" />
    <httpRuntime targetFramework="4.5.1"/>
  </system.web>
  
  <system.serviceModel>
    <behaviors>
      <endpointBehaviors>
        <behavior name="Basic" />
        <behavior name="WebJson">
          <webHttp defaultOutgoingResponseFormat="Json" faultExceptionEnabled="true" />
        </behavior>
        <behavior name="WebXML">
          <webHttp defaultOutgoingResponseFormat="Xml" faultExceptionEnabled="true" />
        </behavior>
      </endpointBehaviors>
      <serviceBehaviors>
        <behavior>
          <serviceMetadata httpGetEnabled="true" httpsGetEnabled="true" />
          <serviceDebug includeExceptionDetailInFaults="false" />
        </behavior>
      </serviceBehaviors>
    </behaviors>

    <bindings>
      <basicHttpBinding>
        <binding name="Basic" closeTimeout="00:01:00" openTimeout="00:01:00"
                 receiveTimeout="00:20:00" sendTimeout="00:20:00" maxBufferPoolSize="524288000"
                 maxBufferSize="65536000" maxReceivedMessageSize="65536000">
          <readerQuotas maxDepth="65536000" maxStringContentLength="65536000"
                        maxArrayLength="65536000" maxBytesPerRead="65536000" maxNameTableCharCount="65536000" />
        </binding>
      </basicHttpBinding>
      <webHttpBinding>
        <binding name="WebJson" closeTimeout="00:01:00" openTimeout="00:01:00"
                 receiveTimeout="00:20:00" sendTimeout="00:20:00" maxBufferSize="65536000"
                 maxBufferPoolSize="524288000" maxReceivedMessageSize="65536000">
          <readerQuotas maxDepth="65536000" maxStringContentLength="65536000"
                        maxArrayLength="65536000" maxBytesPerRead="65536000" maxNameTableCharCount="65536000" />
        </binding>
        <binding name="WebXML" closeTimeout="00:01:00" openTimeout="00:01:00"
                 receiveTimeout="00:20:00" sendTimeout="00:20:00" maxBufferSize="65536000"
                 maxBufferPoolSize="524288000" maxReceivedMessageSize="65536000">
          <readerQuotas maxDepth="65536000" maxStringContentLength="65536000"
                        maxArrayLength="65536000" maxBytesPerRead="65536000" maxNameTableCharCount="65536000" />
        </binding>
      </webHttpBinding>
    </bindings>

    <services>
      <service name="SampleService.Calculator">
        <!-- конечная точка SOAP -->
        <endpoint address="basic" binding="basicHttpBinding" behaviorConfiguration="Basic"
                  bindingConfiguration="Basic" name="Basic" bindingNamespace="http://dszss.proitr.ru/WCF"
                  contract="SampleService.ICalculator" />

        <!-- конечная точка REST (формат Json) -->
        <endpoint address="json" binding="webHttpBinding" behaviorConfiguration="WebJson"
                  bindingConfiguration="WebJson" name="WebJson" bindingNamespace="http://dszss.proitr.ru/WCF"
                  contract="SampleService.ICalculator" />

        <!-- конечная точка REST (формат XML) -->
        <endpoint address="xml" binding="webHttpBinding" behaviorConfiguration="WebXML"
                  bindingConfiguration="WebXML" name="WebXML" bindingNamespace="http://dszss.proitr.ru/WCF"
                  contract="SampleService.ICalculator" />
      </service>
    </services>
    
    <protocolMapping>
        <add binding="basicHttpsBinding" scheme="https" />
    </protocolMapping>    
    
    <serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
  </system.serviceModel>
  
  <system.webServer>
    <modules runAllManagedModulesForAllRequests="true" />
    <directoryBrowse enabled="false" />
  </system.webServer>

</configuration>



Добавленные разделы:

  • system.serviceModel/behaviors/endpointBehaviors – поведения конечных точек. Ранее у нас была одна конечная точка, которая принимала запросы от тестового клиента или SoapUI, теперь точки три, и для каждой надо создать свое поведение. defaultOutgoingResponseFormat определяет формат ответных сообщений — XML или Json. В целях демонстрации создадим дополнительные точки для каждого из форматов.
  • system.serviceModel/bindings — добавлены привязки для каждой из конечных точек. Несложно догадаться, что чему соответствует.
  • system.serviceModel/services — определения конечных точек. Здесь связаны интерфейсы сервиса связаны со своими конечными точками. До этого у нас была всего одна конечная точка типа basicHttpBinding, поэтому данный раздел не требовался.


Одного изменения конфигурации недостаточно, требуется также описать маппинги для методов интерфейса ICalculator. Пример для метода Addition:
[OperationContract]
[WebInvoke(Method = "GET", UriTemplate = "Add?a={a}&b={b}")]
double Addition(double a, double b);


Добавлен атрибут WebInvoke, в котором мы можем указан HTTP-метод (GET, POST, PUT или DELETE), а также часть URL, в которой будут указаны аргументы метода. Т.к. аргументы не строковые, их необходимо указывать в параметрах.

Если бы, допустим, у нас был метод, принимающий строки, можно было бы указать аргументы через слеш, например так:

[OperationContract]
[WebInvoke(Method = "GET", UriTemplate = "Add/{a}/{b}")]
double Addition(string a, string b);


Но у нас числа, поэтому прописываем по аналогии атрибут WebInvoke в прочим методам, в итоге получаем следующий код:
ICalculator.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Text;

namespace SampleService
{
    [ServiceContract(Namespace = "http://dszss.proitr.ru/WCF")]
    public interface ICalculator
    {
        #region Common Methods

        /// <summary>
        /// проверка соединения
        /// </summary>
        /// <returns> OK </returns>
        [OperationContract]
        [WebInvoke(Method = "GET")]
        string TestConnection();

        #endregion

        #region Arithmetic

        /// <summary>
        /// сложение
        /// </summary>
        /// <param name="a"> слагаемое 1 </param>
        /// <param name="b"> слагаемое 2 </param>
        /// <returns> сумма </returns>
        [OperationContract]
        [WebInvoke(Method = "GET", UriTemplate = "Add?a={a}&b={b}")]
        double Addition(double a, double b);

        /// <summary>
        /// вычитание
        /// </summary>
        /// <param name="a"> уменьшаемое </param>
        /// <param name="b"> вычитаемое </param>
        /// <returns> разность </returns>
        [OperationContract]
        [WebInvoke(Method = "GET", UriTemplate = "Sub?a={a}&b={b}")]
        double Subtraction(double a, double b);

        /// <summary>
        /// умножение
        /// </summary>
        /// <param name="a"> множитель 1 </param>
        /// <param name="b"> множитель 2 </param>
        /// <returns> произведение </returns>
        [OperationContract]
        [WebInvoke(Method = "GET", UriTemplate = "Mul?a={a}&b={b}")]
        double Multiplication(double a, double b);

        /// <summary>
        /// деление
        /// </summary>
        /// <param name="a"> делимое </param>
        /// <param name="b"> делитель </param>
        /// <returns> частное </returns>
        [OperationContract]
        [WebInvoke(Method = "GET", UriTemplate = "Div?a={a}&b={b}")]
        double Division(double a, double b);

        #endregion
    }
}



После компиляции сервиса мы получаем возможность вызывать методы через URL, не самую приятную на вид, но все же.

Вызов метода сложения с результатом в XML http://localhost/Lesson1/Calculator.svc/xml/Add?a=2&b=3.5:


Аналогичный метод, но с результатом в Json http://localhost/Lesson1/Calculator.svc/json/Add?a=2&b=3.5:


Сам адрес, как видим, состоит из пути к сервису (http://localhost/Lesson1/Calculator.svc), имени конечной точки (xml или json) и части адреса из UriTemplate. Для метода TestConnection UriTemplate не задан, поэтому используется значение по умолчанию — имя метода: http://localhost/Lesson1/Calculator.svc/xml/TestConnection

7. Исходники и полезные ссылки

вернуться к оглавлению

Код проекта:

Ссылки:
  • SoapUI – бесплатная программа для тестирования сервисов.
133

комментарии (0)