Typ dynamic w C# 4.0

W wersji 4.0 wprowadzono nowy typ – dynamic. Zmienne tego typu będą sprawdzane w momencie wywołania (runtime) a nie kompilacji. Oznacza to, że w czasie kompilacji nie będziemy poinformowani o błędach takich jak brak funkcji. Przykład:

dynamic variable="tekst";
variable=variable *5;

Powyższy kod skompiluje się, jednak w momencie uruchomienia zostanie wyrzucony wyjątek (nie można pomnożyć przecież tekstu przez liczbę). Gdybyśmy użyli klasycznych typów (np. string) kompilacja nie powiodłaby się.

Dynamic jest wykorzystywany w najnowszej wersji (3.0) ASP.NET MVC np. jako ViewBag – upraszcza to składnie. Wykorzystanie dynamic również eliminuje konieczność wykorzystywania refleksji w momencie gdy mamy obiekt typu object i chcemy wywołać jakąś metodę czy konstruktor (a nie mamy bazowego interfejsu). Należy oczywiście stosować typ ostrożnie ponieważ często jego użycie może oznaczać błędną architekturę.

Sprawdzanie czy metoda jest podpięta do danego zdarzenia

Czasami chcemy mieć pewność, że zdarzenie nie zostanie podpięte dwa razy do tej samej metody. Przyjrzyjmy się jak to zrobić w C#:

private EventHandler _Event;

public event EventHandler ExampleEvent
{
  add
  {
      if (_Event == null || !_Event.GetInvocationList().Contains(value))
          _Event += value;
  }

  remove
  {
      _Event -= value;
  }
}

Rozwiązaniem jest użycie metody GetInvocationList. Następnie podłączając metody do zdarzenia za pomocą += mamy pewność, że nie będzie duplikatów i metoda nie będzie wywoływana kilkukrotnie.

WCF behaviors

Zachowania w WCF umożliwiają rozszerzanie funkcjonalności poprzez np. doczepianie niestandardowych modułów. Do dyspozycji mamy 4 typy, różniące się zasięgiem obejmowania: Service, Operation, Endpoint, Contract. Utworzenie zachowania sprowadza się do implementacji odpowiedniego interfejsu, który zawiera m.in. następujące metody:

  1. AddBindingParameters – umożliwia przekazanie dodatkowych parametrów.
  2. Validate –waliduje (np. czy usługa może zostać uruchomiona).
  3. ApplyDispatchBehavior/ApplyClientBehavior – służy do dodawania rozszerzeń (np. Message Inspector).

Zacznijmy od zachowania przeznaczonego dla usługi (Service). Zachowanie musi implementować interfejs IServiceBehavior. Obejmuje w swoim zasięgu całą usługę (wszystkie endpointy). Przykład doczepiania Message Inspector’a do wszystkich endpointów:

public class ServiceBehaviour : IServiceBehavior 
{
    #region IServiceBehavior Members

    public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, System.Collections.ObjectModel.Collection<ServiceEndpoint> endpoints, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
    {
    }
    public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
    {
        foreach (ChannelDispatcher dispatcher in serviceHostBase.ChannelDispatchers)
        {
            foreach (EndpointDispatcher endpoint in dispatcher.Endpoints)
            {
                    endpoint.DispatchRuntime.MessageInspectors.Add(new ExampleMessageInspector());
            }
        }
    }
    public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
    {            
    }

    #endregion
}

ServiceBehaviour może zostać doczepiony przez kod, plik konfiguracyjny lub atrybut.

Zachowania dla endpoint muszą implementować interfejs IEndpointBehavior. Zasięgiem obejmują oczywiście pojedynczy endpoint. Zachowanie można doczepić za pomocą kodu lub pliku konfiguracyjnego – brak możliwości wykorzystania atrybutu. Przykład:

public class CustomBehaviour : IEndpointBehavior
{
    public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
    {
    }

    public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
    {

    }

    public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
    {
        InspectorExample1 inspector = new InspectorExample1();
        endpointDispatcher.DispatchRuntime.MessageInspectors.Add(inspector);
    }

    public void Validate(ServiceEndpoint endpoint)
    {
    }
}

Analogicznie zachowanie dla kontraktu musi implementować IContractBehavior. Zachowanie może zostać dodane wyłącznie przez atrybut lub kod. Przykład ustawiania własnej klasy zarządzającej instancjami usługi:

