poniedziałek, 21 kwietnia 2014

[C#|Visual Studio] C#: covariance, contravariance

Kiedy metoda korzystające z generycznych parametrów jest kompilowana przez JIT compiler, to CLR bierze jej IL i podmienia argumenty, a następnie tworzy kod specyficzny dla metody operującej na konkretnym typie. W związku z tym CLR wygeneruje osobny kod dla każdej kombinacji metod / parametrów, co nazywane jest code explosion. W związku z tym, konwersje pomiędzy typami o generycznych parametrach wydawać by się mogły technicznie trudne do realizacji. Wraz z C# 4.0 możemy konwertować w dwie strony za pomocą mechanizmów kowariancji i kontrawariancji.

Kowariancja
Typ generyczny z parametrem a może być rzutowany do innego typu z parametrem b jeżeli parametr b jest typem bazowym dla typu a. Słowo kluczowe C# dla kowariancji to out.

internal interface ICovariantable<out T> where T: new ()
{
    T GetValue();
}

public class MyCovariant<T> : ICovariantable<T> where T: new()
{
    public T GetValue()
    {
        return new T();
    }
}

public class SampleClass: object 
{
    public SampleClass()
    {
        
    }
}

Kontrawariancja
Typ generyczny z parametrem a może być rzutowany do innego typu z parametrem b jeżeli parametr a jest typem bazowym dla typu b. Słowo kluczowe C# dla kowariancji to in.

internal interface IContravariantable<in T>
{
    void SetValue(T param);
}

public class MyContravariant<T> : IContravariantable<T>
{
    public void SetValue(T param)
    {
        Console.WriteLine("value was set");
    }
}


static void Main(string[] args)
{
    //Covariance

    var instance1 = new MyCovariant<object>();
    var instance2 = new MyCovariant<SampleClass>();

    ICovariantable<object>[] instances = {instance1, instance2};

    //Contravariance

    var instance3 = new MyContravariant<object>();
    var instance4 = new MyContravariant<SampleClass>();

    IContravariantable<SampleClass>[] instances2 = { instance3, instance4 };

    Console.ReadKey();
}

Jeżeli nie wyspecyfikujemy parametru out dla pierwszego interfejsu, to kompilator zaprotestuje przeciwko umieszczeniu w tablicy drugiego elementu (instance2), natomiast jeżeli nie podamy in, to niedozwolone będzie umieszczenie w drugiej tablicy obiektu instance3.

Często wykorzystywane delegaty Func i Action wykorzystują kowariancję i kontrawariację: parametry wejściowe są typu in, natomiast wartości zwracane (w przypadku Func) są oznaczone jako out.

poniedziałek, 10 marca 2014

[NoSQL] Redis: klient C#

Twórcy Redis-a nie dostarczają oficjalnych sterowników do C#, ale protokół komunikacji z bazą jest na tyle prosty, że mamy do wyboru kilka driverów Redisa dla C# jak i dla wielu innych języków. W tym poście przedstawione zostaną możliwości klienta będącego częścią frameworka ServiceStack. Driver instalujemy tradycyjnie NuGetem.



Najważniejszą klasą jest RedisClient implementująca kilka ważnych interfejsów. Poza IDisposable są to:
  • IRedisNativeClient - odpowiadający natywnemu API Redis-a
  • ICacheClient - wprowadza warstwę abstrakcji dla cache'y, wykorzystywany wewnątrz ServiceStacka
  • IRedisClient  - wyższy poziom abstrakcji
  • IRedisTypedClient - operacje na typach, dodatkowe funkcjonalności
Przykłady:

using (IRedisNativeClient client = new RedisClient())
{
    client.Set("urn:messages:1", Encoding.UTF8.GetBytes("Hello C# World"));
}

using (IRedisNativeClient client = new RedisClient())
{
    var result = Encoding.UTF8.GetString(client.Get("urn:messages:1"));
    Console.WriteLine(result);
}

using (IRedisClient client = new RedisClient())
{
    var customerNames = client.Lists["urn:customernames"];
    customerNames.Clear();
    customerNames.Add("Joe");
    customerNames.Add("Mary");
    customerNames.Add("Bob");
}

using (IRedisClient client = new RedisClient())
{
    var customerNames = client.Lists["urn:customernames"];
    foreach (var name in customerNames)
        Console.WriteLine(name);
}

Typowany klient daje możliwość tworzenia sekwencji zapewniających unikatowość identyfikatorów. Sekwencje takie tworzone są "per typ".

long lastId = 0;
using(IRedisClient client = new RedisClient())
{
    var customerClient = client.GetTypedClient<Customer>();
    var customer = new Customer()
                       {
                           Id = customerClient.GetNextSequence(),
                           Address = "123 Main Street",
                           Name = "Bob Green",
                           Orders =
                               new List<Order>()
                                   {
                                       new Order() {OrderNumber = "AB123"},
                                       new Order() {OrderNumber = "AB124"}
                                   }
                       };
    var storedCustomer = customerClient.Store(customer);
    lastId = storedCustomer.Id;
}

using(IRedisClient client = new RedisClient())
{
    var customerClient = client.GetTypedClient<Customer>();
    var customer = customerClient.GetById(lastId);
    Console.WriteLine("Got customer {0} with name {1}", customer.Id, customer.Name);
}

Klient C# udostępnia też możliwość definiowania transakcji w poniższy sposób:


using(IRedisClient client = new RedisClient())
{
  var transaction = client.CreateTransaction();
  transaction.QueueCommand(c => c.Set("abc",1));
  transaction.QueueCommand(c => c.Increment("abc",1));
  transaction.Commit();
  var result = client.Get<int>("abc");
  Console.WriteLine(result);
}

poniedziałek, 3 marca 2014

[NoSQL] Redis: Typy danych, funkcjonalności

Redis wspiera 5 typów danych:

1. STRING


Podstawowy typ danych, który może reprezentować całe obiekty zserializowane do stringów,  może też być liczbą.

Podstawowe operacje na stringach:

redis 127.0.0.1:6379> set user:1 "{'name': 'Joe', 'email': 'joe@joe.com'}"
OK
redis 127.0.0.1:6379> get user:1
"{'name': 'Joe', 'email': 'joe@joe.com'}"
redis 127.0.0.1:6379> set user:id 1
OK
redis 127.0.0.1:6379> incr user:id
(integer) 2
redis 127.0.0.1:6379> append user:1 " extra data"
(integer) 50
redis 127.0.0.1:6379> get user:1
"{'name': 'Joe', 'email': 'joe@joe.com'} extra data"
redis 127.0.0.1:6379> getrange user:1 5 9
"e': '"
redis 127.0.0.1:6379> mset order:1 "o1 data" order:2 "o2 data"
OK
redis 127.0.0.1:6379> mget order:1 order:2
1) "o1 data"
2) "o2 data"
redis 127.0.0.1:6379> strlen user:1
(integer) 50

