RESTful WCF Services Do realizacji ćwiczenia potrzebne jest zintegrowane środowisko programistyczne Microsoft Visual Studio 2010 oraz serwer bazy danych SQL Server Express 2005 lub nowszy. Celem ćwiczenia jest zapoznanie się z możliwością tworzenia serwisów REST (ang. Representational State Transfer) z wykorzystaniem technologii WCF. Windows Communication Foundation (WCF) to technologia pozwalając tworzyć usługi sieciowe. Typowy serwis stworzony w tej technologii wykorzystuje do komunikacji protokół SOAP, ale tworząc serwis REST klient będzie korzystał z usługi wykorzystując protokół HTTP oraz obiekty JSON. Autor ćwiczenia: Piotr Ostrowski Kroki ćwiczenia : 1. Utworzenie nowego projektu RESTful WCF. a. Uruchom środowisko Visual Studio 2010 b. Z menu głównego wybierz File New Project. Z list szablonów dla WCF wybierz WCF Service Application, język Visual C#. Zaakceptuj zaproponowany katalog lub zmień go na inny gdy nie masz praw zapisu w proponowanym katalogu. Zmień nazwę projektu na: RESTServices. Kliknij przycisk OK. c. W panelu Solution Explorer wywołaj prawym klawiszem myszy menu kontekstowe dla projektu RESTServices i wybierz opcję Properties. d. Jako że w momencie uruchomienia usługi tworzona jest instancja lokalnego serwera działającego na domyślnym losowym porcie koniecznie jest zapewnienie aby serwer uruchamiany był każdorazowo na stałym, znanym przez nas porcie. Przejdź do zakładki Web a następnie do sekcji Servers. Zaznacz RadioButton : Specific port. W aktywnym polu wpisz : 8081. Zapisz zmiany wybierając z menu File Save Selected Items:
e. Kolejnym krokiem jest usunięcie domyślnie utworzonych serwisów dodawanych do projektu zgodnie z szablonem. Zaznacz pliki IService1.cs oraz Service1.svc w projekcie RESTServices a następnie usuń je wybierając z menu kontekstowego opcję Delete. 2. Utworzenie połączenia z bazą danych w środowisku MS Visual Studio a. W panelu Solution Explorer wywołaj prawym klawiszem myszy menu kontekstowe dla folderu App_Data i wybierz opcję Add Existing Item. Wskaż pobrany na dysk lokalny plik Instytut.mdf.
b. Po dodaniu pliku do projektu wywołaj dla niego w panelu Solution Explorer prawym klawiszem myszy menu kontekstowe i wybierz opcję Open. Rozwiń gałąź Tables i upewnij się, że plik bazy danych zawiera tabelę PRACOWNICY. 3. Utworzenie modelu encji (Entity Data Model) a. Z menu kontekstowego dla węzła projektu w panelu Solution Explorer wybierz opcję Add New Item. Wybierz szablon ADO.NET Entity Data Model, język Visual C# i pozostaw zaproponowaną nazwę pliku. Kliknij przycisk Add. b. W oknie dialogowym kreatora modelu wybierz opcję generacji modelu na podstawie bazy danych i przejdź do kolejnego kroku. c. W następnym kroku kreatora wybierz połączenie z dołączonym do projektu plikiem bazy danych. Pozostaw zaznaczone pole wyboru dodania parametrów połączenia w pliku konfiguracyjnym aplikacji. Przejdź do kolejnego kroku kreatora.
d. W ostatnim kroku kreatora zaznacz tabelę PRACOWNICY zawartą w bazie danych. Odznacz pole wyboru Include foreign key columns in the model (ze względów ideologicznych nie chcemy kluczy obcych w modelu obiektowym ), pozostaw odznaczone pole wyboru Pluralize or singularize generated object names (używamy nazw tabel w języku polskim, a więc Visual Studio nam przy nazwach nie pomoże) i kliknij Finish.
e. Utworzony plik modelu zostanie otwarty do graficznej edycji w Visual Studio. Dokonaj zmiany encji PRACOWNICY na PRACOWNIK. 4. Dodanie nowego serwisu do projektu a. W panelu Solution Explorer wywołaj prawym klawiszem myszy menu kontekstowe dla projektu RESTServices i wybierz opcję Add New Item... Wybierz typ pliku WCF Service, zmień nazwę pliku na RESTPracownicyService.svc, a następnie zatwierdź wybierając Add. b. Zanim przejdziemy do dalszej implementacji, dodany zostanie plik reprezentujący strukturę danych wysyłanych z serwisu do klienta. W tym celu podobnie jak w punkcie a. dodaj nowy plik typu Class o nazwie Item.cs do projektu RESTServices. c. Podmień implementację klasy Item.cs na poniższą. Zaimportuj brakujące referencje.
[DataContract] public class Item [DataMember] public String Name get; set; [DataMember] public String Value get; set; d. Po dodaniu klasy Item.cs otwórz plik IRESTPracownicyService.cs definiujący interfejs serwisu. Istniejącą definicję interfejsu zastąp poniższą. Zaimportuj brakujące zależności. [ServiceContract] public interface IRESTPracownicyService [OperationContract] [WebInvoke(Method = "GET", ResponseFormat = WebMessageFormat.Json, UriTemplate = "pracownicy/term")] IList<Item> GetPracownicy(string term); e. Następnie otwórz plik RESTPracownicyService.svc.cs (rozwinięcie gałęzi pliku RESTPracownicyService.svc) zawierający definicję klasy RESTPracownicyService implementujący interfejs IRESTPracownicyService. [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)] public class RESTPracownicyService : IRESTPracownicyService public IList<Item> GetPracownicy(string term) InstytutEntities dc = new InstytutEntities(); IList<Item> items = (from p in dc.pracownicy where p.nazwisko.tolower().contains(term.tolower()) select new Item() Name = p.nazwisko + ", " + p.etat, Value = p.nazwisko ).ToList(); return items; f. Teraz wykonana zostanie konfiguracja pozwalająca uruchomić serwis jako REST-owy. Otwórz plik Web.config. Zastąp sekcję <system.servicemodel></system.servicemodel> poniższą:
<system.servicemodel> <servicehostingenvironment aspnetcompatibilityenabled="true" /> <behaviors> <servicebehaviors> <behavior name="restpracownicyservicebehaviour"> <servicemetadata httpgetenabled="true" /> <servicedebug includeexceptiondetailinfaults="true" /> </behavior> </servicebehaviors> <endpointbehaviors> <behavior name="restpracownicyserviceendpointbehavior"> <webhttp /> </behavior> </endpointbehaviors> </behaviors> <services> <service behaviorconfiguration="restpracownicyservicebehaviour" name="restservices.restpracownicyservice"> <endpoint address="" behaviorconfiguration="restpracownicyserviceendpointbehavior" binding="webhttpbinding" contract="restservices.irestpracownicyservice" /> </service> </services> </system.servicemodel> g. Uruchom aplikację: W panelu Solution Explorer wywołaj prawym klawiszem myszy menu kontekstowe dla projektu RESTServices i wybierz Debug Start New Instance. Wpisz w pasku adresu przeglądarki adres: http://localhost:8081/restpracownicyservice.svc/pracownicy/w W rezultacie wyświetlona zostanie lista obiektów JSON z danymi pracowników których nazwisko zawiera literę 'W'. 5. Dodanie aplikacji klienckiej. a. Z menu głównego wybierz File New Project. Z list szablonów dla Web wybierz ASP.NET Empty Web Application, język Visual C#. UWAGA! Z listy dla opcji Solution wybierz Add to solution. Kliknij przycisk OK.
b. W panelu Solution Explorer wywołaj prawym klawiszem myszy menu kontekstowe dla projektu WebApplication1 i wybierz opcję Add New Item. Wybierz szablon Web Form, język Visual C# i zmień nazwę pliku na Default.aspx. Kliknij przycisk Add.
c. W poniższym podpunkcie dodane zostanie właściwe wywołanie serwisu REST. Z wykorzystaniem wtyczki JQuery - AutoComplete() wyświetlana będzie pobrana lista podpowiedzi pobrana z serwisu. Otwórz plik Default.aspx w trybie Source. Znacznik <body></body> zastąp: <body> <form id="form1" runat="server"> <div> <asp:label ID="labPracownikNazwa" runat="server" Text="Pracownik : " /> <asp:textbox ID="tbPracownikNazwa" runat="server" /> <div id="log" class="log-content" ></div> </div> </form> </body> Znacznik <head></head> zastąp : <head runat="server"> <title>pracownicy</title> <link href="style.css" rel="stylesheet" type="text/css" /> <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.js" type="text/javascript"></script> <script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.8.18/jquery-ui.js" type="text/javascript" ></script> <script type="text/javascript"> $(function () $("#<%= tbpracowniknazwa.clientid %>").autocomplete( request.term, source: function (request, response) $.ajax( cache: false, type: "GET", async: false, url: "http://localhost:8081/restpracownicyservice.svc/pracownicy/" + datatype: "json", success: function (data) response( $.map( data, function( item ) return label: item.name, value: item.value ));,, ); error: function (xhr) alert(xhr.responsetext); ); ); </script> </head> select: function (event, ui) $("<div/>").text("selected: " + ui.item.value).prependto("#log"); $("#log").scrolltop(0);
d. W panelu Solution Explorer wywołaj prawym klawiszem myszy menu kontekstowe dla projektu WebApplication1 i wybierz opcję Add New Item. Wybierz szablon Style Sheet, język Visual C# i zmień nazwę pliku na style.css. Kliknij przycisk Add. e. Otwórz plik style.css i zastąp jego zawartość poniższym:.log-content margin: 10px 10px 10px 10px; height: 200px; width: 300px; overflow: auto; border: 1px solid #DDD;.ui-autocomplete position: absolute; cursor: default; border: 1px solid #DDD; background: #EEE; * html.ui-autocomplete width: 1px;.ui-menu list-style: none; padding: 2px; margin: 0; display: block; float: left;.ui-menu.ui-menu margin-top: -3px;.ui-menu.ui-menu-item margin: 0; padding: 0; zoom: 1; float: left; clear: left; width: 100%;.ui-menu.ui-menu-item a text-decoration: none; display: block; padding:.2em.4em; line-height: 1.5; zoom: 1;.ui-menu.ui-menu-item a.ui-state-hover,.ui-menu.ui-menu-item a.ui-state-active font-weight: bold; margin: -1px;
6. Problem z Cross-domain Żądania HTTP typu "Cross-domain" lub "Cross-site" to z reguły żądania o zasoby wysyłane do innych domen niż ta która wykonała żądanie. Ogólnie rzecz biorąc, domyślnym zachowaniem przeglądarek jest blokowanie dostępu typu "cross-domain". Z perspektywy przeglądarki domena jest url czyli np. ładując obraz z "http://ostry.com:8080/image.jpg", domeną pliku obrazu jest "http://ostry.com:8080". W naszym przypadku Visual Studio przydziela dla RESTPracownicyService numer portu 8081, a dla aplikacji internetowej WebApplication1 losowy numer portu - inaczej mówiąc mają różne domeny. Kiedy "AJAX" wywołuje usługę WCF REST z aplikacji internetowej przeglądarka uznaje takie żądanie za "cross-domain". Aby rozwiązać ten problem należy sprawić, aby usługa WCF REST informowała przeglądarkę, że w tym przypadku "cross-domain" jest możliwy. a. W panelu Solution Explorer wywołaj prawym klawiszem myszy menu kontekstowe dla projektu RESTServices i wybierz opcję Add New Item. Wybierz szablon Global Application Class, pozostaw nazwę pliku bez zmian. Kliknij przycisk Add b. Otwórz plik Global.asax.cs. Zmień implementację metody Application_Start na poniższą: protected void Application_Start(object sender, EventArgs e) RouteTable.Routes.Add(new ServiceRoute("", new WebServiceHostFactory(), typeof(restpracownicyservice))); Zmień implementację metody Application_BeginRequest na poniższą:
protected void Application_BeginRequest(object sender, EventArgs e) HttpContext.Current.Response.Cache.SetCacheability(HttpCacheability.NoCache); HttpContext.Current.Response.Cache.SetNoStore(); HttpContext.Current.Response.AddHeader("Access-Control-Allow-Origin", "*"); c. Zaimportuj brakujące referencje. Z pewnością koniecznie będzie dodanie do projektu referencji do System.ServiceModel.Activation.dll. W panelu Solution Explorer wywołaj prawym klawiszem myszy menu kontekstowe dla projektu RESTServices i wybierz opcję Add Reference. W oknie wyboru referencji przejdź do zakładki.net, wyszukaj z listy System.ServiceModel.Activation. Kliknij przycisk OK. d. Uruchom projekt RESTServices, a następnie projekt WebApplication1. Po wpisaniu w polu "Pracownik : " dowolnego tekstu pojawi się lista pracowników, których nazwisko zawiera podany łańcuch znaków.