public class SingletonBehaviorAttribute : Attribute, IContractBehaviorAttribute, IContractBehavior
{

  #region IContractBehaviorAttribute Members

  public Type TargetContract
  {
    get { return typeof(ISampleService); }
  }

  #endregion

  #region IContractBehavior Members

  public void AddBindingParameters(ContractDescription description, ServiceEndpoint endpoint, System.ServiceModel.Channels.BindingParameterCollection parameters)
  {
    return;
  }

  public void ApplyClientBehavior(ContractDescription description, ServiceEndpoint endpoint, ClientRuntime clientRuntime)
  {
    return;
  }

  public void ApplyDispatchBehavior(ContractDescription description, ServiceEndpoint endpoint, DispatchRuntime dispatch)
  {
    dispatch.InstanceProvider = new ObjectProviderBehavior("Custom ObjectProviderBehavior constructor.");
  }

  public void Validate(ContractDescription description, ServiceEndpoint endpoint)
  {
    return;
  }

  #endregion
}

Zachowania dla pojedynczych operacji muszą implementować IOperationBehavior i mogą zostać dodane za pomocą kodu lub atrybutu. Przykład dodania Parameter Inspector:

#region IOperationBehavior Members
public void AddBindingParameters(
  OperationDescription operationDescription, BindingParameterCollection bindingParameters
)
{ return; }

public void ApplyClientBehavior(OperationDescription operationDescription, ClientOperation clientOperation)
{
  clientOperation.ParameterInspectors.Add(new Inspector());
}

public void ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation)
{
  dispatchOperation.ParameterInspectors.Add(new Inspector());
}

public void Validate(OperationDescription operationDescription){ return; }

Parameter Inspectors pełnią rolę analogiczną do Message Inspector jednak ograniczają się do metod a nie całych wiadomości SOAP np:

public void AfterCall(string operationName, object[] outputs, object returnValue, object correlationState)
{
  Console.WriteLine(
    "IParameterInspector.AfterCall called for {0} with return value {1}.", 
    operationName, 
    returnValue.ToString()
  );
}

public object BeforeCall(string operationName, object[] inputs)
{
  Console.WriteLine("IParameterInspector.BeforeCall called for {0}.", operationName);
  return null;
}

Wiemy już jak implementować zachowania. Pozostało wyjaśnić w jaki sposób możemy dodać zachowania tak aby dana usługa czy metoda wykorzystywała je. Zacznijmy od doczepiania różnych typów zachowań za pomocą kodu:

  1. ServiceBehaviour:
    using (ServiceHost host = new ServiceHost(typeof(Test)))
    { 
       host.Description.Behaviors.Add(customBehavour);
    }

  2. EndpointBehaviour:
    foreach (ServiceEndpoint se in host.Description.Endpoints) 
          se.Behaviors.Add(new CustomBehavior());

  3. OperationBehavour:
    foreach (ServiceEndpoint se in host.Description.Endpoints)
    {
        foreach (OperationDescription od in se.Contract.Operations)
        {
            od.Behaviors.Add(new CustomBehavior);
        }
    }

  4. ContractBehaviour:
  5. foreach (ServiceEndpoint se in host.Description.Endpoints)
    {
        se.Contract.Behaviors.Add(new CustomBehavior);
    }

Możemy również doczepić zachowanie za pomocą pliku konfiguracyjnego. W tym przypadku należy najpierw stworzyć klasę dziedziczącą po BehaviorExtensionElement:

public class CustomBehaviorExtensionElement : BehaviorExtensionElement
{
    protected override object CreateBehavior()
    {
        return new CustomBehaviour();
    }

    public override Type BehaviorType
    {
        get
        {
            return typeof(CustomBehaviour);
        }
    }
}

Musimy po prostu zwrócić instancję oraz typ zachowania. Następnie w pliku konfiguracyjnym:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>

    <system.serviceModel>
        <services>
            <service name="WebApp.OrderService">
                <endpoint
                    behaviorConfiguration="WebAppBeaviour"
                    address="http://localhost:8000/OrderService"
                    binding="wsHttpBinding" bindingConfiguration="customWsHttpBinding"
                    contract="WebApp.OrderService.IOrderService" />
            </service>
        </services>

        <extensions>
            <behaviorExtensions>
                <add name="WebAppBeaviourElement" type="WebApp.Extensions.CustomBehaviorExtensionElement, WebApp.Extensions, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
            </behaviorExtensions>
        </extensions>

        <behaviors>
            <endpointBehaviors>
                <behavior name="WebAppBeaviour">
                    <WebAppBeaviourElement />
                </behavior>
            </endpointBehaviors>
        </behaviors>

        <bindings>
            <wsHttpBinding>
                <binding name="customWsHttpBinding">
                    <security mode="None" />
                </binding>
            </wsHttpBinding>
        </bindings>

    </system.serviceModel>