Operacje set i get służą do wstawiania / pobierania wartości pod podany klucz. Podobnie można wykorzystać operacje mset i mget, do których można podać wiele par klucz - wartość i wstawić je do bazy w obrębie jednej operacji. Wartości, które Redis rozpozna jako numeryczne, można inkrementować operacją incr. Kolejne operacje dotyczące typowo ciągów znaków to: append - doklejanie stringa na koniec wartości znajdującej się pod podanym kluczem, getrange - pobranie podzbioru znaków spod podanego klucza oraz strlen, czyli operacja zwracająca długość stringa znajdującego się pod podaną wartością. 

2. LISTA


Zbiór danych posortowanych po kolejności, w jakiej zostały dodane. Podstawowe operacje na listach:


redis 127.0.0.1:6379> lpush mylist "a"
(integer) 1
redis 127.0.0.1:6379> lpush mylist "b"
(integer) 2
redis 127.0.0.1:6379> lpush mylist "c"
(integer) 3
redis 127.0.0.1:6379> lrange mylist 0 2
1) "c"
2) "b"
3) "a"
redis 127.0.0.1:6379> ltrim mylist 0 1
OK
redis 127.0.0.1:6379> lrange mylist 0 2
1) "c"
2) "b"
redis 127.0.0.1:6379> lindex mylist 1
"b"
redis 127.0.0.1:6379> lindex mylist 0
"c"
redis 127.0.0.1:6379> lpop mylist
"c"
redis 127.0.0.1:6379> lrange mylist 0 2
1) "b"
redis 127.0.0.1:6379> rpush mylist "e"
(integer) 2
redis 127.0.0.1:6379> lrange mylist 0 2
1) "b"
2) "e"

Dane dodajemy operatorem lpush. Pobieranie odbywa się po zakresie za pomocą lrange gdzie podajemy zakres indeksów. Element spod konkretnego indeksu pobieramy przez lindex. Lista może działać jako kolejka, gdzie dodajemy dane z prawej strony (rpush) i pobieramy z lewej (lpop).

3. SET


Kolekcja unikalnych stringów. Mamy do dyspozycji operacje z teorii zbiorów. Podstawowe operacje na setach:

redis 127.0.0.1:6379> sadd names "joe" "bob" "mary"
(integer) 3
redis 127.0.0.1:6379> scard names
(integer) 3
redis 127.0.0.1:6379> smembers names
1) "bob"
2) "mary"
3) "joe"
redis 127.0.0.1:6379> sadd names2 "joe" "tim" "mary"
(integer) 3
redis 127.0.0.1:6379> sdiff names names2
1) "bob"
redis 127.0.0.1:6379> sinter names names2
1) "mary"
2) "joe"

