niedziela, 15 września 2013

WCF - Budowa serwisu

Znając podstawową terminologię WCF, możemy przystąpić do budowy serwisu.

W edytorze usuwamy domyślnie stworzone pliki i dodajemy nowy serwis. Nazywamy go CustomerService. Jak nazwa mówi, będzie umożliwiał pobieranie danych o klientach.
Po dodaniu serwisu do projektu zostaną stworzone dwa pliki: CustomerService.svc oraz ICustomerService.cs.
Pierwszy plik implementuje metody znajdujące się w interfejsie zdefiniowanym w drugim pliku. Możemy usunąć domyślnie stworzoną metodę DoWork().

Serwis będzie posiadać następujący interfejs:


Code:
using System.Collections.Generic;
using System.ServiceModel;

namespace Wcf_SimpleService
{
    [ServiceContract]
    public interface ICustomerService
    {
        [OperationContract]
        List<Customer> GetCustomers();

        [OperationContract]
        Customer GetCustomerById(int customerId);

        [OperationContract]
        void AddCustomer(Customer customer);

        [OperationContract]
        void RemoveCustomerById(int customerId);

        [OperationContract]
        int GetCustomerCount();
    }
}


Widać tutaj użycie dwóch ważnych atrybutów:
  • ServiceContract - definiuje dany interfejs jako kontrakt dla serwisu WCF
  • OperationContract - definiuje składową, z której będzie mógł korzystać klient (znajdzie się ona w WSDL)
Każdy z tych atrybutów pozwala dodatkowo ustawić specyficzne dla siebie atrybuty. ServiceContract pozwala ustawić m.in. przestrzeń nazw,interfejs używany w przypadku komunikacji dwustronnej, czy też poziom ochorny. OperationContract pozwala na zmianę nazwy metody w generowanym WSDLu,wybranie czy metoda ma być typu one-way czy request-respond itp.

Kolejnym krokiem w tworzeniu serwisu jest implementacja metod:


Code:
using System.Collections.Generic;
using System.ServiceModel;
using System.Linq;

namespace Wcf_SimpleService
{
    [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
    public class CustomerService : ICustomerService
    {
        private List<Customer> customers = new List<Customer>
            {
                new Customer {Id = 1, FirstName = "Jan", LastName = "Kowalski"},
                new Customer {Id = 2, FirstName = "Marek", LastName = "Wierzbicki"}
            };

        public List<Customer> GetCustomers()
        {
            return customers;
        }

        public Customer GetCustomerById(int customerId)
        {
            return customers.Single(c => c.Id == customerId);
        }

        public void AddCustomer(Customer customer)
        {
            customers.Add(customer);
        }

        public void RemoveCustomerById(int customerId)
        {
            customers.Remove(customers.Single(x => x.Id == customerId));
        }

        public int GetCustomerCount()
        {
            return customers.Count;
        }
    }
}

Na klasie CustomerService został nałożony atrybut ServiceBehaviour. Ktoś może zapytać: dlaczego ten atrybut nie został nałożony na interfejsie? Odpowiedź jest bardzo prosta: ponieważ dotyczy on bezpośrednio serwisu, nie jego interfejsu. Serwis będzie udostępniał listę klientów, którą będziemy modyfikowali za pomocą poszczególnych operacji, dlatego też został oznaczony flagą InstanceContextMode.Singe.
Implementacje metod nie są niczym specjalnym, dlatego nie będę ich szczegółowo omawiać.

Ostatnim elementem jest klasa modelu - Customer:


Code:
using System.Runtime.Serialization;

namespace Wcf_SimpleService
{
    [DataContract]
    public class Customer
    {
        [DataMember]
        public int Id { get; set; }

        [DataMember]
        public string FirstName { get; set; }