</configuration> 

Jak widać w pliku konfiguracyjnym, klasa BehaviorExtensionElement  stanowi mediator pomiędzy plikiem XML a kodem konkretnym zachowaniem.

Ostatnią opcją jest użycie atrybutów .W tym przypadku należy oprócz implementacji stosownego zachowania (np. IServiceOperation) również dziedziczyć po Attribute np:

public class CustomBehaviour : Attribute,IServiceBehavior
{
    //...
}

[CustomBehaviour(MaxPoolSize=10,MinPoolSize=2,IncrementSize=2)]
public class Service:IService
{
    //...
}

WCF Message Inspector–wdrożenie (część II)

W poprzednim poście pokazałem jak stworzyć prosty Message Inspector, wyświetlający nagłówek lub wiadomość SOAP. Dzisiaj zajmiemy się podłączeniem zaimplementowanej klasy do usługi. Najładniejszym moim zdaniem rozwiązaniem jest implementacja behaviour’a, który następnie dołączy zaimplementowany message inspector. W przyszłości planuje napisać osobny post o behaviorach i ich zastosowaniu w WCF. Z punktu widzenia message inspector wystarczy wiedzieć, że behaviory służą do rozszerzania funkcjonalności np. endpoint’a.

Zaczynamy więc od implementacji behaviour’a dla endpoint’a:

public class CustomBehaviour : IEndpointBehavior
{
    public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
    {
    }

    public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
    {

    }

    public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
    {
        InspectorExample1 inspector = new InspectorExample1();
        endpointDispatcher.DispatchRuntime.MessageInspectors.Add(inspector);
    }

    public void Validate(ServiceEndpoint endpoint)
    {
    }
}

ApplyDispatchBehavior wykorzystywany jest dla zachowań usługi (nie klienta). W metodzie po prostu tworzymy inspector i dodajemy do kolekcji. Aby skonfigurować w web.config endpoint zgodnie z behaviorem musimy stworzyć jeszcze BehaviorExtensionElement:

public class CustomBehaviorExtensionElement : BehaviorExtensionElement
{
    protected override object CreateBehavior()
    {
        return new CustomBehaviour();
    }

    public override Type BehaviorType
    {
        get
        {
            return typeof(CustomBehaviour);
        }
    }
}

Jak widać wystarczy przeładować metodę i właściwość. Klasa jest niezbędna aby wykorzystać wcześniej zdefiniowane zachowanie w pliku web.config. Pozostało nam już tylko zmodyfikować odpowiednio plik konfiguracyjny:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>

    <system.serviceModel>
        <services>
            <service name="WebApp.OrderService">
                <endpoint
                    behaviorConfiguration="WebAppBeaviour"
                    address="http://localhost:8000/OrderService"
                    binding="wsHttpBinding" bindingConfiguration="customWsHttpBinding"
                    contract="WebApp.OrderService.IOrderService" />
            </service>
        </services>

        <extensions>
            <behaviorExtensions>
                <add name="WebAppBeaviourElement" type="WebApp.Extensions.CustomBehaviorExtensionElement, WebApp.Extensions, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
            </behaviorExtensions>
        </extensions>

        <behaviors>
            <endpointBehaviors>
                <behavior name="WebAppBeaviour">
                    <WebAppBeaviourElement />
                </behavior>
            </endpointBehaviors>
        </behaviors>

        <bindings>
            <wsHttpBinding>
                <binding name="customWsHttpBinding">
                    <security mode="None" />
                </binding>
            </wsHttpBinding>
        </bindings>

    </system.serviceModel>

</configuration> 

BehaviourExtension umożliwił wykorzystanie zachowania w pliku konfiguracyjnym. Bezpośrednio do behavior’a nie możemy odwoływać się w web.config.

WCF Message Inspector–implementacja (część I)

Używając Message Inspector możemy modyfikować wiadomości przychodzące i wychodzące zarówno po stronie serwera jak i klienta. Wystarczy zaimplementować dwa interfejsy: IClientMessageInspector oraz IDispatchMessageInspector. Ich definicja wygląda następująco:

