Pokazywanie postów oznaczonych etykietą c#. Pokaż wszystkie posty
Pokazywanie postów oznaczonych etykietą c#. Pokaż wszystkie posty

wtorek, 29 września 2015

[Security] JsonWebTokens

Celem jest stworzenie struktury reprezentującej informacje na temat tożsamości, wydającego itd. Security tokens są chronionymi strukturami danych (podpisane, zawierają czas wygasanie). Składają się z trzech sekcji: nagłówka, payloadu oraz podpisu weryfikującego.

Proces wydawania tokenu może zastępować klasyczny proces autentykacji użytkownika do aplikacji (zamiast cookie otrzymuje on token będący stringiem, który później dołącza do swoich żądan, np. jako nagłówek HTTP).

JWT wykorzystuje różne mechanizmy kryptograficzne. Najpopularniejszym z nich jest HMACSHA256, który "wylicza" skrót z contentu przy użyciu klucza symetrycznego. W innych wariantach można spotkać się z RSA, a nawet z kryptografią krzywych eliptycznych. W przypadku aplikacji webowych serwer wydaje tokeny i ten sam serwer je później weryfikuje, więc tajny klucz znajduje się w jednym miejscu. Aplikacja JS może takie tokeny dołączać np. jako nagłówek HTTP.

Przykładowy JWT wraz z zdekodowaną postacią (kodowanie Base64).




Serwerowo w .NET mamy do dyspozycji NuGet od Microsoftu.



Przykładowa aplikacja w C# weryfikująca integralność na podstawie współdzielonego klucza:

static void Main(string[] args)
{
    var pass = "AC917771A299CF9542513AED8885D12";
    var token = CreateJsonWebToken(pass);

    Console.WriteLine(token);

    var payload = ParseAndValidateJwtToken(token, pass);
    Console.WriteLine(payload.Last().Value);

    token = token.Replace("Z", "6");

    ParseAndValidateJwtToken(token, pass);

    Console.Read();
}

private static string CreateJsonWebToken(string pass)
{
    var tokenHandler = new JwtSecurityTokenHandler();

    var descriptor = new SecurityTokenDescriptor();
    DateTime now = DateTime.Now;

    var key = System.Text.Encoding.Default.GetBytes(pass);
    var securityKey = new InMemorySymmetricSecurityKey(key);

    descriptor.TokenIssuerName = "mySecretApp";
    descriptor.AppliesToAddress = "http://mydomain.com";
    descriptor.Lifetime = new System.IdentityModel.Protocols.WSTrust.Lifetime(now, now.AddHours(1));
    descriptor.Subject = new System.Security.Claims.ClaimsIdentity();
    descriptor.SigningCredentials = new SigningCredentials(securityKey,
        "http://www.w3.org/2001/04/xmldsig-more#hmac-sha256",
        "http://www.w3.org/2001/04/xmlenc#sha256");


    JwtSecurityToken token = (JwtSecurityToken)tokenHandler.CreateToken(descriptor);
    token.Payload.Add("myKey", "myValue");            

    return tokenHandler.WriteToken(token);
}

private static JwtPayload ParseAndValidateJwtToken(string jwtToken, string pass)
{
    var tokenHandler = new JwtSecurityTokenHandler();
    try
    {
        ValidationProcedure(jwtToken, pass);
    }            
    catch(SignatureVerificationFailedException e)
    {
        Console.WriteLine("Tampering attack detected");
    }

    JwtSecurityToken parsedJwt = tokenHandler.ReadToken(jwtToken) as JwtSecurityToken;
    return parsedJwt.Payload;
}

private static void ValidationProcedure(string token, string pass)
{
    var tokenHandler = new JwtSecurityTokenHandler();
    var key = System.Text.Encoding.Default.GetBytes(pass);
    var securityKey = new InMemorySymmetricSecurityKey(key);

    TokenValidationParameters validationParameters = new TokenValidationParameters
    {
        ValidateIssuer = false,
        RequireSignedTokens = true,
        ValidateAudience = false,
        IssuerSigningKey = securityKey
    };

    SecurityToken securityToken;
    ClaimsPrincipal claimsPrincipal = tokenHandler.ValidateToken(token, validationParameters, out securityToken);
}
}

piątek, 3 kwietnia 2015

[C#|Visual Studio] C# 6.0 - co nowego ?

Microsoft regularnie co 2 - 3 lata wypuszcza nam nową wersję C#. Dotychczas wyglądało to tak:
2002: C# 1.0
2005: C# 2.0 (generyki)
2007: C# 3.0 (LINQ)
2010: C# 4.0 (dynamic)
2012: C# 5.0 -  (async)

Mamy 2015 rok i wiemy już mniej więcej, czego możemy się spodziewać po nowej wersji C#. Poniżej wybrane nowe funkcjonalności, które będą dostępne w Visual Studio 2015:

Auto-Property Initalizers


public class User
{
    public Guid Id { get; } = Guid.NewGuid();
}


Już wcześniej pozbawiono nas konieczności definiowania backing fields, teraz dodano możliwość automatycznego inicjalizowania danych.

Dictionary Initializers

Dictionary<string, User> _defaultUsers = new Dictionary<string, User>(){

    {"admin", new User("admin")},
    {"guest", new User("guest")}

}


Nowy, inny sposób:


Dictionary<string, User> _defaultUsers = new Dictionary<string, User>(){

    ["admin"] = new User("admin")},
    ["guest"] = new User("guest")}

}

