poniedziałek, 19 maja 2014

ASP.NET Web API - basic authentication

Basic authentication (ASP.NET Web API)

Tworzenie serwisu REST z wykorzystaniem ASP.NET Web API jest stosunkowo proste. Gdy nasz serwis jest już wystawiony na świat, prawdopodobnym stanie się fakt odpowiedniego jego zabezpieczenia. Jednym z najprostszych i najbardziej standardowym sposobem jest zabezpieczenie takiego serwisu z wykorzystaniem basic authentication
Basic authentication jest najprostszym i najłatwiejszym mechanizmem autentykacji poprzez zastosowanie pary "username/password" w "plaintext".
Basic authentication działa w następujący sposób:

  1. Jeżeli żądanie "request" wymaga autentykacji, serwer zwraca status code "401" (unauthorized). Odpowiedź taka zawiere "WWW-uthenticate header", które mówi, że serwer wspiera "basic authentication".
  2. Klient wysyła kolejne żądanie ("request") ale tym razem już z odpowiednimi "credentials" umieszczonymi w "WWW-Authenticate header". Charakteryzują je następujące właściwości: 
    1. sformatowane jako string: "name:password"
    2. base64-encoded
    3. brak szyfrowania

Basic authentication wykonywany jest w kontekście pewnej domeny ("realm"). Serwer dodaje nazwę tej domeny do nagłówka WWW-Authenticate header. 
WAŻNE:
  • Ponieważ "credentials", które przesyłane są od klienta na serwer nie są szyfrowane, stosowanie basic authentication jest sensowne i bezpieczne jedynie jeśli komunikacja jest bezpieczna i przebiega przez szyfrowaną wersję protokołu HTTP czyli HTTPS. 
  • Basic authentication nie jest odporne na ataki CSRF (cross-site request forgery)

Niniejszy post przedstawia jak zaimplementować taką autentykację w naszym ASP.NET Web API (post nie dotyczy ASP.NET Web API 2, które jest prostsze i zawiera już zaimplementowane techniki autentykacji serwisu bez konieczności pisania dodatkowego kodu).

AuthorizeAttribute 

Implementacja basic authentication będzie polegała na stworzeniu własnego atrybutu do autoryzacji. W pierwszej kolejności jednak musimy napisać metodę pomocniczą, która wyłuska nam credentials (nazwę użytkownika i hasło) z wiadomości, która przychodzi do serwera. Metoda taka może wyglądać następująco:

private Credentials ParseAuthorizationHeader(string authHeader)
{
   string[] credentials = Encoding.ASCII.GetString(Convert.FromBase64String(authHeader)).Split(new[] { ':' });
   if (credentials.Length != 2 || string.IsNullOrEmpty(credentials[0]) || string.IsNullOrEmpty(credentials[1]))
      return null;
   return new Credentials() { Username = credentials[0], Password = credentials[1], };
}
Teraz wystarczy napisać naszą klasę, która będzie dziedziczyła po "AuthorizeAttribute":
public class CustomAuthorizeAttribute : AuthorizeAttribute
{
   private const string BasicAuthResponseHeader = "WWW-Authenticate";
   private const string BasicAuthResponseHeaderValue = "Basic";

   protected IPrincipal CurrentUser
   {
      get { return Thread.CurrentPrincipal as IPrincipal; }
      set { Thread.CurrentPrincipal = value as IPrincipal; }
   }

   public override void OnAuthorization(System.Web.Http.Controllers.HttpActionContext actionContext)
   {
      try
      {
         AuthenticationHeaderValue authValue = actionContext.Request.Headers.Authorization;
         Credentials parsedCredentials = ParseAuthorizationHeader(authValue.Parameter);
         if (parsedCredentials != null)
         {
            this.CurrentUser = new DummyPrincipalProvider().CreatePrincipal(parsedCredentials.Username, parsedCredentials.Password);
            if (!CurrentUser.Identity.IsAuthenticated)
            {
               actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Forbidden);
               actionContext.Response.Headers.Add(BasicAuthResponseHeader, BasicAuthResponseHeaderValue);
               return;
            }
         }
      }
      catch (Exception ex)
      {
         actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized);
         actionContext.Response.Headers.Add(BasicAuthResponseHeader, BasicAuthResponseHeaderValue);
         return;
      }
  }
}
W powyższej implementacji korzystam z klasy "DummyPrincipalProvider", która jest dziedziczy z IPrincipalProvider i jest moją "fake'ową" implemetacją dostarczyciela tożsamości. Wygląda następująco:
public class DummyPrincipalProvider : IProvidePrincipal
{
   private const string Username = "username";
   private const string Password = "password";