        [DataMember]
        public string LastName { get; set; }
    }
}

Dwa kolejne atrybuty które znajdą zastosowanie w naszych serwisach to:
  • DataContract - atrybut ten nakładamy na klasę która ma podlegać serializacji 
  • DataMember - atrybut ten determinuje pola które mają podlegać serializacji
Jeżeli w klasie nie zostanie na pole nałożony atrybut DataMemeter wartość takiego pola jak i samo pole nie będzie brało udziału w serializacji. 

Kolejnym ważnym aspektem jest hostowanie serwisu WCF. Tym razem przedstawię jeden z prostszych czyli self hosting - serwis zostanie hosotowany w zwyczajnej aplikacji konsolowej (o hostowaniu więcej w kolejnym poście).

Tworzymy nowy projekt typu Console Application i dodajemy następujący kod:


Code:
using System;
using System.Linq;
using System.ServiceModel;
using Wcf_SimpleService;

namespace HostClientApplication
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var host = new ServiceHost(typeof(CustomerService), new Uri("net.tcp://localhost/netTCP"), new Uri("http://localhost/CustomerService")))
            {
                host.Open();
                host.Description.Endpoints.ToList().ForEach(endPoint => Console.WriteLine(endPoint.ListenUri));
                Console.WriteLine("Press Enter to Exit");
                Console.Read();
            }
        }
    }
}

Hosotwanie odbywa się przy użyciu klasy ServiceHost, której jako parametry przekazujemy typ hosotwanego serwisu oraz adresy pod którymi będzie dostępny dla klientów.

Ostatnim elementem jest stworzenie aplikacji klienckiej za pomocą której możemy korzystać z serwisu. Aplikacja to program typu konsolowego, gdzie za pomocą poleceń wprowadzanych z klawiatury można wykonywać operacje:


Code:
using System;
using System.ServiceModel;
using Wcf_SimpleService;

namespace ClientApplication
{
    class Program
    {
        static void Main(string[] args)
        {
            var channelFactory = new ChannelFactory<ICustomerService>(new BasicHttpBinding());
            ICustomerService channel = channelFactory.CreateChannel(new EndpointAddress("http://localhost/CustomerService"));
            
            Console.WriteLine("Select operation:");
            Console.WriteLine("1 Print all customers");
            Console.WriteLine("2 Print customer by id");
            Console.WriteLine("3 Add new customer");
            Console.WriteLine("4 Remove customer by id");
            Console.WriteLine("5 Get customers count");
            Console.WriteLine("0 quit");
            var ok = true;
            while (ok)
            {
                Console.Write("Podaj operacje: ");
                int option = int.Parse(Console.ReadLine());
                switch (option)
                {
                    case 0:
                        ok = false;
                        break;
                    case 1:
                        PrintCustomers(channel);
                        break;
                    case 2:
                        Console.Write("Podaj id klienta: ");
                        PrintCustomerById(channel, ReadInt());
                        break;
                    case 3:
                        Console.Write("Podaj imię: ");
                        string firstName = Console.ReadLine();
                        Console.Write("Podaj nazwisko: ");
                        string lastName = Console.ReadLine();
                        Console.Write("Podaj id: ");
                        int id = ReadInt();
                        var customer = new Customer {FirstName = firstName, LastName = lastName, Id = id};
                        channel.AddCustomer(customer);
                        break;
                    case 4:
                        Console.WriteLine("Podaj id klienta, ktorego chcesz usunac: ");
                        channel.RemoveCustomerById(ReadInt());
                        break;
                    case 5:
                        Console.WriteLine(channel.GetCustomerCount());
                        break;
                }
                Console.WriteLine();
            }
        }

        private static void PrintCustomers(ICustomerService channel)
        {
            var customers = channel.GetCustomers();
            customers.ForEach(x => Console.WriteLine("{0} {1} {2}", x.Id, x.FirstName, x.LastName));
        }

        private static void PrintCustomerById(ICustomerService channel, int id)
        {
            var customer = channel.GetCustomerById(id);
            Console.WriteLine("{0} {1} {2}", customer.Id, customer.FirstName, customer.LastName);
        }

        private static int ReadInt()
        {
            return int.Parse(Console.ReadLine());
        }
    }
}


Powyższy kod nie jest szablonowym przykładem w jaki sposób tworzyć kod aplikacji, służy tylko jako przykład wywoływania operacji na serwisie.

Kod aplikacji testowej można pobrać stąd: http://sdrv.ms/11gW6nS

2 komentarze: