sobota, 16 listopada 2013

[HTML|JS|CSS] jQuery: Pluginy i rozszerzanie

Na githubie aż roi się od przeróżnych pluginów do jQuery pisanych przez mniej lub bardziej sprawnych programistów. Jak napisać własny plugin ? Przykład poniżej.

(function($){

 $.fn.print = function(props) {
  var functions = $.fn.print.functions;
  if ($.isArray(props)) {
   functions = $.merge(functions, props);
  }
  return this.each(function() {
   var element = $(this);
   functions.forEach(function(fn){
    console.log(fn, ': ', $(element)[fn]());
   });
  });
 };

 $.fn.print.functions= ['height', 'width'];

})(jQuery);

Trick z immediate function to zabezpieczenie na wypadek, gdyby aplikacja używała $ (który jest aliasem dla obiektu jQuery) do innych celów. Pisząc plugin rozszerza się obiekt fn, który jest referencją na prototype obiektu jQuery. Korzystając z faktu, że każda funkcja jest obiektem, możemy załączyć do niej obiekt, który może zostać wykorzystany, jako domyślne ustawienia pluginu.

Plugin wywołujemy na dowolnym selektorze, na przykład:

$(function(){
 $('div').print();
 $('a').print(['size']);
 $('button').print(['html']);
});

Drugi sposób na uzyskanie podobnego efektu, to funkcja extend z jQuery, którą wykorzysujemy do łączenia dwóch obiektów JavaScript w jeden. Wywołanie tej funkcji na $.fn spowoduje rozszerzenie jQuery o naszą funkcję.

(function($){

 $.fn.extend({
  count: function(){
   console.log(this.length);
  }
 });

})(jQuery);

$(function(){
 $('div').count();
});

sobota, 9 listopada 2013

[HTML|JS|CSS] jQuery: Lightbox

Lightbox to popularna na stronach internetowych technika wyświetlania obrazów z wyszarzonym tłem, znana na przykład z allegro. W tym poście przedstawione zostanie, jak zaimplementować taką funkcjonalność z użyciem jQuery.


Odnośniki do obrazków będą zwykłymi tagami typu <a>. Po kliknięciu w odnośnik zostaną dodane dwie warstwy: zewnętrzna (overlay) i ta, na której wyświetlany jest obrazek (lightbox).

<!DOCTYPE html>
<html>
<head>
 <meta charset="utf-8" />
 <title>Lightbox</title> 
 <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.1/jquery.min.js"></script>
    <script src="scripts/myscript.js"></script>
</head>
<body>
 <h2>Nice cars list</h2>
 <ul>
  <li>
   <a href='/images/bmw_seria6.jpg' class='lightbox'>BMW Seria 6</a>
   <a href='/images/hyundai_genesis.jpg' class='lightbox'>Hyundai Genesis</a>
   <a href='/images/lexus_ct200h.jpg' class='lightbox'>Lexus Ct200h</a>
   <a href='/images/mercedes_slk.jpg' class='lightbox'>Mercedes SLK</a>
   <a href='/images/nissan_350z.jpg' class='lightbox'>Nissan 350z</a>
  </li>
 </ul>
</body>
 <style>
  #lightbox_overlay {
   position:absolute;
   top:0;
   left:0;
   height:100%;
   width:100%;
   background:black url(spinner.gif) no-repeat scroll center center;
  }
  #lightbox {
   position:absolute;
  }
 </style>
</html>

Overlay pozycjonowany jest absolutnie z wymiarami na 100%. Pozycję lightboxa wylicza się tak, aby trafił na środek ekranu. Animacja opacity daje efekt półprzezroczystej warstwy.

$(document).ready(function(){
 $('a.lightbox').click(function(e) {
  $('body').css('overflow-y', 'hidden');
  $('<div id="lightbox_overlay"></div>').css('top', $(document).scrollTop())
  .css('opacity', '0')
  .animate({'opacity': '0.5'}, 'slow')
  .appendTo('body');
 $('<div id="lightbox"></div>')
  .hide()
  .appendTo('body');
 $('<img />')
  .attr('src', $(this).attr('href'))
  .load(function() {
   positionLightboxImage();
  })
  .click(function() {
   removeLightbox();
  })
  .appendTo('#lightbox');
  return false;
 });
 });

function positionLightboxImage() {
 var top = ($(window).height() - $('#lightbox').height()) / 2;
 var left = ($(window).width() - $('#lightbox').width()) / 2;
 $('#lightbox')
  .css({
   'top': top + $(document).scrollTop(),
   'left': left
  })
 .fadeIn();
}

function removeLightbox() {
 console.log('asd');
 $('#lightbox_overlay, #lightbox')
  .fadeOut('slow', function() {
   $(this).remove();
   $('body').css('overflow-y', 'auto');
  });
}

czwartek, 7 listopada 2013

[WCF] REST

REST (Representational State Transfer) to drugi obok SOAP (Simple Object Access Protocol) wzorzec budowy aplikacji wymagających komunikacji pomiędzy stroną kliencką i serwerową. Oparty jest na protokole HTTP (w przeciwieństwie do niezależnego od protokołów SOAP). Wzorzec ten zakłada prostą definicję dostępu do zasobów (poprzez czasowniki HTTP, takie jak GET, POST, PUT, DELETE). Różna są też formaty, w jakich zwracany jest ten zasób, głównie jest to XML i JSON. Mówi się więc, że REST to wzorzec nastawiony na zasoby, natomiast SOAP kładzie nacisk na akcje (wykonywane na zasobach). Dużą przewagą REST-a jest interoperacyjność i skalowalność. Pomiędzy REST i SOAP definiuje się POX (Plain Old Xml) - przesyłanie danych w formacie XML, ale bez narzutów SOAP.

Dla samego REST wykorzystujemy w WCF webHttpBinding oraz webHttpBehavior. Adresowanie zasobów odbywa się poprzez dwa atrybuty zawarte w System.ServiceModel.Web: WebGet i WebInvoke. Pierwszy z nich definiuje operacje wykonywane przy żądaniach typu GET, drugi zapewnia obsługę pozostałych żądań.

[ServiceContract]
public interface IEvaluationService
{
    [OperationContract]
    [WebInvoke(Method ="POST", UriTemplate = "evals")]
    void SubmitEvaluation(Evaluation eval);

    [OperationContract]
    [WebGet(UriTemplate = "evals", ResponseFormat = WebMessageFormat.Json)]
    List<Evaluation> GetEvaluations();

    [OperationContract]
    [WebGet(UriTemplate = "eval/{id}")]
    Evaluation GetEvaluation(string id);

    [OperationContract]
    [WebInvoke(Method = "DELETE", UriTemplate = "eval/{id}")]
    void RemoveEvaluation(string id);
}

Poprzez atrybuty WebGet i WebInvoke można także ustawić format zwracanej / przyjmowanej wiadomości. Domyślnie jest to XML. Plik konfiguracyjny dla aplikacji hostującej jest bardzo krótki, a wszystko dzięki specjalnej klasie hostującej REST-owe serwisy.