   public IPrincipal CreatePrincipal(string username, string password)
   {
      if (username != Username || password != Password)
      {
         return null;
      }
      var identity = new GenericIdentity(Username);
      IPrincipal principal = new GenericPrincipal(identity, new[] { "User" });
      return principal;
    }
 }
W tym miejscu jednak powinna być implemetacja odpowiedniego dortarczyciela tożsamości, np. jeśli dane użytkowników przechowywane są bazie to wtedy najlepszym rozwiązaniem będzie użycie gołego ADO.NET lub z wykorzystaniem EntityFramework do pobrania takich danych z bazy danych. Mając tak napisany atrybut, wystarczy teraz, że klasę lub metodę, którą chcemy zabezpieczyć udekorujemy tym atrybutem:
[CustomAuthorizeAttribute]
[HttpGet, HttpHead]
public Product Get(int id)
{
   var product = productRepository.Get(id);
   if (product == null)
      throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.NotFound));
   return product;
}

sobota, 17 maja 2014

Javascript basics - Part 1

Wstęp

Na pewno każdy developer miał styczność z jQuery w mniejszym lub większym stopniu. Tak jak ja, zrobiłem kilka stron www, dla żony, dla znajomych, w których wykorzystywałem jQuery. Jednak tak na prawdę nigdy nie zagłębiałem się w javascript. jQuery jest wspaniałą biblioteką, jednak przyszedł czas aby poznać podstawy javascript. W związku z tym, wziąłem się za zgłębianie tajników tego języka czytając książkę Javascript Patterns. Książkę polecam każdemu, jest ona wspaniałym źródłem wiedzy dla kogoś takiego jak ja, który wcześniej właściwie nie miał styczności z czystym javascript (oczywiście pewne podstawy znałem, których musiałem się nauczyć aby korzystać z jQuery). Seria tych postów zawiera fundamentalne podstawy javascript, które wg. mnie każdy szanujący się developer powinien znać. 

Deklarowanie zmiennych

W javascript, jak można przypuszczać mamy zmienne globalne i lokalne. Jednak trzeba wiedzieć jak poprawnie je deklarować, ponieważ może okazać się, że zmienna, którą chcieliśmy zadeklarować lokalnie okaże się być zmienną globalną (tak tak, też wydawało mi się to nieprawdopodobne).

DEKLAROWANIE ZMIENNYCH LOKALNYCH
Zmienna jest zmienną lokalną gdy jest poprawnie zadeklarowana w ciele funkcji:

function foo(){
    var localVariable = "this is a localVariable";
}

zmienna localVariable w powyższym przykładzie jest zadeklarowana jako zmienna lokalna. Aby poprawnie tworzyć zmienne lokalne musimy zapamiętać następujące 2 zasady:
  • musi być zadeklarowana w ciele funkcji
  • musi być zadeklarowana ze słowem kluczowym var
Są to dwie podstawowe zasady, które musimy zapamiętać. Nic więcej! Sprawa przedstawia się trochę inaczej jeśli chodzi o zmienne globalne!


DEKLAROWANIE ZMIENNYCH GLOBALNYCH
Podstawowa zasada jest prosta, wszystko co jest deklarowane poza funkcją jest zmienna globalną! Jednakże, jest jeszcze inna zasada. Spójrzmy na poniższy przykład:

var globalVariable = "This is global variable";
secondGlobalVariable = "This is second global variable";
function foo(){
   anotherGlobalVariable = "This is another global variable";
}
Wszystko co jest zadeklarowane bez słowa kluczowego var jest zmienną globalną, nawet jeśli zadeklarowane w ciele funkcji! W związku z tym, jakie są przesłanki przy tworzeniu zmiennych globalnych?

  • każda zmienna zadeklarowana ze słowem kluczowym var poza funkcją jest zmienną globalną
  • każda zmienna zadeklarowana bez słowa kluczowego var jest również zmienna globalną (nawet jeśli jest zadeklarowana w ciele funkcji)
WAŻNE
Przy deklarowaniu zmiennych lokalnych należy pamiętać, że każda zmienna lokalna musi  być zadeklarowana ze słowem kluczowych var. Dlaczego jest to takie ważne? Rozważmy następujący przykład:

function foo(){
   var variable1 = variable2 = "Some text";
}
Zmienna variable1 jest zmienna lokalną, jednak zmienna variable2 jest już zmienną globalną! Jeśli chcemy aby obie były zmiennymi lokalnymi należy je zadeklarować następująco:

function foo(){
   var variable1, variable2;
   variable1 = variable2 = "Some text";
}
Tak zadeklarowane będą obie zmiennymi lokalnymi!

poniedziałek, 12 maja 2014

Bezpieczeństwo WCF - Message i Transport Level Security

Wprowadzenie

Bezpieczeństwo jest istotną częścią każdego kazdego oprogramowania a staje się wręcz niezbędną częścią jeśli chodzi o serwisu webowe - które wystawiane na świat i stają się widocznymi celami różnego rodzaju ataków. 

WCF posiada dwa podstawowe tryby obsługi bezpieczeństwa:
  • bezpieczeństwo na poziomie transportu (Transport Level Security)
  • bezpieczeństwo na poziomie wiadomości  (Message Level Security)
(istnieje jeszcze jeden tryb który łączy dwa powyższe - TransportWithMessageCredential)


Bezpieczeństwo wiadomości można zdefiniować za pomocą 3 aspektów, potocznie nazywanych CIA:
  • Confidentiality - poufność - oznacza, ze osoba lub podmiot, który powinien widzieć wiadomość w rzeczywistości ta osoba/podmiotem jest
  • Integrity - integralność - oznacza, ze wiadomość nie może być w żaden sposób zmieniona lub "zainfekowana" bez wykrycia tego faktu
  • Authentication - uwierzytelnianie - potwierdza tożsamość osoby/podmiotu przeglądającego wiadomość
Brzmi dość tragicznie i zniechęcająco? Można to sobie porównać np. do korzystania z konta bankowego. Tylko osoba do której konto należy może przeglądać dane swojego konta i wykonywać w nim odpowiednie operacje (no chyba ze ktoś inny jeszcze zostanie do tego upoważniony). Wpłacając lub wypłacając pieniądze chcemy mieć pewność ze kwota wpłacona/wypłacona będzie miała swoje odzwierciedlenie na naszym koncie (no może nie zawsze zwłaszcza jeśli chodzi o wypłaty ;)). Dodatkowo oczekujemy od banku iż przed dostępem do naszego konta zostaniemy odpowiednio uwierzytelnieni. Logując się online musimy podać np. nr klienta oraz hasło, korzystając z infolinii tez musimy posiadać odpowiednie dane dostępowe. Jak widać wszystkie niuanse prawdziwego życia w tym kontekście mogą być odzwierciedlone w WCF.

Message Level Security

Message Level Security jest pierwszą podstawową kategorią w całej strukturze bezpieczeństwa Windows Communication Foundation. Jest fizyczną implementacją WS-Security Specification, a jej głównym zadaniem jest rozszerzanie wiadomości SOAP (Simple Object Access Protocol) w celu zapewnienia poufności, integralności i uwierzytelniania. Oznacza to z wszystkie detale związane z bezpieczeństwem wiadomości przechowywane są w niej samej. Ma to swoje wady i zalety:

Zalety
  • End-to-end security. Jak wcześniej zostało napisane, "message level security" rozszerza wiadomość SOAP dodając do niej odpowiednie informacje zabezpieczające. Można powiedzieć, ze w przeciwieństwie do "transport level security" (np. SSL które zabezpiecza wiadomość w przypadku komunikacji punkt-punkt, tzn. ze jeśli istnieją jakieś pośrednie urządzenia to ta informacja jest za każdym razem re-transmitowana), informacje zabezpieczające są z wiadomością przez cały cykl jej życia. 
  • Increased flexibility - zwiększona elastyczność. Niekoniecznie cała wiadomość lecz część wiadomości może być podpisana lub zaszyfrowana. Dzięki temu pewna część wiadomości może być widoczna dla serwisów pośredniczących a część zaszyfrowana/podpisana co zapewnia jej integralność. Domyślnie, w przypadku stosowanie "message level security", WCF nie szyfruje wiadomości lecz podpisuje ja.
  • Support for multiple transports - wsparcie dla wielu typów transportu. Oznacza to, że "message level security" jest niezależne od typu transportu. Zabezpieczoną wiadomość możemy przesłać używając HTTP, TCP czy named pipes. 
Wady
Za MSDN:
  • Wydajność - z racji tego iż wiadomość jest rozszerzona, automatycznie staje się większa
  • Nie można używać message stereaming
  • Wymaga implementacji mechanizmów bezpieczeństwa na poziomie XML oraz wsparcia dla WS-Security Sepcification co może zmniejszyć interoperacyjność 
Kiedy uzywac
  • w przypadku gdy wiadomość będzie krążyła pomiędzy różnymi serwisami
  • w przypadku gdy serwis WCF będzie dostępny poprzez Internet i dostęp do niego będzie wymagał innych serwisów pośredniczących w komunikacji
Jest dostepnę we wszystkich typach bindingów z wyjątkiem netNamedPipeBinding oraz MSmqIntegrationBinding.

Transport Message Security

Generalnie, w odróżnieniu od "Message Level Security" tutaj wszystkie dane dotyczące bezpieczeństwa wiadomości przekazywane są za pomocą warstwy transportującej. Oznacza to iż ten typ zabezpieczeń jest zależny od typu wybranego transportu co automatycznie zmniejsza jego możliwości jeśli chodzi o opcje uwierzytelniania. Kazdy z protokolow transportowych (TCP, IPC, MSMQ czy HTTP) implementuje własny mechanizm zabezpieczenia wiadomości. Najpopularniejszym podejściem jest wykorzystanie Secure Socket Layer (SSL) do szyfrowania bądź podpisywania zawartości pakietów wysyłanych przez HTTPS.
Zalety

  • zapewnia interoperacyjność ponieważ nie musi implementować WS-Security Specification
  • większa wydajność

Wady

  • zapewnia bezpieczeństwo punkt-punkt
  • ograniczona liczba opcji uwierzytelniania w stosunku do message level security
  • zależne od protokołu transportującego

Kiedy używać

  • gdy wiadomość będzie wysyłana bezpośrednio między dwoma punktami bez angażowania jakichkolwiek pośredników, np. w przypadku bezpośredniej komunikacji klient-serwer
  • gdy serwer i klient są w lokalnej sieci intranet
Jest dostępny we wszystkich typach bindingów z wyjątkiem wsDualHttpBinding

Show me the code

Jak właściwie wszystko w WCF, bezpieczeństwo na poziomie wiadomości można zdefiniować używajac kodu deklaratywnego lub imperatywnego. Obie implementacji są banale i wyglądają następująco:
Kod deklaratywny:


   
       
   

Kod imperatywny (z wykorzystaniem domyslnego (bezparametrowego kontruktora)):
WSHttpBinding wsHttpSecurity = new WSHttpBinding();
wsHttpSecurity.Security.Mode = SecurityMode.Message;
Kod imperatywny (okreslanie security level w konstruktorze):
WSHttpBinding wsHttpSecurity = new WSHttpBinding(SecurityMode.Message);

wtorek, 6 maja 2014

Kontrola wiadomości WCF - IDispatchMessageInspector

Jeśli korzystaliście kiedyś z WCF (lub pisaliście jakikolwiek inny serwis z wykorzystaniem SOAP), możliwe że mieliście zaimplementowany mechanizm kontroli wiadomości - np. mechanizm logowania wiadomości, które przychodzą do serwera. W tym poście chciałbym opisać jak zaimplementować taki mechanizm kontroli wiadomości przed jej przetworzeniem przez serwis WCF lub przed wysłaniem odpowiedzi do klienta. Dlaczego właściwie chcielibyśmy robić taką kontrolę? Tak wcześniej wspomniałem np. aby logować takie wiadomości na przykład w celach statystycznych lub w przypadku gdy chcemy coś szczególnego dodać do naszej wiadomości - odpowiedzi  na przykład w nagłówku przed wysłaniem jej do aplikacji klienckiej.
W WCF'ie możemy taką funkcjonalność zaimplementować w "dość prosty" sposób.  (Prosty dla kogoś, kto już miał wcześniej styczność z WCF'em).

Na początku musimy stworzyć klasę, w której będziemy robić inspekcję wiadomości WCF - to własnie tak klasa będzie implementowała interfejs IDispatchMessageInspector. Interfejs ten posiada dwie metody:

  • AfterReceiveRequest - wykonywana w momencie gdy wiadomość została odebrana przez serwis lecz przed przekazaniem jej do odpowiedniej metody serwisu
  • BeforeSendReply - wywoływana po przetworzeniu naszej wiadomości przez odpowiednią metodę i przed wysłaniem odpowiedzi do aplikacji klienckiej