Pierwszy sposób (stary) to seria wywołań metody Add, drugi to seria odwołań przez indekser.

Kolejna zmiana jest taka, że paramsy (słowo kluczowe params) mogą być jako IEnumerable<T>, nie muszą być tablicą T.

public int Sum(params IEnumerable<int> numbers){
    return numbers.Sum(n => n);
}

Mamy nowe literale: bitowy (0b1100) oraz poprawiający czytelność (1_000_000_000).

Wywoływanie statycznych metod:

W C# 6 można wywoływać statyczne metody bez podawania nazwy klasy, jeżeli typ jest statyczny i załączymy odpowiedni using.


using System.Console;

public static void Main(){
    Write("Writted without type specification");
}


W przypadku konfliktów zawsze wygra metoda statyczna (podobna interpretacja jak w przypadku extension methods).

Conditional access operator:


var name = action.?Method.?Name ?? "no name";


Sprawdzimy, czy wartości są różne od null i jeśli któraś jest nullem, to wartość po lewej stronie zostanie ustawiona na null. Dodatkowo null - coalesce operator ustawi nam domyślną wartość.

Ponadto wewnątrz bloków catch można będzie wołać instrukcje poprzedzone keywordem await.

Exception filters:


catch(Exception ex) if(ex.InnerException == null){

}


Jeśli wyrażenie zwróci false, to wyjątek nie zostanie złapany.

Operator nameof:

zamiast:

throw new ArgumentNullException("name")

będzie można napisać


throw new ArgumentNullException(nameof(name))

sobota, 16 sierpnia 2014