<?xml version="1.0"?>
<configuration>

  <system.web>
    <compilation debug="true"/>
  </system.web>
  <system.serviceModel>
    <services>
      <service name="EvaluationServiceLibraryRest.EvaluationService" >
        <host>
          <baseAddresses>
            <add baseAddress="http://localhost:18081/evaluations/"/>
          </baseAddresses>
        </host>
      </service>
    </services>
  </system.serviceModel>
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/></startup></configuration>


WebServiceHost host = new WebServiceHost(typeof(EvaluationService));
try
{
    host.Open();
    Console.ReadKey();
    host.Close();
}
catch (Exception e)
{
    Console.WriteLine(e.Message);
    Console.ReadKey();
    host.Abort();
}

czwartek, 31 października 2013

[WCF] Clients

Stronę kliencką po utworzeniu serwisów WCF można stworzyć na dwa sposoby. Strona serwerowa wystawia metadane pod specjalnym adresem MEX (Metadata EXchange) lub plik WSDL. Na podstawie tych danych możemy za pomocą narzędzia svcutil wygenerować kod kliencki oraz plik z ustawieniami. Narzędzie svcutil.exe wymaga korzystania z command line, dlatego Microsoft od VS 2008 wprowadził funkcjonalność AddServiceReference, która na podstawie podanego adresu z metadanymi tworzy kod w C#, konfigurację oraz dodaje do projektu klienckiego odpowiednie dll-ki. Wygenerowany kod można swobodnie przegenerowywać zmieniając niektóre opcje (klikamy prawym przyciskiem myszy na referencji i wybieramy Update Service Reference).




W oknie możemy wybrać między innymi, czy chcemy generować asynchroniczne wywołania operacji z Operation Contract-ów. Ponadto możemy wybrać, na jaki tym mają być rzutowane kolekcje (lista, tablica itd.). Co więcej, w przypadku, gdy strona kliencka i serwerowa znajdują się w jednej solucji, możemy reużywać niektóre typy zdefiniowane po stronie serwerowej.

Serwisy możemy wywoływać tworząc jawnie channele za pomocą klasy ChannelFactory<T> lub korzystając z wygenerowanych klas proxy, które przejmują odpowiedzialność tworzenia channeli.

bool useProxy = new Random().Next()%2 == 0;
IEvaluationService channel = null;

try
{
    if (!useProxy)
    {
        //Transparent proxy
        ChannelFactory<IEvaluationService> factory =
            new ChannelFactory<IEvaluationService>("WSHttpBinding_IEvaluationService");
        channel = factory.CreateChannel();
    }
    else
    {
        //Generated proxy
        channel = new EvaluationServiceClient("WSHttpBinding_IEvaluationService");
    }

    var eval = new Evaluation()
    {
        TimeSent = DateTime.Now,
        Comments = "Nice",
        Submitter = "Johnny"
    };

    channel.SubmitEvaluation(new EvaluationRequest()
    {
        EvaluationBody = eval
    });

    ((IClientChannel)channel).Close();
}
catch (FaultException e)
{
    ((IClientChannel)channel).Abort();
}
catch (CommunicationException e)
{
    ((IClientChannel)channel).Abort();
}
catch (TimeoutException e)
{
    ((IClientChannel)channel).Abort();
}

Ważne aby odpowiednio obsługiwać wyjątki w WCF.FaultException, to propagacja wyjątku powstałego w kodzie po stronie serwerowej. CommunicationException wynika ze złej konfiguracji endpointu po którejś ze stron (np. źle podana nazwa do konstruktora ChannelFactory). TimeoutException zostanie rzucony, jeżeli serwer nie odpowie przez dłużej niż ustalony przez klienta próg.

Podstawowa różnica pomiędzy poleceniami Close i Abort polega na tym, że pierwsze zamyka połączenie w sposób prawidłowy, natomiast drugie warto wykorzystywać, jeżeli chcemy zamknąć połączenie natychmiast. Polecenie Close zostaje wstrzymane do póki nie zakończą się wszystkie operacje, także te asynchroniczne, może ono także rzucić wyjątkiem typu CommunicationException lub TimeoutException.

Nazwa klasy klienckiej zostaje wygenerowana przez konwencję (odcięcie od nazwy interfejsu pierwszej litery "I" oraz dodanie na końcu "Client"). Również przez konwencję tworzone są metody asynchroniczne, do których podajemy callback, który wywoła się po otrzymaniu odpowiedzi.

var client = channel as EvaluationServiceClient;
client.GetEvaluationsCompleted += (e, o) => Console.WriteLine(o.Result.Length);
client.GetEvaluationsAsync();

Metadane można pobierać nie tylko przez VS czy svcutil, ale także w kodzie. Przykład poniżej.

var endpoints = MetadataResolver.Resolve(typeof (IEvaluationService),
                             new EndpointAddress("http://localhost:8732/evals/mex/"));

foreach (var endpoint in endpoints)
    Console.WriteLine(endpoint.Name);

poniedziałek, 28 października 2013

[WCF] Address, Binding

Zdefiniowanie serwisu WCF wymaga określenie tzw. ABC, czyli adresu, bindingów i kontraktu, które odpowiadają na pytania gdzie ? jak ? i co ?

Adres definiuje, gdzie serwis jest hostowany. WCF wspiera różne sposoby przesyłania danych, a co za tym idzie różne są rodzaje adresów. Poniższa tabela przedstawia sposoby adresowania w WCF.

Technologia Przykładowy adres Opis
HTTP http://localhost:8001 Najczęściej używany tekstowy sposób przesyłania danych, dane przeważnie w XML lub JSON
HTTPS https://localhost:8001 Bezpieczny sposób przesyłania danych po HTTP. Szyfrowanie za pomocą TLS lub SSL.
TCP Peer network net.tcp://localhost:8002/Service1, net.p2p://localhost/ Bardzo szybki sposób przesyłania danych w formacie binarnym.
IPC (Inter-process communication over named pipes) net.pipe://localhost/PipeService1 Szybki sposób przesyłania danych w obrębie jednej maszyny (współdzielona pamięć).
MSMQ (Microsoft Message Queue) net.msmq://localhost Wiadomości wysyłane przez klienta są kolejkowane. Użyteczny sposób dla scenariuszy, w których istnieje możliwość rozłączenia klienta z serwerem.

Kluczowym elementem architektury WCF jest binding, odpowiadający na pytanie "jak?". Główne obszary, jakie definiuje binding to protokół transportu danych, format wiadomości oraz szczegóły przesyłania wiadomości specyficzne dla danego protokołu. WCF udostępnia szereg protokołów, niemniej jednak zawsze istnieje możliwość zdefiniowania własnego.Poniżej przedstawiono listę bindingów dostarczanych przez WCF. Te, które rozpoczynają się od przedrostka "net" są dedykowane dla technologii .NET, z kolei bindingi rozpoczynające się od "ws" reprezentują ogólnie przyjęte standardy implementowane w wielu technologiach.

Binding Opis
basicHttpBinding Wiadomości przesyłane po Http i zakodowane w Utf-8. Oparte na WS-I profile.
webHttpBinding Serwisy wystawiane jako requesty Http, format XML lub JSON umożliwia tworzenie serwisów w oparciu o wzorzec REST.
wsHttpBinding Korzysta z zaawansowanych profili WS-*, takich jak WS-Security, WS-Transactions, WS-BusinessActivity, etc.
wsDualHttpBinding Korzysta z tych samych profili, co wsHttpBinding ale wspiera komunikację typu duplex.
wsFederationHttpBinding Profile WS-* rozszerzone o federated identity.
netTcpBinding Wiadomości binarne przesyłane po TCP. Wymaga, aby obie strony korzystały z WCF.
netNamedPipeBinding Zoptymalizowany pod kątem komunikacji w obrębie jednej maszyny.
netPeerTcpBinding Komunikacja po TCP z wykorzystaniem technologii P2P.
netMsmqBinding Asynchroniczna komunikacja z wykorzystaniem kolejki komunikatów.

Bindingi można ustawiać w sposób proceduralny...

ServiceHost host = new ServiceHost(typeof(EvaluationService));

host.AddServiceEndpoint(typeof (IEvaluationService), 
    new BasicHttpBinding(), "http://localhost:8080/evals/");

...ale znacznie wygodniej jest robić to w ustawieniach projektu (App.config lub Web.config), gdzie mamy do dyspozycji narzędzie wbudowane w Visual Studio.


W oknie tym możemy ustawić behavior dla określonego typu bindingu zmieniając domyślne parametry. Po zamknięciu okna zmiany zostaną przeniesione do pliku XML z konfiguracją. Na przykład:

<configuration>
  <system.web>
    <compilation debug="true" />
  </system.web>
  <system.serviceModel>
    <bindings>
      <wsHttpBinding>
        <binding name="MyBindingConfiguration" allowCookies="true" />
      </wsHttpBinding>
    </bindings>
    <services>
      <service name="EvaluationServiceLibrary.EvaluationService">
        <endpoint address="/evals/" binding="wsHttpBinding" bindingConfiguration="MyBindingConfiguration"
          contract="EvaluationServiceLibrary.IEvaluationService">
          <identity>
            <dns value="localhost" />
          </identity>
        </endpoint>
        <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
        <host>
          <baseAddresses>
            <add baseAddress="http://localhost:8732/" />
          </baseAddresses>
        </host>
      </service>
    </services>
    <behaviors>
      <serviceBehaviors>
        <behavior>
          <serviceMetadata httpGetEnabled="True"/>
          <serviceDebug includeExceptionDetailInFaults="False" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
  </system.serviceModel>
</configuration>

niedziela, 27 października 2013

[WCF] ServiceContract, DataContract, MessageContract

Kontrakty w WCF można porównać do kontraktów w życiu codziennym. Podpisując jakiś kontrakt obie strony potwierdzają, że są świadome pewnych warunków. W WCF kontrakt definiuje między innymi wystawione serwisy, adresy, rodzaje wiadomości, jakie te serwisy odbierają i zwracają. WCF wykorzystuje  WSDL (Web Services Description Language) oraz XSD do dostarczenie metadanych na temat wystawionych serwisów. Ważne jest, aby kontrakty były niezależne od technologii, dlatego definiuje się je w neutralnym formacie bazującym na XML. Dzięki temu WebSerwisy wystawione na przykład w technologiach Javy mogą być wykorzystywane przez proxy WCF i aplikację napisaną w .NET i na odwrót.

ServiceContract

Definiuje funkcjonalność wystawioną przez serwis WCF. Dobrą praktyką jest oznaczanie tym atrybutem interfejsu .NET tak, aby zapewnić pewną warstwę abstrakcji nad konkretnymi implementacjami. Przykładowo:

[ServiceContract]
public interface IEvaluationService
{
    [OperationContract(IsOneWay = true)]
    void SubmitEvaluation(Evaluation eval);
    [OperationContract]
    List<Evaluation> GetEvaluations();
}

Property IsOneWay oznacza, że dana operacja nie zwraca wyniku. Konkretną implementację interfejsu można konfigurować poprzez atrybut ServiceBehavior.

[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single,
    ConcurrencyMode = ConcurrencyMode.Single)]
public class EvaluationService : IEvaluationService
{
    private List<Evaluation> _evals = new List<Evaluation>();

    public void SubmitEvaluation(Evaluation eval)
    {
        _evals.Add(eval);
    }

    public List<Evaluation> GetEvaluations()
    {
        return _evals;
    }
}

InstanceContextMode:  Single - Singleton, PerCall - zawsze nowa instancje, Session - każdy klient dostaje unikalną instancje w obrębie sesji.
ConcurrencyMode - Single - tylko jeden wątek ma dostęp do obiektu, Multiple - dostępny dla wielu wątków, Reentrant - nowe operacje mogą być wykonywane tylko podczas wywoływania innego serwisu.

DataContract

Definiuje typy, które mają być serializowane i przesyłane dalej przez WCF. Domyślny serializator to DataContractSerializer, który sprawdza, które obiekty są oznaczone tym atrybutem. Serializowane będą te propercje lub pola, które są oznaczone atrybutami DataMember.

[DataContract]
public class Evaluation
{
    [DataMember(IsRequired = true)]
    public string Submitted { get; set; }
    [DataMember(Order = 2)]
    public DateTime TimeSent { get; set; }
    [DataMember]
    public string Comments { get; set; }
}

Jeżeli w odebranej wiadomości nie będzie wartości, którą możnaby zdeserializować na property oznaczone z opcją IsRequired, aplikacja zwróci błąd. Poprzez opcję Order możemy ustawiać kolejność elementów w XML-u.

Problem może pojawić się w sytuacji, gdy chcemy wykorzystać mechanizm polimorfizmu. Jeżeli w sygnaturach metod mamy klasy bazowe, natomiast chcemy zwrócić klasę po nich dziedziczącą musimy użyć atrybutu KnownType. Znane typy dziedziczące po klasie bazowej zostaną dodane do kontraktu..

[DataContract]
[KnownType(typeof(Car))]
public abstract class Vehicle
{
    [DataMember]
    public string Name { get; set; }
}

[DataContract]
public class Car : Vehicle
{
    [DataMember]
    public int Year { get; set; }
}

MessageContract

Pozwala na zdefiniowanie, które pola mają znajdować się w nagłówku SOAP, a które w body. Typ oznaczony typ atrybutem przekazujemy do metody oznaczonej atrybutem OperationContract. 

[DataContract]
public class Evaluation
{
    [DataMember(IsRequired = true)]
    public string Submitted { get; set; }
    [DataMember(Order = 2)]
    public DateTime TimeSent { get; set; }
    [DataMember]
    public string Comments { get; set; }
}

[DataContract]
public class CustomHeader
{
   [DataMember]
   public string Username { get; set; }
}

[MessageContract]
public class EvaluationRequest
{
    [MessageHeader]
    public CustomHeader EvaluationHeader { get; set; }

    [MessageBodyMember]
    public Evaluation EvaluationBody { get; set; }
}

Dzięki temu wiadomość SOAP przyjmuje poniższą postać:

<s:Envelope xmlns:a="http://www.w3.org/2005/08/addressing" xmlns:s="http://www.w3.org/2003/05/soap-envelope">
  <s:Header>
    <a:Action s:mustUnderstand="1">http://tempuri.org/IEvaluationService/SubmitEvaluation</a:Action>
    <h:EvaluationHeader xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns:h="http://tempuri.org/">
      <Username xmlns="http://schemas.datacontract.org/2004/07/EvaluationServiceLibrary">Joe</Username>
    </h:EvaluationHeader>
  </s:Header>
  <s:Body>
    <EvaluationRequest xmlns="http://tempuri.org/">
      <EvaluationBody xmlns:d4p1="http://schemas.datacontract.org/2004/07/EvaluationServiceLibrary" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
        <d4p1:Comments>Git</d4p1:Comments>
        <d4p1:Submitted>Yes</d4p1:Submitted>
        <d4p1:TimeSent>2013-10-27T12:46:00</d4p1:TimeSent>
      </EvaluationBody>
    </EvaluationRequest>
  </s:Body>
</s:Envelope>

wtorek, 8 października 2013

[WCF] Wprowadzenie

Windows Communication Foundation to technologia, którą świat ujrzał z .NET Framework 3.0 jako odpowiedź na potrzebę budowania rozproszonych systemów, które MS zaczął marketingowo nazywać "Connected Systems". Podczas tworzenia aplikacji rozproszonych ważne jest, aby umożliwić komunikowanie się systemów napisanych w różnych technologiach. Stąd pomysł serwisów jako jednostek udostępniających pewne funkcjonalności poprzez przesyłanie ustandaryzowanych wiadomości.

Dominują dwa wzorce, SOAP i REST.

SOAP (Simple Object Access Protocol) - wiadomości wysyłane są w formacie XML i WS-* protocols. Wspiera wiele protokołów transportowych np. HTTP, TCP, MSMQ.

REST (Representational State Transfer) - różne formaty wiadomości (XML, coraz częściej JSON), protokół transportowy HTTP, paradygmat ukierunkowany na adresowanie, modyfikowanie i odczytywanie zasobów.

WCF wprowadza unifikację, umożliwiając wymianę wiadomości po różnych protokołach, w różnych formatach, wzorcach.

Większość funkcjonalności WCF zawarte jest w System.ServiceModel.dll. Najprościej filozofię WCF oddaje zdanie: serwisy wystawiają swoje funkcjonalności poprzez endpointy, natomiast klienci konsumują je poprzez kanały. WCF-owe endpointy składają się z trzech składowych ABC definiujące podstawowe pojęcia (Address - gdzie?, Binding - jak?, Contract - co?).

Tworzenie najprostszej aplikacji WCF:

Pierwszy projekt to serwisy WCF.

[DataContract]
public class Name
{
    [DataMember]
    public string First { get; set; }

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

[ServiceContract]
public interface IMyFirstService
{
    [OperationContract]
    string SayHello(Name name);
}

public class MyFirstService : IMyFirstService
{
    public string SayHello(Name name)
    {
        return string.Format("Hello, {0} {1}", name.First, name.Last);
    }
}

Klasa Name będzie przesyłaną wiadomością, natomiast oznaczenie interfejsu przez atrybuty definiuje, jakie operacje chcemy wystawić na zewnątrz. Kolejne zmiany nanosimy w pliku App.config.Wystawiamy jeden serwis dostępny pod trzema endpointami wykorzystującymi różne protokoły transportowe.

<services>
  <service name="WcfEssentials.MyFirstService">
    <host>
      <baseAddresses>
        <add baseAddress="http://localhost:8080/helloworld/"   />
      </baseAddresses>
    </host>
    <endpoint address="ws"  binding="wsHttpBinding" contract="WcfEssentials.IMyFirstService" />
    <endpoint address="basic"  binding="basicHttpBinding" contract="WcfEssentials.IMyFirstService" />
    <endpoint address="net.tcp://localhost:8081/helloworld/"  binding="netTcpBinding" contract="WcfEssentials.IMyFirstService" />
    <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
  </service>
</services>

Uruchomienie aplikacji spowoduje pojawienie się okna, w którym możemy testować nasze endpointy.


Jeżeli chcemy wykorzystać nasz serwis w kodzie, dodajemy osobny projekt, np. aplikację konsolową. Następnie musimy dodać referencję do serwisów WCF. Klikamy prawym przyciskiem myszy i wybieramy Add Service Reference....


Strona kliencka pobierze metadane i utworzy obiekty proxy, którymi będziemy mogli się łączyć do stworzonych wcześniej serwisów. W pliku app.config automatycznie wygenerują się bindingi dla strony klienckiej, gdzie każdy endpoint otrzyma swoją nazwę. Wywołanie serwisu w kodzie C# jest bardzo proste.

using (var client = new MyFirstServiceClient("NetTcpBinding_IMyFirstService"))
{
    var name = new Name() { First = "Tom", Last = "Smith" };
    Console.WriteLine(client.SayHello(name));
    Console.Read();
}

Jako parametr konstruktora podajemy wygenerowaną przez VS nazwę serwisu.

niedziela, 6 października 2013

[SQL] T-SQL: Common Table Expression

CTE występuje tylko w Transact - SQL-u i można go nazwać tymczasowym wynikiem otrzymanym w wyniku jednego zapytania na potrzebny innej operacji. Rozwiązanie takie poprawia czytelność dużych zapytań i może być wykorzystane w różnych sytuacjach:
  • zapytania rekurencyjne
  • odwoływanie się do jakiegoś wyniku wielokrotnie (dzięki nazwanemu CTE odwołujemy się do nazwy)
  • grupowanie wynikające z pewnych operacji agregujących
Poniżej przykład użycia Common Table Expression.

USE Cars;
GO
WITH Rated_Cars_CTE (Make, Score, Price)
AS
( SELECT Marka, ISNULL([Ocena subiektywna],0) + 
ISNULL([Ocena subiektywna],0), [Cena 2008]
FROM dbo.Cars
WHERE [Cena 2008] IS NOT NULL)

SELECT t.RANGE AS [Score Range], count(*) AS [Number of Occurences]
FROM (
  SELECT CASE  
    WHEN score between 0 and 1 THEN ' 0- 1'
    WHEN score between 1 and 2 THEN ' 1- 2'
    WHEN score between 2 and 3 THEN ' 2- 3'
    WHEN score between 3 and 4 THEN ' 3- 4'
    WHEN score between 4 and 5 THEN ' 4- 5'
    WHEN score between 5 and 6 THEN ' 5- 6'
    WHEN score between 6 and 7 THEN ' 6- 7'
    WHEN score between 7 and 8 THEN ' 7- 8'
    WHEN score between 8 and 9 THEN ' 8- 9'
    WHEN score between 9 and 10 THEN ' 9- 10'
    ELSE '10+' END AS RANGE
  FROM Rated_Cars_CTE) t
GROUP BY t.RANGE

sobota, 5 października 2013

[SQL Server] Import / eksport danych z Excela

Gdy chcemy w szybki sposób przenieść małą bazę danych, możemy ją wyeksportować do Excela. Rozpoczynamy od założenia pustego arkusza kalkulacyjnego i zapisania go na dysku. Następnie otwieramy Sql Server Management Studio i logujemy się do uruchomionej instancji MSSQL.

Na liście baz wybieramy tą, którą chcemy wyeksportować i klikamy na nią prawym przyciskiem myszy.





Przechodzimy przez uwierzytelnianie i wybieramy typ eksportu.



Dochodzimy do okna, w którym możemy wybrać te tabele, które chcemy eksportować.


Po wybraniu przechodzimy do ekranu z podsumowaniem, gdzie po wybraniu Finish nastąpi eksport danych.




Dane zostaną wyeksportowane do arkusza o nazwie Words. Import możemy wykonać w symetryczny sposób. Tym razem jako źródło danych wybieramy arkusz Excala podając ścieżkę.

niedziela, 29 września 2013

[FullTextSearch] ElasticSearch: river MongoDB

Najpopularniejszym sposobem dodawania danych do indeksów ElasticSearch jest wysyłanie odpowiednich REST-owych wiadomości. Ale to nie jedyny sposób. Możemy czasem pobierać dane także bezpośrednio z zewnętrznych źródeł, na przykład z baz danych. Mechanizmem, który umożliwia synchronizację pomiędzy źródłem danych, a indeksem ElasticSearch jest river. Rivery są dostępne dla różnych baz NoSql, przykładowo dla MongoDB musimy zainstalować ten river. Aby uruchomić synchronizację pomiędzy instancją bazy, a indeksem ElasticSearch musimy wykonać poniższe kroki. Rozpoczynamy od instalacji pluginu do ElasticSearch. Wchodzimy do katalogu bin w ElasticSearch i wpisujemy polecenie

plugin.bat -i com.github.richardwilly98.elasticsearch/elasticsearch-river-mongodb/1.7.0

Warto pamiętać, że przy instalacji rivera muszą się zgadzać wersje ES i MongoDB. Kompatybilność możemy sprawdzić tutaj.

Teraz musimy przygotować bazę do pracy w trybie replikacji. Plugin river synchronizuje dane dzięki plikowi oplog.rs, udostępnianemu przez MongoDB tylko w trybie replikacji. Plik ten zawiera log z wszystkich zmian, jakie były wykonywane na danych. Pierwotnie jego celem było synchronizowanie danych pomiędzy wieloma replikami, ale wykorzystuje go także river. Do celów ElasticSearch można włączyć replikację składającą się z jednej repliki.

1. Uruchamiamy jeden proces, na którym chodzi baza poleceniem mongod --dbpath rsdata --replSet rs0
2. W drugim oknie wchodzimy do narzędzia konfiguracji poleceniem mongo
3. Wpisujemy polecenie rs.initiate() i czekamy, aż nasz node ustawi się jako PRIMARY (naciśnięcie enter po kilkunastu sekundach powinno upewnić nas o zmianie stanu)

Teraz uruchamiamy ElasticSearch pozostawiając MongoDB działające w innym oknie, tryb konfiguracji możemy opuścić.Wysyłamy PUT z konfiguracją mapowania kolekcji w bazie na indeks w ElasticSearch.

PUT http://localhost:9200/_river/mongolink/_meta HTTP/1.1
User-Agent: Fiddler
content-type: application/json
Host: localhost:9200
Content-Length: 214

{
  "type" : "mongodb",
  "mongodb" : {
   "servers" : [
    { "host" : "localhost", "port" : 27017 }
   ],
   "db" : "esbook",
   "collection" : "products"
  },
  "index" : {
   "name" : "esbook"
  }
}

Na indeksie _river stworzyliśmy typ mongolink, który będzie się mapował na kolekcję products z bazy esbook . Teraz możemy dodać dane, na przykład z konsoli mongo.

rs0:PRIMARY> use esbook
switched to db esbook
rs0:PRIMARY> db.products.insert({ "name" : "book", "value" : 200 });

Teraz możemy obejrzeć zmapowane zasoby pod adresem http://localhost:9200/esbook/_search?pretty

sobota, 28 września 2013

[FullTextSearch] ElasticSearch: Faceting

Terminem Faceting określa się technikę agregowania danych gromadzonych w indeksach ElasticSearch. Wysyłając odpowiednie zapytania możemy pobrać pewne informacje statystyczne na temat dokumentów, jakie przechowujemy. Pierwszy rodzaj statystyki, jaką możemy sobie stworzyć to grupowanie po tokenach utworzonych na danym polu, a następnie zliczenie ilości. Wysyłamy zapytanie...

POST http://localhost:9200/books/book/_search HTTP/1.1
User-Agent: Fiddler
content-type: application/json
Host: localhost:9200
Content-Length: 135

{
 "query" : { "match_all" : {} },
 "facets" : {
  "author_facet" : {
   "terms" : {
    "field" : "author"
   }
  }
 }
}

...i w odpowiedzi otrzymujemy:

{
   "took":4,
   "timed_out":false,
   "_shards":{
      "total":5,
      "successful":5,
      "failed":0
   },
   "hits":{
      "total":5,
      "max_score":1.0,
      "hits":[
       (...)
      ]
   },
   "facets":{
      "author_facet":{
         "_type":"terms",
         "missing":0,
         "total":10,
         "other":0,
         "terms":[
            {
               "term":"michio",
               "count":3
            },
            {
               "term":"kaku",
               "count":3
            },
            {
               "term":"roger",
               "count":2
            },
            {
               "term":"penrose",
               "count":2
            }
         ]
      }
   }
}

Dane możemy także agregować definiując zakresy, na przykład

POST http://localhost:9200/books/book/_search HTTP/1.1
User-Agent: Fiddler
content-type: application/json
Host: localhost:9200
Content-Length: 246

{
 "query" : { "match_all" : {} },
 "facets" : {
  "ranges_facet_result" : {
   "range" : {
    "field" : "year",
    "ranges" : [
     { "to" : 1995 },
     { "from" : 1995, "to" : 2000 },
     { "from" : 2000 }
    ]
   }
  }
 }
}

Zwrócone nam zostanie więcej informacji niż poprzednio, ponieważ agregujemy po polu typu numerycznego

{
   "took":145,
   "timed_out":false,
   "_shards":{
      "total":5,
      "successful":5,
      "failed":0
   },
   "hits":{
      "total":5,
      "max_score":1.0,
      "hits":[

      ]
   },
   "facets":{
      "ranges_facet_result":{
         "_type":"range",
         "ranges":[
            {
               "to":1995.0,
               "count":2,
               "min":1989.0,
               "max":1993.0,
               "total_count":2,
               "total":3982.0,
               "mean":1991.0
            },
            {
               "from":1995.0,
               "to":2000.0,
               "count":1,
               "min":1995.0,
               "max":1995.0,
               "total_count":1,
               "total":1995.0,
               "mean":1995.0
            },
            {
               "from":2000.0,
               "count":2,
               "min":2004.0,
               "max":2006.0,
               "total_count":2,
               "total":4010.0,
               "mean":2005.0
            }
         ]
      }
   }
}

Powyższe zapytanie możemy także wykonać przy równomiernym rozłożeniu danych (tak jak w histogramie).

POST http://localhost:9200/books/book/_search HTTP/1.1
User-Agent: Fiddler
content-type: application/json
Host: localhost:9200
Content-Length: 156

{
 "query" : { "match_all" : {} },
 "facets" : {
  "total_histogram" : {
   "histogram" : {
    "field" : "year",
    "interval" : 5
   }
  }
 }
}

Najwięcej danych do analizy dostarcza nam jednak zapytanie typu statistical.

POST http://localhost:9200/books/book/_search HTTP/1.1
User-Agent: Fiddler
content-type: application/json
Host: localhost:9200
Content-Length: 139

{
 "query" : { "match_all" : {} },
 "facets" : {
  "statistical_test" : {
   "statistical" : {
    "field" : "price"
   }
  }
 }
}

ElasticSearch zwraca między innymi zakres, średnią, wariancję, odchylenie standardowe i sumę kwadratów.

{
   "took":55,
   "timed_out":false,
   "_shards":{
      "total":5,
      "successful":5,
      "failed":0
   },
   "hits":{
      "total":5,
      "max_score":1.0,
      "hits":[(...)]
   },
   "facets":{
      "statistical_test":{
         "_type":"statistical",
         "count":5,
         "total":114.67999999999999,
         "min":12.06,
         "max":42.0,
         "mean":22.936,
         "sum_of_squares":3157.3112,
         "variance":105.40214400000013,
         "std_deviation":10.266554631423345
      }
   }
}

[FullTextSearch] ElasticSearch: Highlighting

Wyniki wyszukiwania bardzo często są wyświetlane bezpośrednio na interfejsie użytkownika, np. poprzez wstawienie ich bezpośrednio do pewnych kontenerów HTML-owych. W związku z tym bardzo przydatna okazuje się możliwość zaznaczania wyszukanej frazy. W ElasticSearch możemy skorzystać z takiej funkcjonalności w bardzo prosty sposób.

POST http://localhost:9200/books/book/_search HTTP/1.1
User-Agent: Fiddler
content-type: application/json
Host: localhost:9200
Content-Length: 276

{
 "query" : {
  "prefix" : {
   "author" : "ro"
  }
 },
 "highlight" : {
  "require_field_match" : true,
  "fields" : {
   "title" : { "pre_tags" : [ "<b>" ], "post_tags" : [ "</b>" ] },
   "author" : { "pre_tags" : [ "<b>" ], "post_tags" : [ "</b>" ] }
  }
 }
}

W odpowiedzi otrzymamy:

HTTP/1.1 200 OK
Content-Type: application/json; charset=UTF-8
Content-Length: 660

{"took":8,"timed_out":false,"_shards":{"total":5,"successful":5,"failed":0},"hits":{"total":2,"max_score":1.0,"hits":[{"_index":"books","_type":"book","_id":"5","_score":1.0, "_source" : {
 "title": "The Road to Reality: A Complete Guide to the Laws of the Universe",
 "author": "Roger Penrose",
 "year": 2004,
 "price": 42.00
},"highlight":{"author":["<b>Roger</b> Penrose"]}},{"_index":"books","_type":"book","_id":"4","_score":1.0, "_source" : {
 "title": "The Emperor's New Mind: Concerning Computers, Minds and The Laws of Physics",
 "author": "Roger Penrose",
 "year": 1989,
 "price": 22.74
},"highlight":{"author":["<b>Roger</b> Penrose"]}}]}}

Nieustawienie pola require_field_match spowoduje, że znaleziona fraza będzie zaznaczana we wszystkich skonfigurowanych polach, a nie tylko na tym polu, do którego wysyłamy zapytanie.

piątek, 27 września 2013

[FullTextSearch] ElasticSearch: Filtrowanie, Sortowanie, Paginacja

Filtrowanie

Filtrowanie zawęża zbiór dokumentów, na których odbywa się przeszukiwanie pełnotekstowe. Podejście takie znacznie poprawia wydajność zapytań. Filtr nie wpływa także na wynik wyszukiwania (pole _score) w zwracanej kolekcji wyników. Przykład poprawnego filtrowanego zapytania, gdzie kolekcja jest najpierw filtrowana, a później odpytana po indeksie:

POST http://localhost:9200/books/book/_search HTTP/1.1
User-Agent: Fiddler
Content-Type: application/json
Host: localhost:9200
Content-Length: 160

{
 "query": {
  "filtered" : {
   "query" : {
    "field" : { "author" : "Kaku" }
   },
    "filter" : {
    "term" : { "year" : 1993 }
   }
  }
 }
}

Filtrować można także zawężając przeszukiwany zbiór

POST http://localhost:9200/books/book/_search HTTP/1.1
User-Agent: Fiddler
Content-Type: application/json
Host: localhost:9200
Content-Length: 331

{
   "query":{
      "filtered":{
         "query":{
            "term":{
               "title":"worlds"
            }
         },
         "filter":{
            "range":{
               "year":{
                  "from":2000,
                  "to":2013
               }
            }
         }
      }
   }
}

Sortowanie

Ponieważ ElasticSearch może działać jako zwykła dokumentowa baza danych, musi także wspierać sortowanie zwracanych elementów. Wykorzystujemy do tego pole sort.

POST http://localhost:9200/books/book/_search HTTP/1.1
User-Agent: Fiddler
Content-Type: application/json
Host: localhost:9200
Content-Length: 156

{
"query" : {
 "match" : {
  "author" : {
   "query" : "penrose kaku",
   "operator" : "or"
  }
 }
},
  "sort" : [
    { "price" : "asc" }
  ]
}

Paginacja

Stronnicowanie jest dziś powszechnie stosowaną techniką umożliwiającą przesyłanie danych małymi porcjami. Również ElasticSearch wspiera stronnicowanie za pomocą pól from oraz size. 

POST http://localhost:9200/books/book/_search HTTP/1.1
User-Agent: Fiddler
Content-Type: application/json
Host: localhost:9200
Content-Length: 184

{
"query" : {
 "match" : {
  "author" : {
   "query" : "penrose kaku",
   "operator" : "or"
  }
 }
},
  "sort" : [
    { "price" : "asc" }
  ],
 "from" : 2,
 "size" : 2
}

[FullTextSearch] ElasticSearch: Query DSL

Zapytania do ElasticSearch wysyłamy REST-owo. Do prostego pobierania dokumentów wystarczą nam sparametryzowane zapytania typu GET. Aby jednak poznać pełnię możliwości ElasticSearch musimy poznać język zapytań DSL, w którym parametry zapytania ustawiamy poprzez załączenie z żądaniem typu POST odpowiedniego obiektu w postaci JSON. ElasticSearch udostępnia wiele rodzajów zapytań. Poniżej ciekawsze z nich.

Term Query

Tego typu zapytania można wykonywać na dwa sposoby. Przy użyciu czasownika GET

http://localhost:9200/books/book/_search?q=title:parallel&pretty=true

lub za pomocą czasownika POST

POST http://localhost:9200/books/book/_search?pretty=true HTTP/1.1
User-Agent: Fiddler
content-type: application/json
Host: localhost:9200
Content-Length: 59

{
 "query" : {
  "term" : { "title" : "parallel" }
 }
}

W obu przypadkach otrzymujemy ten sam rezultat

HTTP/1.1 200 OK
Content-Type: application/json; charset=UTF-8
Content-Length: 809

{
  "took" : 3,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "failed" : 0
  },
  "hits" : {
    "total" : 2,
    "max_score" : 0.095891505,
    "hits" : [ {
      "_index" : "books",
      "_type" : "book",
      "_id" : "1",
      "_score" : 0.095891505, "_source" : {
 "title": "Parallel Worlds: A Journey Through Creation, Higher Dimensions, and the Future of the Cosmos",
 "author": "Michio Kaku",
 "year": 2006,
 "price": 12.06
}
    }, {
      "_index" : "books",
      "_type" : "book",
      "_id" : "3",
      "_score" : 0.095891505, "_source" : {
 "title": "Hyperspace: A Scientific Odyssey Through Parallel Universes, Time Warps, and the Tenth Dimension",
 "author": "Michio Kaku",
 "year": 1995,
 "price": 16.36
}
    } ]
  }
}

Term Query to takie zapytanie, które zwraca wyniki tylko wtedy, gdy podamy dokładnie słowo, które znajduje się w danym polu. Nie zadziałają tutaj zapytania przedrostowe typu "paral*". Wyniki za każdym razem dostępne będą w tablicy dostępnej pod hits.hits. Pełny dokument zwracany jest w polu _source. Pobieranie tak zagnieżdżonych informacji wydaje się niewygodne, a czasami także niepotrzebnie przesyłany jest cały duży dokument, dlatego możemy podać, które pola nas interesują.

POST http://localhost:9200/books/book/_search?pretty=true HTTP/1.1
User-Agent: Fiddler
content-type: application/json
Host: localhost:9200
Content-Length: 92

{
 "fields" : ["title","author"],
 "query" : {
  "term" : { "title" : "parallel" }
 }
}

W rezultacie otrzymamy:

HTTP/1.1 200 OK
Content-Type: application/json; charset=UTF-8
Content-Length: 797

{
  "took" : 9,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "failed" : 0
  },
  "hits" : {
    "total" : 2,
    "max_score" : 0.095891505,
    "hits" : [ {
      "_index" : "books",
      "_type" : "book",
      "_id" : "1",
      "_score" : 0.095891505,
      "fields" : {
        "title" : "Parallel Worlds: A Journey Through Creation, Higher Dimensions, and the Future of the Cosmos",
        "author" : "Michio Kaku"
      }
    }, {
      "_index" : "books",
      "_type" : "book",
      "_id" : "3",
      "_score" : 0.095891505,
      "fields" : {
        "title" : "Hyperspace: A Scientific Odyssey Through Parallel Universes, Time Warps, and the Tenth Dimension",
        "author" : "Michio Kaku"
      }
    } ]
  }
}

Odpytywać można także tylko indeks (bez podawania typu book) oraz wiele indeksów lub wiele typów. Na przykład:

http://localhost:9200/books,books5/book/_search?q=author:kaku&pretty=true

Terms Query

Podajemy kilka wartości, które nie będą analizowane (muszą to być dokładne wartości), oraz opcjonalnie ile z nich musi pasować, aby zwrócony został dany dokument.

POST http://localhost:9200/books/book/_search?pretty=true HTTP/1.1
User-Agent: Fiddler
content-type: application/json
Host: localhost:9200
Content-Length: 111

{
 "query" : {
  "terms" : { "title" : ["parallel","hyperspace","worlds"],
  "minimum_match" : 2
  }
 }
}

Match Query

W tym zapytaniu podane wyrażenie zostaje przetworzone przez analizator. A zatem tym razem możemy podawać niepełne wyrazy, na przykład:

POST http://localhost:9200/books/book/_search?pretty=true HTTP/1.1
User-Agent: Fiddler
content-type: application/json
Host: localhost:9200
Content-Length: 65

{
 "query" : {
  "match" : { "title" :"para worlds"
  }
 }
}

Zapytanie takie zwróci wyniki. Domyślnie słowa są łączone operatorem OR, można to zmienić w następujący sposób.

POST http://localhost:9200/books/book/_search?pretty=true HTTP/1.1
User-Agent: Fiddler
content-type: application/json
Host: localhost:9200
Content-Length: 122

{
 "query" : {
  "match" : {
   "title" : {
    "query" : "parallel worlds",
    "operator" : "and"
   }
  }
 }
}

Match Phrase

Jeżeli chcemy wyszukać frazy składającej się z większej ilości słów, korzystamy z match_phrase, gdzie parametr slop definiuje, ile nieznanych słów może się mieścić pomiędzy podanymi wyrazami.

POST http://localhost:9200/books/book/_search?pretty=true HTTP/1.1
User-Agent: Fiddler
content-type: application/json
Host: localhost:9200
Content-Length: 121

{
 "query" : {
  "match_phrase" : {
   "title" : {
    "query" : "parallel worlds",
    "slop" : 1
   }
  }
 }
}

Multi Match

Efekt zapytania typu "Match" możemy uzyskać także na wielu polach dokumentu.

POST http://localhost:9200/books/book/_search?pretty=true HTTP/1.1
User-Agent: Fiddler
content-type: application/json
Host: localhost:9200
Content-Length: 131

{
 "query" : { 
  "multi_match" : {
   "query" : "univers michio penrose",
   "fields" : [ "title", "author" ]
  }
 }
}

Identifiers Query

Kiedy dokładnie znamy identyfikatory dokumentów, które chcemy pobrać, korzystamy z tego typu zapytania.

POST http://localhost:9200/books/book/_search?pretty=true HTTP/1.1
User-Agent: Fiddler
content-type: application/json
Host: localhost:9200
Content-Length: 73

{
 "query" : {
  "ids" : {
   "values" : [ "1", "4", "5" ]
  }
 }
}

Prefix Query

Wyszukiwanie po przedrostkach.

POST http://localhost:9200/books/book/_search?pretty=true HTTP/1.1
User-Agent: Fiddler
content-type: application/json
Host: localhost:9200
Content-Length: 63

{
 "query" : {
  "prefix" : {
   "title" : "uni"
  }
 }
}

Fuzzy Like This Query

Zapytanie, które dopuszcza literówki w podanej frazie.