Dodatkowo, z racji tego iż nasza klasa będzie określała pewne zachowanie dla serwisu lub endpointa musi również implementować odpowiednio:
IServiceBehavior lub IEndpointBehavior lub oba interfejsy jeśli zdecydujemy się, że może być używana zamiennie dla serwisu i endpointa.
Kod naszej klasy wygląda następująco:

public class ServerInspector : IDispatchMessageInspector, IEndpointBehavior, IServiceBehavior
    {
        #region IDispatchMessageInspector Members

        public object AfterReceiveRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel, System.ServiceModel.InstanceContext instanceContext)
        {
            Debug.WriteLine("Called after an inbound message has been received but before the message is forwared to the target operation");
            return request;
        }

        public void BeforeSendReply(ref System.ServiceModel.Channels.Message reply, object correlationState)
        {
            Debug.WriteLine("Called after the operation has returned but before the reply is actually relayed");
        }

        #endregion

        #region IEndpointBehavior Members

        public void AddBindingParameters(ServiceEndpoint endpoint, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
        {
        }

        public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
        {
        }

        public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
        {
            endpointDispatcher.DispatchRuntime.MessageInspectors.Add(this);
        }

        public void Validate(ServiceEndpoint endpoint)
        {
        }

        #endregion

        #region IServiceBehavior Members

        public void AddBindingParameters(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase, System.Collections.ObjectModel.Collection endpoints, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
        {
        }

        public void ApplyDispatchBehavior(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase)
        {
            foreach (ChannelDispatcher dispatcher in serviceHostBase.ChannelDispatchers)
            {
                foreach (var endpoint in dispatcher.Endpoints)
                {
                    endpoint.DispatchRuntime.MessageInspectors.Add(this);
                }
            }
        }

        public void Validate(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase)
        {
        }

        #endregion
    }

Należy zwrócić uwagę na metody ApplyDispatchBehavior w których dodajemy do naszego endpointa aktualnego inspektora. Bez dodania inspektora, nasz kod nie będzie działał. Jeśli chodzi o samą kontrolę wiadomości to może być ona wykonywana w 2 metodach o których wcześniej wspomniałem : AfterReceiveRequest oraz BeforeSendReply. Ja akurat w tych metodach dodałem zwykłe wyświetlanie wiadomości, jednak należy pamiętać, że własnie te metody przeznaczone są do przeprowadzania właściwej kontroli wiadomości WCF. 

Kolejnym krokiem jest dodanie "klasy rozszerzającej" która będzie dziedziczyła z BehaviorExtensionElement. Musimy mieć taką klasę ponieważ jak zobaczymy z chwilę, w pliku konfiguracyjnym ustawimy sobie takie własnie rozszerzenie zachowania naszego serwisu. Najważniejszą sprawą jaką trzeba zapamiętać, że taka klasa musi dziedziczyć z klasy BehaviorExtensionElement, w przeciwnym wypadku kompilator nam zaprotestuje i wywali błąd. W związku z tym nasza klasa rozszerzająca wygląda następująco:
public class ServerInspectorExtension : BehaviorExtensionElement
{
   public override Type BehaviorType
   {
      get { return typeof(ServerInspector); }
   }

   protected override object CreateBehavior()
   {
      return new ServerInspector();
   }
}
Jak widzimy klasa ta tworzy nową instancję naszego inspektora, którego dodaliśmy wcześniej. Ostatnim krokiem jest skonfigurowanie "behaviorExtension" w pliku konfiguracyjnym. Nasze rozszerzenie wygląda następująco:

   
      
   

Takie rozszerzenie przypisujemy do odpowiedniego "behavior'a":

   
      
   

środa, 16 kwietnia 2014

Stronicowanie (pagination) w SQL Server - ROW_NUMBER() vs RANK() vs DENSE_RANK()

Zastanawialiście się kiedyś jak robić stronicowanie w SQL Server? Ja ostatnio musiałem coś takiego zrobić, ale nie używając żadnego ORM'a lecz jedynie gołe ADO.NET. Sprawa wydawała się być banalna, napisałem zapytanie ale okazało się, że wyniki są jakieś dziwne i jeszcze się powielają mimo tego iż miałem DISTINCTA na zapytaniu. Poszperałem trochę w necie i doczytałem, że powinienem zamiast funkcji ROW_NUMBER() używać DENSE_RANK(). Okazuje się, że ROW_NUMBER() nadaje każdemu wierszowi kolejny, unikalny number. DENSE_RANK() nadaje unikalny numer ale jedynie wierszom, które się nie duplikują.

Na tej stronie możecie znaleźć fajnie to wszystko wyjaśnione. Również różnicę między DENSE_RANK() a RANK(). 
W skrócie:

  • ROW_NUMBER - nadaje każdemu wierszowi unikalny numer, nie zważając na duplikaty
  • RANK() - nadaje unikalnym wierszom rangę (unikalny numer) z przerwami w rankingu
  • DENSE_RANK() - nadaje unikalnym wierszom rangę (unikalny number) bez przerw w rankingu.  

Z tej powyższej strony wzięta jest również poniższa tabelka, która na szybko przedstawia różnicę między ROW_NUMBER(), RANK() oraz DENSE_RANK()
+---+------------+------+------------+
| V | ROW_NUMBER | RANK | DENSE_RANK |
+---+------------+------+------------+
| a |          1 |    1 |          1 |
| a |          2 |    1 |          1 |
| a |          3 |    1 |          1 |
| b |          4 |    4 |          2 |
| c |          5 |    5 |          3 |
| c |          6 |    5 |          3 |
| d |          7 |    7 |          4 |
| e |          8 |    8 |          5 |
+---+------------+------+------------+

wtorek, 15 kwietnia 2014

Asynchroniczne pobieranie danych i uaktualnianie CollectionView w WPF

W WPF klasa CollectionView jest niezwykle przydatna jeśli chcemy w prosty sposób zaimplementować mechanizm sortowania, grupowania lub filtrowania. Używając jej jednak w programowaniu asynchronicznym podczas uaktualniania tej kolekcji z innego wątku, dostaniemy następujący komunikat:

The calling thread cannot access this object because a different thread owns it

Nie dziwota, jest to normalnie zachowanie, które często się zdarza, ponieważ CollectionView nie obsługuje zmian własnej kolekcji z wątku innego niż Dispatcher. Co jednak zrobić aby obejść ten problem? Po pierwsze musimy mieć obiekt dispatcher'a, która w WPF'ie jest klasą zapewniającą usługi do zarządzania wątkami, np. synchronizacji. Jeśli korzystamy ze wzorca MVVM (Model-View-ViewModel), pierwsze co musimy zrobić to przekazać obiekt dispatcher'a do ViewModelu, np w konstruktorze:

private Dispatcher currentDispatcher;

public ViewModel(Dispatcher currentDispatcher)
{
    this.currentDispatcher = currentDispatcher;
}
Następnie aby dodawać obiekty do kolekcji CollectionViewSource, należy wykonać następujący kod:
currentDispatcher.Invoke(new Action(()=>
{
    this.collectionViewSource.Add(someObject);
}));

piątek, 11 kwietnia 2014

Database schema modeling - SQL Server Data Tools!

Post ku pamięci. Czy zdarzyło wam się kiedykolwiek, że mieliście 2 bazy danych, w których musieliście porównać ich schemę? Ja ostatnio miałem taką zagwozdkę, robiłem nową funkcjonalność i przepadł mi gdzieś skrypt, który uaktualniałby schemę bazy danych. Wiedziałem, że są pewne narzędzia, które by mi pomogły jednak nie chciałem korzystać z narzędzi firm trzecich. Poszperałem trochę w necie i okazało się, że Microsoft zrobił takie narzędzie. Nazywa się SQL Server Data Tools. Dzięki temu narzędzi możemy porównać sobie schemę dwóch baz, a nawet wygenerować skrypt zmian, który uaktualni nam naszą bazę danych.



SQL Server Data Tools jest dostarczane razem z Visual Studio 2013, jest możliwość dociągniecia instalki również do Visual Studio 2010 i Visual Studio 2012. Konkretną wersję można ściągnąć tutaj
http://blogs.technet.com/b/dataplatforminsider/archive/2013/11/13/microsoft-sql-server-data-tools-update.aspx

Co oferuje nam jeszcze to narzędzie? Możemy oczywiście graficznie modelować sobie naszą bazę danych. Mało tego narzędzie umożliwia nam nawet porównać dane w obu bazach za pomocą opcji "Data comparision" oraz tworzyć unit testy naszej bazy: więcej informacji znajdziemy tutaj