Elementy do zbioru dodajemy za pomocą operatora sadd. Liczność zbioru możemy podejrzeć za pomocą scard, a jego elementy pobieramy przez smembers. Przykładowe operacje na zbiorach to różnica (sdiff) i przecięcie (sinter).

4. HASH


Typ danych mapujący stringowe pola na stringowe wartości. Redis jest zoptymalizowany pod kątem hashy z niewielką liczbą wartości.

redis 127.0.0.1:6379> hset user:1:h name "joe"
(integer) 1
redis 127.0.0.1:6379> hset user:1:h email "joe@joe.com"
(integer) 1
redis 127.0.0.1:6379> hgetall user:1:h
1) "name"
2) "joe"
3) "email"
4) "joe@joe.com"
redis 127.0.0.1:6379> hmget user:1:h name email
1) "joe"
2) "joe@joe.com"
redis 127.0.0.1:6379> hkeys user:1:h
1) "name"
2) "email"
redis 127.0.0.1:6379> hvals user:1:h
1) "joe"
2) "joe@joe.com"

Dane do hasha o nazwie user:1:h  pod pole name wstawiamy poleceniem hset. Wartości możemy pobrać na kilka sposobów. Jawnie podając pola (hmget), pobierając wszystkie (hvals). Same nazwy pól pobieramy poleceniem hkeys, a klucze i wartości komendą hgetall.

5. SORTED SET


Zbiory unikalnych danych posortowane po rankingu.

redis 127.0.0.1:6379> zadd hs 120 "joe" 100 "bob" 100 "mary" 90 "tim"
(integer) 4
redis 127.0.0.1:6379> zrank hs "bob"
(integer) 1

Dodajemy dane jako pary ranking - klucz poleceniem zadd, natomiast ranking konkretnego klucza (indeks) pobieramy za pomocą zrank.

Pozostałe funkcjonalności:


Redis może pracować jako message bus poprzez proste subskrybowanie / publikowanie wiadomości.

subscribe greetings //klient 1
publish greetings "hello redis" //klient 2
psubscribe greet* // subskrybuje na wszystkie kanały spełniające pattern
unsubscribe / punsubscribe

Ciekawą funkcjonalnością niekoniecznie spotykaną w bazach NoSQL jest zaimplementowana transakcyjność. Transakcję otwieramy poleceniem multi, po którym podajemy operacje mające się wykonać w obrębie transakcji. Kończymy poleceniem exec.
Z poziomu aplikacji klienckiej możemy także włączyć tryb monitorowania operacji, jakie wykonywane są na bazie poleceniem monitor. 

Redis przechowuje dane w pamięci z opcją "persisted to disk". Klient komunikuje się z serwerem po TCP wykorzystując specjalny protokół Redis-a. Baza domyślnie robi snapshoty danych na dysk co określoną liczbę zmian wartości. Plik redis.conf przechowuje konfigurację w sposób tekstowy. Można tam ustawić, co ile sekund lub ile zmian kluczy ma się robić snapshot. Przykładowo:

save 300 10 (co najmniej 300 sekund i 10 zmian kluczy)

Replikacja: działa na zasadzie master / slave(s) - slave'y pozostają domyślnie w trybie read only.

Prosta autentykacja polega na tym, że klient podaje hasło, które jest ustawione w pliku redis.conf na serwerze.

[NoSQL] Redis: Wprowadzenie

Redis to wg. ostatnich statystyk najpopularniejsza baza NoSQL typu key - value. Główną jest zaletą jest bardzo duża szybkość, dzięki czemu korzystają z niej takie marki jak stackoverflow czy instagram. Dane przechowywane są w pamięci w postaci przypominającej słownik, z możliwością zapisu danych na dysk.

Podstawowe cechy:
  • ekstremalnie szybki
  • przechowujemy nie tylko stringi
  • możliwość trwałego zapisu danych na dysk
  • replikacja
  • wbudowane wsparcie dla języka LUA
Podstawowe wykorzystanie Redis-a polega na zapisaniu danych pod podanym kluczem i odczytaniu ich spod tego klucza. 


Instalacja:
Binaria dla windowsów możemy pobrać pod adresem: https://github.com/dmajkic/redis/downloads

Pobieramy paczkę, w której znajdują się pliki .exe. Serwer uruchamiamy jako plik redis-server.exe. Domyślnie startuje on na porcie 6379. Dodatkowo ważna jest aplikacja kliencka, z której możemy wykonywać zapytania do serwera, przykładowo:

redis 127.0.0.1:6379> set key "Hello world"
OK
redis 127.0.0.1:6379> get key
"Hello world"
redis 127.0.0.1:6379> del key
(integer) 1
redis 127.0.0.1:6379> get key
(nil)

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();
}