public interface IClientMessageInspector
{
    void AfterReceiveReply(ref Message reply, object correlationState);
    object BeforeSendRequest(ref Message request, IClientChannel channel);
}
public interface IDispatchMessageInspector
{
    object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext);
    void BeforeSendReply(ref Message reply, object correlationState);
}

Pierwszy z nich służy do tworzenia filtrów po stronie klienta a drugi po stronie usługi. Każda z metod wywoływana jest w momencie odebrania odpowiedzi lub wysłania zapytania. Warto zwrócić uwagę, że wiadomości przekazywane są przez referencję(REF). Oznacza to, że możemy tworzyć całkowicie nowy obiekt (wiadomość) przez operator new.

Wartość zwrócona w BeforeSendRequest jest potem przekazywana w parametrze correlationState AfterReceiveReply. Spróbujmy napisać prosty filtr, który wyświetli wiadomość w momencie wysyłki i odbioru:

public class SampleMessageInspector : IDispatchMessageInspector
{
    public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
    {
        MessageBuffer buffer = request.CreateBufferedCopy(Int32.MaxValue);
        request = buffer.CreateMessage();
        Console.WriteLine(buffer.CreateMessage().ToString());
        return null;
    }

    public void BeforeSendReply(ref Message reply, object correlationState)
    {
        MessageBuffer buffer = reply.CreateBufferedCopy(Int32.MaxValue);
        reply = buffer.CreateMessage();
        Console.WriteLine(buffer.CreateMessage().ToString());
    }
}

Innym przykładem jest wyświetlenie wszystkich nagłówków SOAP:

public class MessageInspectorExample2 : IDispatchMessageInspector
{
    public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
    {
        MessageBuffer buffer = request.CreateBufferedCopy(Int32.MaxValue);
        request = buffer.CreateMessage();
        Message originalMessage = buffer.CreateMessage();
        foreach (MessageHeader header in originalMessage.Headers)
        {
            Console.WriteLine("\n{0}\n", header);
        }
        return null;
    }

    public void BeforeSendReply(ref Message reply, object correlationState)
    {
        MessageBuffer buffer = reply.CreateBufferedCopy(Int32.MaxValue);
        reply = buffer.CreateMessage();
        Message originalMessage = buffer.CreateMessage();
        foreach (MessageHeader header in originalMessage.Headers)
        {
            Console.WriteLine("\n{0}\n", header);
        }
    }
}

Analogicznie wygląda sytuacja w przypadku klienta. W prawdziwym przypadku oczywiście w metodach umieścilibyśmy bardziej zaawansowaną logikę (np. walidacja luz wykonywanie logów). W następnej części przedstawię w jaki sposób można podłączyć Message Inspector do klienta lub usługi.

Kiedy i gdzie wykonać czasochłonne operacje inicjalizacyjne w ASP.NET 4.0

Rozważmy architekturę w której na początku ładujemy dane do cache a następnie na poszczególnych stronach wyświetlamy dane zbuforowane. Przyspieszy to oczywiście znacząco działanie aplikacji. Jednym z podejść jest załadowanie danych w momencie startu aplikacji (plik global). Rozwiązanie dobre jednak metoda Application_Start jest wywoływana tak naprawdę w momencie otrzymania pierwszego zapytania – czyli w sytuacji gdy pierwszy użytkownik naszego serwisu próbuje otworzyć stronę. Taki użytkownik doświadczy niższej wydajności systemu niż pozostali. Jednym z rozwiązań przedstawionego problemu jest wysyłanie sztucznych zapytań, które spowodują wywołanie Application_Start.

Najlepszym jednak podejściem jest wykorzystanie interfejsu IProcessHostPreloadClient i zaimplementowanie metody, która będzie wykonana przed przyjściem zapytań HTTP:

  1. Otwieramy plik konfiguracyjny applicationHost.config. Znajduje się on w C:\Windows\System32\inetsrv\config\applicationHost.config. Odnajdujemy nasz applicationPool i dodajemy\modyfikujemy atrybut startMode.
    <applicationPools>
         <add name="nazwa pool" managedRuntimeVersion="v4.0" startMode="AlwaysRunning" />
    </applicationPools> 

  2. Odnajdujemy definicję strony i dokonujemy kilka modyfikacji (serviceAutoStart) podłączających naszą klasę implementującą interfejs IProcessHostPreloadClient :
    <sites>
        <site name="MySite" id="1">
            <application path="/"
            serviceAutoStartEnabled="true"
            serviceAutoStartProvider="Warmup" >
        </application>
        </site>
    </sites>
    
    <serviceAutoStartProviders>
      <add name="Warmup" 
           type="WebApp.PreWarmUp, WebApp" />
    </serviceAutoStartProviders>

  3. Na koniec wystarczy zaimplementować odpowiednią klasę:
public class PreWarmUp :IProcessHostPreloadClient
{
    public void Preload(string[] parameters)
    {
        // implementacja czasochłonnych operacji
    }
}

W metodzie Preload możemy umieścić kod, który ma zainicjalizować dane jeszcze przed przyjściem jakichkolwiek zapytań.

Integer vs. GUID – bazy danych

Po przerwie związanej ze świętami, mam nadzieję, że blog wróci do dawnej formy… 🙂

W bazach danych wykorzystuje się zwykle jako wartości kluczy głównych i obcych liczby całkowite – INT.
Drugim podejściem (mniej popularnym) jest wykorzystanie globalnych identyfikatorów GUID. Jaka jest tak naprawdę różnica? Skupmy się na zaletach i wadach każdego z nich.

Zaczynamy od typów całkowitych (int).
Zalety:
-bardzo mały rozmiar (tylko 4 bajty),
-naturalna postać – każdy kolejny wiersz to sekwencja liczb naturalnych,
-identyfikator ostatnio dodanego wiersza można łatwo uzyskać za pomocą IDENTITY lub SCOPE_IDENTITY.

Wady:
-problemy ze scalaniem tych samych tabel znajdujących się w różnych bazach. Gdy każda z tabeli ma osobny licznik, ciężko będzie scalić wiersze – prawdopodobnie będą występować duplikaty,
-powyższa wada utrudnia pracę w bazach rozproszonych,
-trudno wygenerować kolejną wartość po stronie klienta i mieć pewność, że w przypadku commit’u nie będzie duplikatu.

Zalety GUID:
-łatwość scalania,
-doskonały do tabel\baz rozproszonych,
-identyfikator jest unikalny w całym systemie.
Wady:
-zbyt pamięciożerny – 16 bajtów! Jeśli na prawdę potrzebujemy tyle wartości lepiej wykorzystać BIGINT i mieć pewność, że cała pula będzie bezpiecznie zaalokowana (unikalna),
-nie możemy wykorzystać IDENTITY\SCOPE_IDENTITY, które często znaczącą ułatwiają życie,
-generując ID po stronie klienta (częsty przypadek) nie mamy pewności unikalności – zależy to od systemu a nawet sprzętu!,
-ostatnia, największa wada to fragmentacja.

Fragmentacja w bazach danych to zdecydowanie temat na osobny post lub nawet cykl postów. Aby zrozumieć problem wyobraźmy sobie, że mamy listę imion np.
Anna
Piotr
Paweł
Marek
Marcin

Następnie tworzymy indeks clustered na tej kolumnie. Oznacza to, że dane do bazy danych musza być dodawane w formie posortowanej. Tzn Anna musi zostać dodana przed Piotr, Paweł przed Piotr itd – zgodnie z alfabetem. Dodając więc kolejno wiersz Anna a następnie Piotr, system musi zaalokować ileś pamięci między Anna a Piotr aby było miejsce na imiona znajdujące się pomiędzy nimi. Jeśli zabraknie tego miejsca występuje fragmentacja – puste miejsca, przepisania. Fragmentacja tworzy się również podczas usuwania wierszy. Oczywiście w realnym systemie SQL Server sytuacja jest bardziej skomplikowana – przykład miał tylko naszkicować problem.

Jak to się ma więc do GUID? Są one niestety niesekwencyjne.Generując kolejno dwa GUID’y nie możemy przewidzieć ich kolejności. W przypadku int (IDENTITY) jest to bardzo proste – każda kolejna liczba jest większa.

Podsumowując, największym problemem GUID’a jest szeroko pojęta wydajność (pamięć, kosztowne operacje spowodowane fragmentacją bazy). Osobiście uważam, że o ile nie wykorzystujemy baz rozproszonych, lepiej nie korzystać z GUID’ów – szybszym i bardziej naturalnym typem jest INT\BIGINT.