[C#|Visual Studio] Rhino Mocks

Idealny Unit Test to taki, który jest:
  1. atomowy (testujemy tylko jeden fragment funkcjonalności)
  2. deterministyczny
  3. powtarzalny
  4. niezależny od kolejności wywoływania innych testów 
  5. szybki (czas wykonania rzędu milisekund)
  6. łatwy do uruchomienia
 Problemem zawsze będą zależności. Przeważnie metoda poddawana testowi woła metody innych klas, które mogą wykonywać wolne operacje, np. łączenie się z bazą danych. Co więcej testowanie metod w ten sposób narusza zasadę atomowości Unit Testów.

W tym przypadku z pomocą przychodzi zasada Dependency Inversion. Zamiast wstrzykiwać do testowanego serwisu właściwe zależności, możemy na etapie UT wstrzyknąć inną implementację danego interfejsu wykonującą tylko tyle kodu, ile potrzebujemy. Problem polega na tym, że wraz ze wzrostem komplikacji właściwych serwisów musimy także utrzymywać kod mockowanych serwisów.

Tu z pomocą przychodzi Rhino Mocks - framework budujący dynamiczne mocki (wykorzystując obiekty proxy) na potrzeby naszych testów.

 Mockowanie rozpoczynamy od użycia klasy MockRepository, która stworzy nam mock dla dowolnego interfejsu. Mock taki będzie zawierał puste metody (dla metod zwracających void) lub metody zwracające wartość domyślną dla pozostałych metod z interfejsu.

Przykładowo mamy klasę InvoiceService, która woła jedną ze swoich zależności - InvoiceRepository.

public class Invoice
{
    public string Id { get; set; }
    public decimal Amount { get; set; }
    public string UserId { get; set; }
}

public interface IInvoiceRepository
{
    bool Store(Invoice entity);
}

public class InvoiceRepository : IInvoiceRepository
{
    public bool Store(Invoice entity)
    {
        //Long running SQL operation
        return true;
    }
}

public class InvoiceService
{
    private IInvoiceRepository _invoiceRepository;

    public InvoiceService(IInvoiceRepository invoiceRepository)
    {
        _invoiceRepository = invoiceRepository;
    }

    public void SaveInvoice(Invoice item)
    {
        var success = _invoiceRepository.Store(item);
        if(!success)
            throw new Exception("Unable to save");
    }
}

Chcemy zamockować interfejs IInvoiceRepository w ten sposób, by zwracał true, jeżeli podamy fakturę różną od null.

[Test]
public static void InvoiceService_ShouldCallInvoiceRepository()
{
    //Arrange
    IInvoiceRepository repositoryMock = MockRepository.GenerateMock<IInvoiceRepository>();
    repositoryMock.Stub(x => x.Store(Arg<Invoice>.Is.NotNull)).Return(true);

    Debug.WriteLine(repositoryMock.GetType().FullName);
    var service = new InvoiceService(repositoryMock);

    //Act
    service.SaveInvoice(new Invoice());

    //Assert
    repositoryMock.AssertWasCalled(s => s.Store(Arg<Invoice>.Is.Anything));
}

Na konsoli wypisze się: Castle.Proxies.IInvoiceRepositoryProxyc9859a0ce17d461faef8b8deb39a407c, ponieważ RhinoMocks korzysta z mechanizmu Castle.DynamicProxy.

Zamiast parametru Anything moglibyśmy podać referencję do obiektu faktury przekazywanej do metody SaveInvoice. Często zdarza się też tak, że metoda przyjmuje kilka parametrów, na podstawie których budowany jest obiekt. Tu z pomocą przychodzi mechanizm constraintów.

Rozszerzamy nasz serwis o dodatkową metodę:

public class InvoiceService
{
    //...
    public void SaveInvoice(string id, string userId, decimal amount)
    {
        SaveInvoice(new Invoice()
                        {
                            Amount = amount,
                            Id = id,
                            UserId = userId
                        });
    }
}

I za pomocą metody Matches podajemy warunki, jakie muszą być spełnione.


[Test]
public static void InvoiceService_ShouldCallInvoiceRepository_AndSaveInvoiceWithSameProperties()
{
    //Arrange
    IInvoiceRepository repositoryMock = MockRepository.GenerateMock<IInvoiceRepository>();
    repositoryMock.Stub(x => x.Store(Arg<Invoice>.Is.NotNull)).Return(true);
    string id = "12";
    string userId = "5";
    decimal amount = 100;

    var service = new InvoiceService(repositoryMock);

    //Act
    service.SaveInvoice(id, userId, amount);

    //Assert
    repositoryMock.AssertWasCalled(s => s.Store(Arg<Invoice>.Matches(d => d.Amount == amount 
        && d.UserId == userId)));
}

niedziela, 29 czerwca 2014

[C#|Visual Studio] AOP: Method Interception

Opakowanie metody możemy wykonać zarówno w Castle.DynamicProxy jak i przez Postsharp. Oba podejścia nieco różnią się od siebie.

W przypadku Castle zakładamy, że będziemy korzystać z kontenera IoC. Mamy prosty interfejs IMyInterface oraz prosty typ MyType. Rozpoczynamy od stworzenia interceptora, który będzie logował zdarzenia. Musi on dziedziczyć po interfejsie IInterceptor z przestrzeni nazw IInterceptor. Interfejs ten wystawia metodę Intercept.

public class LoggingInterceptor : IInterceptor
{
    public void Intercept(IInvocation invocation)
    {
        Console.WriteLine("On Enter");
        try
        {
            invocation.Proceed();
        }
        catch (Exception)
        {
            Console.WriteLine("On Error");
            throw;
        }
        finally
        {
            Console.WriteLine("On Exit");
        }
    }
}

Pozostaje jeszcze skonfigurować odpowiednio kontener i można korzystać z typu opakowanego w intereceptor.

using (var windsorContainer = new WindsorContainer())
{
    windsorContainer.Register(Component.For<LoggingInterceptor>());
    windsorContainer.Register(Component.For<IMyInterface>()
                                    .ImplementedBy<MyType>()
                                    .Interceptors<LoggingInterceptor>());

    var type = windsorContainer.Resolve<IMyInterface>();

    type.Run();
}

W Postsharpie dziedziczymy po bazowej klasie OnMethodBoundaryAspect, która definiuje potrzebne nam metody wirtualne. Ważne, by nasz aspekt oznaczyć atrybutem Serializable.

[Serializable]
public class LoggingInterceptor2: OnMethodBoundaryAspect
{
    public override void OnEntry(MethodExecutionArgs args)
    {
        Console.WriteLine("On Entry");
        base.OnEntry(args);
    }

    public override void OnException(MethodExecutionArgs args)
    {
        Console.WriteLine("On Error");
        base.OnException(args);
    }

    public override void OnExit(MethodExecutionArgs args)
    {
        Console.WriteLine("On Exit");
        base.OnSuccess(args);
    }
}

Nasza klasa jest atrybutem, który możemy umieścić nad definicją dowolnej metody.

[C#|Visual Studio] AOP: Wprowadzenie

Programowanie aspektowe powstało niejako w odpowiedzi na powtarzające się schematy pisania kodu takie jak na przykład instrukcje try-catch, transakcje, logowanie itd. Aby uniknąć kopiowania kodu wprowadzono komplementarne do OOP pojęcie aspektów. AOP wprowadza kilka ważnych pojęć:
  • cross-cutting concerns - funkcjonalności używane w wielu miejscach w systemie, np. logowanie zdarzeń
  • advice - kod wykonujący cross-cutting concern
  • pointcut - miejsce występowania aspektów (np. wyjście metody abc)
  • tangling - określenie opisujące wymieszanie kodu typu advice z logiką biznesową
  • weaving (dzierganie) - proces wstrzykiwania kodu związanego z aspektem do logiki biznesowej
Cała idea polega na tym, że chcemy oddzielić cross-cutting concerns od właściwej logiki. Często nie wystarczy wydzielenie do nowej klasy, bo mamy np. do czynienia z konstrukcjami języka typu try-catch-finally. AOP daje nam kod czystszy i napisany w bardziej deklaratywny sposób. Programowanie aspektowe zapewnia zachowanie Single Responsibility Principle, a co za tym idzie znacznie ułatwia refaktoryzację. W wielu przypadkach można je porównać do wzorca dekoratora (np. opakowujemy wywołanie metody w dodatkowy kod).

Przykładowe lokalizacje aspektów:
  • przed wywołaniem metody
  • po pomyślnym zakończeniu metody
  • przy rzuceniu wyjątku
  • zawsze po metodzie (wewnątrz bloku finally)

Najpopularniejsze narzędzia związane z  AOP to Postsharp oraz Castle Dynamic Proxy. Postsharp działa na zasadzie postkompilacji modyfikując wytworzone przez Visual Studio assembly. Dodaje on instrukcje IL w wybranych przez nas miejscach. Jest zintegrowany z VS tak, że możemy przeprowadzać dzięki niemu np. walidację postkompilacyjną. Jeżeli assembly nie przejdzie takiej walidacji, to VS zwróci błąd kompilacji.
Instalujemy go NuGetem, przy czym za pełną wersję trzeba zapłacić.



Drugie narzędzie co Castle Dynamic Proxy. Wstrzykiwanie aspektów odbywa się tu runtime-owo i jest powiązane z kontenerami IoC. Odpytując kontener o dany interfejs dostajemy jego implementację opakowaną w kod przerywający wykonanie metody i dodający do niej kod aspektowy. Jest częścią większego frameworka Castle, w którym kontenerem IoC jest Castle Windsor.

niedziela, 15 czerwca 2014

[C#|Visual Studio] Threading: Interlocked

Interlocked to klasa wystawiająca statyczne metody, które pozwalają uczynić pewne podstawowe operacje atomowymi. W atomowość możemy obłożyć operacje arytmetyczne:

var task1 = Task.Factory.StartNew(() =>
{
    for (int i = 0; i < 50000; i++)
        Interlocked.Increment(ref _variable);
});

var task2 = Task.Factory.StartNew(() =>
{
    for (int i = 0; i < 100000; i++)
        Interlocked.Decrement(ref _variable);
});

var task3 = Task.Factory.StartNew(() =>
{
    for (int i = 0; i < 25001; i++)
        Interlocked.Add(ref _variable, 2);
});

Task.WaitAll(task1, task2, task3);
Console.WriteLine(_variable); //2

, a także operacje podstawienia lub warunkowego podstawienia (jeżeli zmienna ma wartość x, to podstaw y).

private static void Exchange()
{
    Thread thread1 = new Thread(new ThreadStart(A));
    thread1.Start();
    thread1.Join();

    // Written [2]
    Console.WriteLine(Interlocked.Read(ref _value));
}

private static void A()
{
    // Replace value with 10.
    Interlocked.Exchange(ref _value, 10);

    // CompareExchange: if 10, change to 20.
    long result = Interlocked.CompareExchange(ref _value, 20, 10);

    // Returns original value from CompareExchange [1]
    Console.WriteLine(result);
}

Mając do dyspozycji te metody, możemy budować bardziej skomplikowane równoległe algorytmy.

[C#|Visual Studio] String, ToString()

Typ System.String znany także pod aliasem string towarzyszy nam przez całe nasze .NET-owe życie. W tym poście przedstawione zostaną pewne mniej znane konstrukcje, które mogą przydać się w codziennej pracy z C#.

W C# ciągi znaków są immutable, co oznacza, że nie można ich modyfikować. Każda operacja modyfikująca utworzy na stercie nową instancję. Typ System.String dziedziczy po object i implementuje takie interfejsy, jak IComparable, ICloneable, IConvertible, IEnumerable, IEquatable. Dodatkowo w C# można deklarować tzw. verbatim strings - ciągi znaków wewnątrz których każdy znak ma być traktowany jako część ciągu znaków - przed takim stringiem umieszczamy @.

String interning

Jest to technika optymalizująca zużycie pamięci w .NET. Polega ona na tym, że dla każdego ciągu znaków liczony jest hash i taki ciąg znaków wkładany jest do specjalnego słownika, gdzie kluczem jest właśnie ten hash. Słownik istnieje w obrębie całej AppDomain i podczas alokacji nowego string-a, jeżeli wyliczony hash znajduje się w słowniku, to instancja jest reużywana.

Konstruktory

Warto znać przeciążenia konstruktora. Poniżej przykład na to,  jak utworzyć ciąg 10 zer na 4 sposoby. Sposób wpisania literal-u wydaje się najprostszy, ale możemy to zrobić na inne, bardziej wygodne sposoby za pomocą konstruktorów.

char[] array = new char[10];
for (int i = 0; i < array.Length; i++)
    array[i] = '0';

char[] array2 = new char[12];
for (int i = 0; i < array2.Length; i++)
    array2[i] = '0';

Console.WriteLine(new string(array));
Console.WriteLine(new string(array2, 1, array2.Length -2));
Console.WriteLine(new string('0', 10));
Console.WriteLine("0000000000");

StringBuilder

W C# możemy sklejać stringi za pomocą operatora +. W pewnych sytuacjach (wiele sklejeń) jest on jednak bardzo niewydajny, dlatego zaleca się korzystać z klasy StringBuilder. Obiekt taki zawiera pole, które wskazuje na tablicę struktur typu Char. StringBuilder pozwala na manipulowanie taką tablicą. Jeżeli przekroczymy zaalokowane miejsce, to StringBuilder automatycznie skopiuje znaki i zbuduje nową tablicę. Wywołanie ToString spowoduje stworzenie nowego obiektu String na stercie i zwrócenie referencji do niego. Domyślna pojemność StringBuildera to 16 znaków. Jeżeli operacje wykonywane na nim próbują przekroczyć pojemność, to StringBuilder podwaja zaalokowane miejsce.

var random = new Random();
var sb = new StringBuilder();
var next = random.Next() % 100;
while (next % 2 == 0)
{
    sb.Append(next);
    next = random.Next() % 100;
}
Console.WriteLine(sb.ToString());

Instancyjne metody


var str = "Jambalaya";
//IEnumerable extension methods
Console.WriteLine(new string(str.Distinct().ToArray()));

//Walidacja
if(str.EndsWith("ya") && str.StartsWith("Ja") && str.Contains("ala"))
    Console.WriteLine("Matches");

Console.WriteLine(new string(str.Except("aya").ToArray())); //Jmbl
//traktuje aya jako IEnumerable<char>

//Analiza
Console.WriteLine(str.IndexOf('a'));
Console.WriteLine(str.IndexOfAny(new char[] {'a', 'J'}));
Console.WriteLine(str.LastIndexOf('a'));
Console.WriteLine(str.LastIndexOfAny(new char[] { 'l', 'y' }));

//Tworzenie nowych instancji
Console.WriteLine(str.Insert(3, "error")); //Jamerrorbalaya

//Uzupełnia podanym znakiem do zadanej długości (20), domyślnie spacja
Console.WriteLine(str.PadLeft(20, '_')); // ___________Jambalaya
Console.WriteLine(str.PadLeft(20)); //           Jambalaya

//3 oznacza maksymalną liczbę itemów
var items = str.Split(new char[] {'a'}, 3, 
    StringSplitOptions.RemoveEmptyEntries); //J,mb,laya
foreach (var item in items)
    Console.WriteLine(item);

str += "\n \t";
Console.WriteLine(str.Length); //12
//usuwa wszystkie biale spacje
str = str.Trim();
Console.WriteLine(str.Length); //9

Statyczne metody


var s1 = "me";
var s2 = "Mark";
Console.WriteLine(string.Compare(s1, s2)); //1

var s3 = string.Format("{0} and {1} went to school", s1, s2);
Console.WriteLine(s3);

Console.WriteLine(string.IsNullOrEmpty("")); //true
//drugi argument to params
Console.WriteLine(string.Join(",", s1, s2));

 

Numeryczne konwersje


float price = 99.99f;
Console.WriteLine("C - currency: " +
    price.ToString("C")); //99.99 zł
Console.WriteLine(price.ToString("C", 
    CultureInfo.CreateSpecificCulture("en-US"))); //$99.99

int num = 70;
Console.WriteLine("D - decimal: " + num.ToString("D4")); //0070

long number = 70000000000000000;
Console.WriteLine("E - exponential: " + 
    number.ToString("E")); // 7,000000E+016

Console.WriteLine("N - number: " +
    number.ToString("N")); // 70 000 000 000 000 000,00

double perc = 0.02;
Console.WriteLine("P - percent: " + perc.ToString("P")); //2,00%

int value = 1024;
Console.WriteLine("X - hexadecimal: " + value.ToString("X4")); //0040

poniedziałek, 28 kwietnia 2014

[C#|Visual Studio] C#: Reflection

Reflection to potężne narzędzie, za pomocą którego można zrobić wiele dobrego jak również i wiele złego w każdej .NET-owej aplikacji. Najważniejsze klasy, to Type, Assembly, Activator oraz wszystkie opisujące składowe typów, czyli MethodInfo, PropertyInfo, ConstructorInfo, FieldInfo, EventInfo itd. Poza swobodnym przeglądaniem assemblies za pomocą kodu, reflection ma zastosowanie w operacjach wykonywanych dynamicznie na starcie aplikacji. W każdym innym wypadku musimy liczyć się ze sporym spadkiem wydajności w stosunku do kodu statycznie typowanego.

Klasa assembly pozwala na odczytywanie metadanych z różnych assemblies a także ładowanie ich.

var current = Assembly.GetExecutingAssembly();

Console.WriteLine("Current assembly: " + current.FullName);

var fromGac =
    Assembly.Load("System.Xml.Linq,Version=4.0.0.0,Culture=neutral,PublicKeyToken=b77a5c561934e089");

var restSharpAssembly = Assembly.LoadFrom("RestSharp.dll");

Console.WriteLine("\nLoaded assembly: " + restSharpAssembly.FullName);
var dependant = restSharpAssembly.GetReferencedAssemblies();

Console.WriteLine("\nReferenced assemblies:");
foreach (var assemblyName in dependant)
    Console.WriteLine("\t" + assemblyName.FullName);

Metoda GetExecutingAssembly zwróci nam informacje o assembly, w którym ją wywołujemy (np. plik exe). Za pomocą metody Load możemy ładować assemblies z Global Assembly Cache, podając full name. Z kolei dla assemblies bez strong name możemy użyć metody LoadFrom lub LoadFile szukających na dysku. Jeżeli interesują nas zależne assemblies, możemy uzyskać do nich dostęp przy użyciu metody GetReferencedAssemblies.

Mając obiekt typu Assembly możemy pobrać wszystkie publiczne typy w nim zdefiniowane - służy do tego metoda GetExportedTypes..

Console.WriteLine("\nPublic types:");
            var types = restSharpAssembly.GetExportedTypes();

            foreach (Type type in types)
                Console.WriteLine("\t" + type);

            var stype = (from type in types
                        orderby type.GetCustomAttributes(true).Count() descending
                        select type).First();

            var instance = Activator.CreateInstance(stype);
            Console.WriteLine("\nCreated type instance:" + stype.FullName);

            var props = stype.GetProperties();
            Console.WriteLine("Properties:");
            foreach (var propertyInfo in props)
                Console.WriteLine("\t" + propertyInfo.Name);

            var tfields = (from type in types
                           let fields = type.GetFields(BindingFlags.Instance | BindingFlags.NonPublic)
                           orderby fields.Count() descending
                           select fields).First();

            foreach (var fieldInfo in tfields)
                Console.WriteLine("\t" + fieldInfo.Name);

Na każdym z typów możemy sprawdzić, jakie zdefiniowano atrybuty (GetCustomAttributes), a także pobrać wszystkie properties (GetProperties) czy pola (GetFields), także te prywatne, ustawiając odpowiednie flagi. Klasa Activator pozwala nam na tworzenie instancji dowolnego typu (rzutowanej do object). Mając taką instancję możemy pobrać MethodInfo i wywołać taką metodę przekazując do niej parametry.

var sw = new Stopwatch();
sw.Start();
var jsonArray = new JsonArray();
for (int i = 0; i < 1000000; i++)
{
    jsonArray.Add(i);
}
Console.WriteLine("Invoking instance method: {0} ms", sw.ElapsedMilliseconds);
sw.Restart();

var methodInfo = stype.GetMethod("Add");
for (int i = 0; i < 1000000; i++)
{
    methodInfo.Invoke(instance, new object[] {i});
}
Console.WriteLine("Invoking method via reflection: {0} ms", sw.ElapsedMilliseconds);
sw.Reset();

Porównanie takie wypada zdecydowanie na niekorzyść Reflection, głównie przez iterowanie po metadanych i boxing parametrów wejściowych.

Invoking instance method: 83 ms
Invoking method via reflection: 2241 ms

sobota, 26 kwietnia 2014

[C#|Visual Studio] C#: Cryptography

Podstawowe trzy obszary zastosowań kryptografii, którymi są: wyliczanie haszy, kryptografia symetryczna oraz kryptografia asymetryczna zostały w bardzo dobry sposób zaimplementowane w dot Necie w namespace System.Security.Cryptography. Podstawowe pojęcia związane z kryptografią to: plaintext - wiadomość do zaszyfrowania, szyfrowanie - proces obfuskacji danych, deszyfracja - proces odtwarzania oryginalnych danych oraz ciphertext - dane w postaci zaszyfrowanej. Różne techniki kryprograficzne zapewniają:
  • poufność - dane nie mogą być przeczytane przez nieodpowiednie osoby
  • integralność - możliwość weryfikacji, czy dane były modyfikowane
  • uwierzytelnianie - potwierdzenie tożsamości użytkownika
  • niezaprzeczalność - autor wiadomości nie może się jej wyprzeć

Hashing:

Funkcje haszujące konwertują wejście o zmiennej długości w wyjściowy ciąg bajtów o stałej długości tworząc skrót (nazywany także hashem) z wiadomości. Operacja taka jest nieodwracalna i charakteryzuje się tym, że małe zmiany ciągu wejściowego powodują duże zmiany w ciągu wyjściowym. Haszowanie stosuje się w celu zapewnienia integralności. W .NET mamy dostępne 6 klas dziedziczących po abstrakcyjnej HashAlgorithm. Wybór powinien być kompromisem pomiędzy długością klucza (ataki typu bruteforce), a szybkością wykonywania.

Console.WriteLine("Please enter secret message:");
var msg = Console.ReadLine();
var msgBytes = Encoding.UTF8.GetBytes(msg);

HashAlgorithm[] algorithms = { 
                                 new MD5Cng(),
                                 new MD5CryptoServiceProvider(),
                                 new SHA1Managed(), 
                                 new SHA256Managed(),
                                 new SHA384Managed(),
                                 new SHA512Managed(),
                                 new RIPEMD160Managed()
                             };

var watch = new Stopwatch();
foreach (var hashAlgorithm in algorithms)
{
    watch.Start();
    byte[] hash = null;
    for (int i = 0; i < 100000; i++)
    {
        hash = hashAlgorithm.ComputeHash(msgBytes);
    }
    watch.Stop();
    Console.WriteLine("{0} ({1} bit): {2} ms ", 
        hashAlgorithm.GetType().Name, 8*hash.Length, watch.ElapsedMilliseconds);
    watch.Reset();
    Console.WriteLine(BitConverter.ToString(hash).Replace("-", ""));
}

Uzyskane wyniki:

Please enter secret message:
Hello world!

MD5Cng (128 bit): 933 ms
86FB269D190D2C85F6E0468CECA42A20

MD5CryptoServiceProvider (128 bit): 812 ms
86FB269D190D2C85F6E0468CECA42A20

SHA1Managed (160 bit): 257 ms
D3486AE9136E7856BC42212385EA797094475802

SHA256Managed (256 bit): 375 ms
C0535E4BE2B79FFD93291305436BF889314E4A3FAEC05ECFFCBB7DF31AD9E51A

SHA384Managed (384 bit): 2696 ms
86255FA2C36E4B30969EAE17DC34C772CBEBDFC58B58403900BE87614EB1A34B8780263F255EB5E65CA9BBB8641CCCFE

SHA512Managed (512 bit): 3323 ms
F6CDE2A0F819314CDDE55FC227D8D7DAE3D28CC556222A0A8AD66D91CCAD4AAD6094F517A2182360C9AACF6A3DC323162CB6FD8CDFFEDB0FE038F55E85FFB5B6

RIPEMD160Managed (160 bit): 423 ms
7F772647D88750ADD82D8E1A7A3E5C0902A346A3

Symmetric Algorithms:

Algorytmy symetryczne, to takie, w których do procesu szyfrowania i deszyfrowania używany jest ten sam klucz. Klucz taki musi być przechowywany w odpowiednio bezpiecznych warunkach i mieć odpowiednią długość, żeby uniknąć ataków typu brute-force. Obecnie powszechnie używanym algorytmem jest AES (Rijndael z min.128 bitowymi kluczami). Im dłuższy klucz, tym dłuższe szyfrowanie. Jeżeli chcemy wygenerować sobie klucz, najlepiej skorzystać z klasy RNGCryptoServiceProvider zapewniającej dużo lepszą "losowość", niż pozostałe narzędzia do generacji liczb pseudolosowych. Algorytm Rijndael często używany jest w trybie CBC, gdzie wynik kolejnej rundy szyfrowania zależy od rundy poprzedniej. Stąd obok klucza musimy znać wektor wejściowy (przed pierwszą rundą). Wektor taki - wektor IV (Initialization Vector) podaje się obok klucza przy dystrybucji. Konstruktor klasy RijndaelManaged wygeneruje taki wektor.

private static void Symmetric(byte[] msgBytes)
{
    var key = new byte[256/8];
    RNGCryptoServiceProvider provider = new RNGCryptoServiceProvider();
    provider.GetBytes(key);

    var cipher = CreateCipher();

    cipher.Key = key;
    var encryptor = cipher.CreateEncryptor();
    var result = encryptor.TransformFinalBlock(msgBytes, 0, msgBytes.Length);

    Console.WriteLine("Secret key: " + BytesToString(key));
    Console.WriteLine("IV: " + BytesToString(cipher.IV));
    Console.WriteLine("Encrypted message: " + BytesToString(result));

    var cipher2 = CreateCipher();
    cipher2.Key = key;
    cipher2.IV = cipher.IV;

    var decryptor = cipher.CreateDecryptor();
    var originalMessage = decryptor.TransformFinalBlock(result, 0, result.Length);

    var s1 = BytesToString(originalMessage);
    var s2 = BytesToString(msgBytes);

    Console.WriteLine("Are messages equal ? " + s1.Equals(s2));
}

private static RijndaelManaged CreateCipher()
{
    RijndaelManaged cipher = new RijndaelManaged();
    cipher.KeySize = 256;
    cipher.BlockSize = 256;
    cipher.Padding = PaddingMode.ISO10126;
    cipher.Mode = CipherMode.CBC;
    return cipher;
}

Asymmetric algorithms:

Kryptografia asymetryczna nazywana także kryptografią klucza publicznego polega na tym, że mamy wygenerowaną parę kluczy: publiczny i prywatny. Klucze te uzupełniają się, to znaczy, że wiadomość zaszyfrowaną kluczem prywatnym można odszyfrować kluczem publicznym i na odwrót. Operacje takie są od 100 do 1000 razy wolniejsze od kryptografii symetrycznej, dlatego często algorytmów asymetrycznych używa się tylko do przesłania symetrycznego klucza sesji. Najpopularniejszy algorytm to RSA. Para kluczy przechowywana jest w kontenerze, przeważnie w systemie operacyjnym, ale można ją też eksportować do pliku XML. Wczytujemy ją z Xml metodę FromString lub podając nazwę kontenera systemowego przy użyciu klasy CspParameters.

CspParameters cp = new CspParameters();
cp.KeyContainerName = "SampleKeys";
cp.Flags = CspProviderFlags.UseMachineKeyStore;
RSACryptoServiceProvider cipher = new RSACryptoServiceProvider(cp);
var bytes = cipher.Encrypt(msgBytes, true);

Console.WriteLine("Encrypted message: " + BytesToString(bytes));
Console.WriteLine(cipher.ToXmlString(includePrivateParameters:true));

var s1 = BytesToString(msgBytes);
var s2 = BytesToString(cipher.Decrypt(bytes, true));

Console.WriteLine("Are messages equal ? " + s1.Equals(s2));

wtorek, 22 kwietnia 2014

[C#|Visual Studio] LINQ: join

Łączenie dwóch kolekcji w LINQ można wykonać na kilka sposobów:
  • inner join
  • group join
  • left outer join
Podstawowy i najczęściej używany sposób to inner join, czyli po prostu zwykłe złączenie kolekcji, przy czym jeżeli istnieją elementy, które nie mają swoich odpowiedników w drugiej kolekcji, to zostaną pominięte.

internal class Team
{
    public int Id { get; set; }
    public string Name { get; set; }
}

internal class Player
{
    public int Id { get; set; }
    public int TeamId { get; set; }
    public string Name { get; set; }
}

//...

var teams = new Team[3];
for (int i = 0; i < teams.Length; i++)
{
    teams[i] = new Team(){Id = i};
}

teams[0].Name = "Liverpool";
teams[1].Name = "Chelsea";
teams[2].Name = "Arsenal";

var players = new Player[3];
for (int i = 1; i <= players.Length; i++)
{
    players[i-1] = new Player() { Id = i - 1, TeamId =  i};

}

players[0].Name = "Hazard";
players[1].Name = "Ramsey";
players[2].Name = "Rooney";

var inner = from player in players
            join team in teams on player.TeamId equals team.Id
            select new
            {
                player.Name,
                TeamName = team.Name
            };

Console.WriteLine("Inner join:");
foreach (var result in inner)
{
    Console.WriteLine("{0}, {1}", result.Name, result.TeamName);
}    

Wypisane zostaną tylko dwa elementy, te, których indeksy TeamId pokrywają się z identyfikatorami drużyn (player[0], player[1])

Group join to konstrukcja sprowadzająca wynik do niepłaskiej struktury przypominającej wynik operacji group by. Operacja ta związana jest ze słowem kluczowym into, a jej wynik jest typu IEnumerable<IEnumerable<T>>. Możemy na tym oczywiście wywołać select spłaszczający wyniki.

var group = from team in teams
            join player in players on team.Id equals player.TeamId
                into playersWithTeams
            select new { Key = team.Name, Count = playersWithTeams.Count() };

foreach (var item in group)
{
    Console.WriteLine("{0}, {1}", item.Key, item.Count);
}

Left outer join przydaje się tam, gdzie z jakichś powodów chcemy dostać wszystkie elementy z kolekcji będącej lewą stroną operacji. Problemem jest, jak przedstawić elementy, które nie mają swojego odpowiednika w kolekcji, będącej prawą stroną join-a. W takim przypadku LINQ dostarcza extension metodę DefaultIfEmpty<T>, gdzie możemy podać, jak ma wyglądać taki "sztuczny" element.

var outer = from player in players
            join team in teams on player.TeamId equals team.Id
                into playersWithTeams
            from item in playersWithTeams.DefaultIfEmpty(new Team() {Name = "<Empty>"})
            select new
                       {
                           item.Name,
                           Player = player.Name
                       };

foreach (var item in outer)
{
    Console.WriteLine("{1}, {0}", item.Name, item.Player);
}            

Zostaną wypisane wszystkie trzy elementy kolekcji players.

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

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>