POST http://localhost:9200/books/book/_search?pretty=true HTTP/1.1
User-Agent: Fiddler
content-type: application/json
Host: localhost:9200
Content-Length: 115

{
 "query" : {
  "fuzzy_like_this" : {
   "fields" : ["title", "author"],
   "like_text" : "penrse"
  }
 }
}

Range Query

Podajemy zakresy, przydatne dla wartości numerycznych.

POST http://localhost:9200/books/book/_search?pretty=true HTTP/1.1
User-Agent: Fiddler
content-type: application/json
Host: localhost:9200
Content-Length: 100

{
 "query" : {
  "range" : {
   "year" : {
    "from" : 1990,
    "to" : 2000
   }
  }
 }
}

czwartek, 26 września 2013

[FullTextSearch] ElasticSearch: Mapping

Po dodaniu dokumentów do indeksu ElasticSearch stworzy za nas domyślny mapping na podstawie dodawanych danych. Mappingi założone na danym indeksie możemy obejrzeć wysyłając zapytanie REST-owe.

GET http://localhost:9200/books/_mapping?pretty HTTP/1.1

Zwrócony nam zostanie mapping dla wszystkich typów założonych na danym indeksie.

HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
Content-Type: application/json; charset=UTF-8
Content-Length: 325

{
  "books" : {
    "book" : {
      "properties" : {
        "author" : {
          "type" : "string"
        },
        "price" : {
          "type" : "double"
        },
        "title" : {
          "type" : "string"
        },
        "year" : {
          "type" : "long"
        }
      }
    }
  }
}

Jeżeli kolejne dokumenty naruszą wcześniej zdefiniowany mapping (np. zmieniając typ jednego z pól w JSONie"), ElasticSearch zwróci nam błąd.

HTTP/1.1 400 Bad Request
Content-Type: application/json; charset=UTF-8
Content-Length: 129

{"error":"MapperParsingException[failed to parse [year]]; nested: NumberFormatException[For input string: \"a\"]; ","status":400}

Mappingi możemy także definiować ręcznie. Przed wysłaniem jakichkolwiek dokumentów wysyłamy żadanie typu POST pod adres zawierający nazwę nowego indeksu.

POST http://localhost:9200/books2/ HTTP/1.1
User-Agent: Fiddler
Host: localhost:9200
Content-Type: application/json
Content-Length: 384

{
  "mappings": {
    "book" : {
      "_source": {
        "enabled": false 
      },
      "properties" : {
        "author" : {
          "type" : "string"
        },
        "price" : {
          "type" : "double"
        },
        "title" : {
          "type" : "string"
        },
        "year" : {
          "type" : "long"
        }
      }
    }
  }
}

Ustawienie_source.enabled na false spowoduje, że zapytania zwracać nam będą jedynie identyfikatory dokumentów bez ich zawartości. Inny przykład pokazujący, co można ustawić dla poszczególnych properties.

{
  "mappings": {
    "book" : {
      "properties" : {
        "author" : {
          "type" : "string",
          "index" : "not_analyzed"
        },
        "price" : {
          "type" : "double",
          "store" : "yes"
        },
        "title" : {
          "type" : "string"
        },
        "year" : {
          "type" : "long"
        }
      }
    }
  }
}

Pole index ustawione na analyzed sprawi że wartości zostaną przeanalizowane i zaindeksowane przez ElastcSearch. Dla pól typu string ustawienie wartości not_analyzed spowoduje, że pole zostanie zaindeksowane, ale nie będzie przetwarzane przez analyzer, tak więc search zwróci wyniki tylko w przypadku, gdy podamy dokładnie któryś z wyrazów zawartych w tym polu. Nie będzie zatem możliwe na przykład przeszukiwanie po przedrostkach typu "Mi*". Pole store odpowiada temu, czy oryginalna wartość pola powinna być wpisywana do indeksu. Dodatkowo dla dat możemy ustawiać format zgodnie z tą listą. Przy bardziej zaawansowanych problemach możemy także dla danego indeksu wybrać specjalny analizator.

Maping dynamiczny można także w pewien sposób konfigurować. Jeżeli chcemy wymusić, aby pola wysyłane jako string były rzutowane do long-ów wtedy gdy to możliwe, przy tworzeniu indeksu wysyłamy następujący obiekt.

POST http://localhost:9200/books5 HTTP/1.1
User-Agent: Fiddler
Host: localhost:9200
Content-Type: application/json
Content-Length: 83

{
  "mappings": {
    "book" : {
      "numeric_detection" : true
    }
  }
}

niedziela, 22 września 2013

[FullTextSearch] ElasticSearch: CRUD

Używając ElasticSearch jako zwykłej dokumentowej bazy danych możemy w prosty sposób wykonywać CRUD-owe operacje. Jak już wspomniano w poprzednim poście, wszystkie operacje wykonuje się przy użyciu RESTa i odpowiednich verb-ów HTTP.

Dodawanie

PUT http://localhost:9200/books/book/1 HTTP/1.1
User-Agent: Fiddler
Content-Type: application/json
Host: localhost:9200
Content-Length: 175

{
	"title": "Parallel Worlds: A Journey Through Creation, Higher Dimensions, and the Future of the Cosmos",
	"author": "Michio Kaku",
	"year": "2006",
	"price": "12.06"
}

Dodaliśmy zasób do kolekcji books. Zasób jest typu book. Na końcu dodajemy unikalny identyfikator. Jeżeli chcemy, by serwer ElasticSearch wygenerował za nas Id wysyłamy żądanie typu POST pod adres /books/book/.

Pobieranie

Aby pobrać zasób po identyfikatorze wysyłamy żądanie typu GET.
 
GET http://localhost:9200/books/book/1 HTTP/1.1
User-Agent: Fiddler
Content-Type: application/json
Host: localhost:9200

W odpowiedzi otrzymujemy JSON w następującej postaci:

HTTP/1.1 200 OK
Content-Type: application/json; charset=UTF-8
Content-Length: 259

{"_index":"books","_type":"book","_id":"1","_version":1,"exists":true, "_source" : {
	"title": "Parallel Worlds: A Journey Through Creation, Higher Dimensions, and the Future of the Cosmos",
	"author": "Michio Kaku",
	"year": "2006",
	"price": "12.06"
}}

Modyfikacja

Do modyfikacji wykorzystujemy POST, gdzie w body podajemy "ścieżkę" do property, które chcemy zmodyfikować.

POST http://localhost:9200/books/book/1/_update HTTP/1.1
User-Agent: Fiddler
Content-Type: application/json
Host: localhost:9200
Content-Length: 57

{
  "script": "ctx._source.author = \"Kaku, Michio\""
}

Usuwanie

Do usunięcia wykorzystuje się czasownik DELETE.
 
DELETE http://localhost:9200/books/book/1/ HTTP/1.1
User-Agent: Fiddler
Content-Type: application/json
Host: localhost:9200

Ponowne wysłanie GET'a pod powyższy adres zwraca kod HTTP 404.