sobota, 25 września 2010

HttpContext - Problemy i implementacja wrappera.

Witam dziś podczas kodowania natrafiłem na dosyć nieprzyjemne utrudnienie. Mianowicie dotyczy ono wstrzykiwania poprzez IoC HttpContext i pobierania aktualnego kontekstu do klas które tego potrzebują np. sesja. ciasteczka itp. Jak wiadomo HttpContext jest trudno dostępnym obiektem i ciężko w łatwy sposób wstrzyknąć. Wiec postanowiłem napisac wrapper do HttpContext.

HttpContextProvider
Nasz obiekt będzie dziedziczył po klasie IHttpContextProvider. Będzie on posiadał podstawowe zmienna które pozwolą nam na dostanie się do wszystkich parametrów aktualnego HttpContextu. Obiekt bedzie wyglądał tak:
public class HttpContextProvider : IHttpContextProvider
{

    public System.Web.HttpContextBase Context
    {
        get { return new HttpContextWrapper(HttpContext.Current); }
    }

    public System.Web.HttpRequestBase Request
    {
        get { return Context.Request; }
    }

    public System.Web.HttpResponseBase Response
    {
        get { return Context.Response; }
    }

    public System.Collections.Specialized.NameValueCollection FormOrQueryString
    {
        get
        {
            if (Request.RequestType == "POST")
            {
                return Request.Form;
            }
            return Request.QueryString;
        }
    }
}
Generalnie nic specjalnie trudnego dziwnego w tym obiekcie. Jedyny co może wzbudzić ciekawość to tajemniczy klasa HttpContextWrapper z biblioteki System.Web. Ta klasa dziedziczy po HttpContextBase i pozwala na dostanie się do funkcjonalności aktualnego kontekstu. Dzięki takiemu rozwiązaniu mamy łatwy dostęp do HttpContextu z kontenera IoC jak i również ułatwia nam to testy ponieważ jak większość z was wie stworzenie fałszywego kontekstu jest dosyć nieprzyjemnym zadaniem.

Session Manager
Wszystkie powyższe wypociny były związane z tym ze chciałem stworzyć obiekt do zarządzania sesją użytkownika. Główne rozwiązanie które chciałem dodać silnie typowana obsługa sesji. Klasa sessionManagera wygląda tak:
public class SessionManager : ISessionManager
{
    private IHttpContextProvider _httpContextProvider;

    public SessionManager(IHttpContextProvider provider)
    {
        _httpContextProvider = provider;
    }

    public void Set<T>(string name, T value)
    {
        _httpContextProvider.Context.Session.Add(name, value);
    }

    public T Get<T>(string name)
    {
        return (T)_httpContextProvider.Context.Session[name];
    }

    public T TryGet<T>(string name)
    {
        try
        {
            return (T)_httpContextProvider.Context.Session[name];
        }
        catch (NullReferenceException e)
        {
            return default(T);
        }
    }

    public void Abandon()
    {
        _httpContextProvider.Context.Session.Abandon();
    }
}
Klasa posiada metody podstawowe do pobierania i ustawiania wartości w sesji jak i również porzucania sesji. Oprócz tego dodałem metodę TryGet która pobiera wartość z sesji natomiast gdy nie istnieje to zwraca standardową wartość dla danego typu. Podczas zwykłego pobierania metoda Get wyrzuci wyjątek NullReferenceException co czasami jest niepożądane. Jest to coś podobnego jak mamy w słowniku. Natomiast niestety kolekcja sesji nie jest słownikiem. To tyle na dziś.

niedziela, 19 września 2010

Podstawowe konfiguracje - pliki konfiguracyjne w Asp.net Mvc

Dziś chciałbym pokazać rozwiązanie problemu związanego ze sposobem przechowywania podstawowych informacji o forum np, nazwa, słowa kluczowe.

Podczas gdy zastanawiałem się nad rozwiązaniem problemu najprostszym pomysłem jest po prostu przechowywanie tego w bazie danych. Po głębszych przemyśleniach stwierdziłem ze dla podstawowych informacji nie ma sensu dodatkowo obwiązać bazy danych wiec postanowiłem skorzystać z plików konfiguracyjnych które są dostępne na platformie nie. Jako że i tak one są wykorzystywane podczas tworzenia serwisu wiec nie powoduje to żadnego zwieszonego obciążenia związanego z odczytywaniem tych danych.

Pliki konfiguracyjne
Jak większość wie pliki konfiguracyjne są podzielone na sekcje. Na początek dla naszego forum stworzymy podstawową sekcje która będzie przechowywała najprostsze informacje. Wygląda ona tak:
<mForum SiteTitle="mForum"
        MetaKeywords="Forum,Board,Project,.Net"
        MetaDescription="mForum is going to be fully featured forum implemented in C# on .NET platform"
        MinPasswordLength="5">
</mForum>
Jednak żeby za bardzo nie mieszać postanowiłem konfiguracje dotyczacą forum przenieść do oddzielnego pliku konfiguracyjnego. Aby nasze forum o tym wiedziało w głównym web.configu musimy wstawic taka linie:
<mForum configSource="mForum.config"/>

Klasa Settings
Klasa ta będzie przechowywała informacje dotyczące wszystkich ustawień forum. Nic w tej klasie specjalnego. Zmienne i obiekty które będą zawierały dane z pliku konfiguracyjnego.
public class Settings
{
    public Settings(string siteTitle, string metaKeywords, string metaDescription, int minPasswordLength)
    {
        Check.Argument.IsNotEmpty(siteTitle, "siteTitle");
        Check.Argument.IsNotEmpty(metaKeywords, "metaKeywords");
        Check.Argument.IsNotEmpty(metaDescription, "metaDescription");
        Check.Argument.IsNotNull(minPasswordLength, "minPasswordLength");
        Check.Argument.IsNotNegativeOrZero(minPasswordLength, "minPasswordLength");

        SiteTitle = siteTitle;
        MetaKeywords = metaKeywords;
        MetaDescription = metaDescription;
    }

    public string SiteTitle 
    { 
        get; 
        internal set; 
    }

    ...

}
Klasa SettingsConfigurationSection
Tutaj mamy natomiast obiekt który będzie wykorzystywany w bezpośrednim wyciąganiu danych z plików konfiguracyjnych. Dane z sekcji są bindowane do tego obiektu. Sam obiekt dziedziczy po klasie ConfigurationSection. Wygląda tak:
public class SettingsConfigurationSection : ConfigurationSection
{
    private static string _sectionName = "mForum";

    public static string SectionName
    {
        [DebuggerStepThrough]
        get
        {
            return _sectionName;
        }

        [DebuggerStepThrough]
        set
        {
            Check.Argument.IsNotEmpty(value, "value");

            _sectionName = value;
        }
    }


    [ConfigurationProperty("SiteTitle", DefaultValue = "mForum")]
    public string SiteTitle
    {
        [DebuggerStepThrough]
        get
        {
            return (string)this["SiteTitle"];
        }

        [DebuggerStepThrough]
        set
        {
            this["SiteTitle"] = value;
        }
    }

    [ConfigurationProperty("MetaKeywords", DefaultValue = "")]
    public string MetaKeywords
    {
        [DebuggerStepThrough]
        get
        {
            return (string)this["MetaKeywords"];
        }

        [DebuggerStepThrough]
        set
        {
            this["MetaKeywords"] = value;
        }
    }
    
    ...
}
Składa się on z zmiennych statycznych które bezpośrednio pobierają dane z sekcji. Oprócz tego jest jedna zmienna która jest na stałe ustawiania. Jest to nazwa sekcji.
Inicjowanie klasy Settings odbywa się podczas rejestracji kontenera IoC jak w przypadku wszystkich innych obiektów. Nie jest to nic skomplikowanego. Zapraszam do źródeł po dokładna implementacje.

poniedziałek, 13 września 2010

Komunikacja miedzy usługami a kontrolerem...

Witam dziś chciałbym poruszyć kolejny problem na który się natknąłem podczas rozwijania projektu. Problem dotyczy przesyłania danych pomiędzy kontrolerem a usługami(service). Weźmy prosty przykład. Mamy funkcje która z pobranych danych od użytkownika tworzy temat i post na forum następnie zwraca id zarówno tematu jak i posta. I tutaj pojawia się problem gdy potrzebujemy zwrócić wiele danych z danej funkcji z usług.

Podczas moim przemyśleń postanowiłem wzorować się na mechanizmie żądań i odpowiedzi jaki zwykle stosuje się w WCF usługach. My oczywiście ich nie używamy ale dzięki temu nasz projekt będzie przygotowany na taka ewentualność.

Komunikacja miedzy usługa a kontrolerem
Komunikacja między usługą a kontrolerem wygląda dosyć prosto. Jak może się z początku wydawać mechanizm który stosuje niepotrzebnie tylko zaciemnia kod ale w miarę rozwoju projektu ustandaryzowana komunikacja będzie pomagać otrzymywać i rozwijać projekt. Wyróżniam 2 typy wiadomości: Żądanie(request) i Odpowiedz(response). Odpowiadają im odpowiednia typy: BaseRequestMessage i BaseResponseMessage. Oba typy dziedziczą po BaseMessage. Przykładowy żądanie i odpowiedź:
public class LogOnRequest : BasicRequestMessage
{
    public LogOnDTO LogOnDTO { get; set; }
}
A tutaj mamy odpowiedź:
public class LogOnResponse : BasicResponseMessage
{
    public bool isValidated { get; set; }
}

Dzięki takiemu mechanizmowi komunikacja wygląda czytelnie i ładnie jak i mamy możliwość odbieranie wielu danych z usługi. Oczywiście odpowiednia zmodyfikowałem usługi i testy aby współpracowały z naszym nowym mechanizmem. Ale tego już nie będę opisywał tylko zapraszam do przeglądania źródeł.

czwartek, 9 września 2010

Globalne filtry i wstrzykiwanie zależności...

Witam dziś chciałbym nieco przybliżyć globalne filtry z MvcExtensions i jedno z zastosowań które będzie u mnie w systemie działo za pomocą tego mechanizmu.

Prosty problem: Chcemy łatwo mieć możliwość na bieżąco uaktualniać informacje o ostatnich czasie kiedy user przeglądał stronę bądź zapisywać wykonane przez niego akcje. Gdy pierwszy raz podchodziłem do tego problemu przychodziły mi 2 rozwiązania:
-Pierwsze: Stworzyć BaseController z którego będą dziedziczyły wszystkie inne a który będzie posiadał metodę która będzie wykonywana za każdym razem gdy wykonamy akcje kontrolera.
-Drugie: Użycie filtrów nakładanych na dany kontroler.

Pierwsze rozwiązanie wydaje się dosyć fajnie jednak kłóci się to z zasada ze kontrolery nie powinny posiadać w sobie logiki. Poza tym staja się trudno testowalne poprzez taki zabieg. Wiec postanowiłem skorzystac z drugiego sposobu. Jak się później okazało problemem związanym z atrybutami jest wstrzykiwanie potrzebnych zależności do wykonania konkretnej czynności. Poszperałem troszkę i znalazłem w MvcExtension mechanizm zwany globalnymi filtrami. Notabene jest on zaimplementowany w wersji 3 Mvc frameworka.

UpdateUserLastActivityFilter
Jak już wcześniej mówiłem pierwszym prostym filtrem który będę wykorzystywał jest uaktualnianie ostatniej aktywności użytkownika. Sam atrybut jest prosty. Jeżeli użytkownik jest zalogowany to wywołuje metodę z klasy UserService która uaktualnia jego aktywność. Atrybut wygląda tak:
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = true, AllowMultiple = false), CLSCompliant(false)]
public class UpdateUserLastActivityAttribute : FilterAttribute, IResultFilter
{
    public UpdateUserLastActivityAttribute(IUserService userService)
    {
        Check.Argument.IsNotNull(userService, "userService");

        UserService = userService;
    }

    public IUserService UserService
    {
        get;
        private set;
    }

    public void OnResultExecuting(ResultExecutingContext filterContext)
    {

    }

    public void OnResultExecuted(ResultExecutedContext filterContext)
    {
        Check.Argument.IsNotNull(filterContext, "filterContext");

        string username = filterContext.HttpContext.User.Identity.IsAuthenticated ? filterContext.HttpContext.User.Identity.Name : null;

        if (!string.IsNullOrEmpty(username))
        {
            UserService.UpdateLastActivity(username);
        }
    }
}
Posiadamy już atrybut teraz musimy znaleźć sposób na wstrzykniecie UserService do tego atrybutu. W bibliotece MvcExtension jest zdefiniowany specjalny interfejs który pomaga nam w tym zadaniu. Nazywa sie ConfigureFilterBase. Implementujemy go jako Bootstrapper task. Wygląda to tak:
public class RegisterGlobalFilters : ConfigureFiltersBase
{
    protected override void Configure(IFilterRegistry registry)
    {
        registry.Register<AccountController, UpdateUserLastActivityAttribute>()
                .Register<HomeController, UpdateUserLastActivityAttribute>();
    }
}
Jak widać rejestruje się filtry podobnie jak typy w IoC kontenerze. Wygląda to tak że jako pierwszy typ podajemy dany kontroler natomiast kolejne następne są to filtry które są do niego podczepiane. Takim zabiegiem umożliwiamy wstrzykniecie niezbędnych interfejsów do atrybutów. Poza tym nie musimy już ręcznie dekorować kontrolera ta akcja.

To tyle jeżeli chodzi o globalne filtry. Jak widać MvcExtension posiada wiele ciekawych rozwiązań. Wiele z nich bedzie dostepna w przyszlych wersjach Asp.net Mvc.

niedziela, 5 września 2010

Walidacja obiektów domeny i nie tylko...

Witam troszkę teraz nie regularnie pisałem z powodu braku czasu ale już rozpoczął się wrzesień wiec czas do roboty. Dziś chciałbym omówić problem walidacji obiektów domenowych ale i nie tylko. Interfejs ten będzie można wykorzystać przy dowolnym obiekcie. Cele które chciałbym osiągnąć są 2: Przede wszystkim walidacja musi być wielokrotnego użytku i łatwo dostępna. Drugi cel to możliwość walidacji odrazu całego obiektu i zwrócenie do kontrolera informacji o wszystkich błędnych polach.

Interfejs IValidatable
Zacznijmy od samego początku ponieważ walidacja musi być łatwo dostępna i wielokrotnego użytku każdy obiekt który będzie posiadał walidacje musi implementować powyższy interfejs. Nic specjalnego:
public interface IValidatable
{
    ValidationResult Validate();
}

ValidationResult
Klasa ta składa się ze słownika który przechowuje informacje o błędzie jak i pole którego dotyczy informacja. Natomiast z powodu ze dane pole może posiadać wiele błędów dlatego przechowuje listę stringów. Oprócz tego posiada 2 funkcje 1 do zliczania błędów a druga do dodawania błędów do słownika. Sama klasa wygląda tak:
public class ValidationResult
{
    private IDictionary<string, IList<string>> _validationErrors;

    public ValidationResult()
    {
        _validationErrors = new Dictionary<string, IList<string>>();
    }

    public IDictionary<string, IList<string>> Errors
    {
        get { return _validationErrors; }
        private set { _validationErrors = value; }
    }

    public int CountErrors()
    {
        int errorCount = 0;

        foreach (var errorMessages in _validationErrors.Values)
        {
            errorCount += errorMessages.Count;
        }

        return errorCount;
    }

    public void AddError(KeyValuePair<string, string> error)
    {
        if (error.Key != null)
        {
            if (_validationErrors.ContainsKey(error.Key))
            {
                _validationErrors[error.Key].Add(error.Value);
            }
            else
            {
                IList<string> newErrorList = new List<string>();
                newErrorList.Add(error.Value);

                _validationErrors.Add(new KeyValuePair<string, IList<string>>(error.Key, newErrorList));
            }
        }
    }
}

ValidationHelper
Żeby troszeczkę sobie ułatwić dodawanie błędów postanowiłem stworzyć klasę pomocnicza która będzie nieco automatyzować dodawanie błędów. Będzie to obiekt który posiada 2 metody które dodają błąd jeżeli warunek jest niespełniony. Różnią się tylko 1 parametrem związanym z kluczem błędu. Zwykle nazywa się tak samo jak dane pole którego dotyczy. I w tym celu służy wersja metody z wyrażeniem lambda. Natomiast druga przyjmuje parametr string. Czyli jest bardziej uogólniona. Klasa wygląda tak:
public class ValidationHelper<T>
{
    public ValidationHelper()
    {

    }

    public KeyValuePair<string, string> CreateErrorIf(bool condition, string errorKey, string errorMessageKey)
    {
        if (!condition)
            return new KeyValuePair<string, string>(errorKey, ResourceHelper.GetErrorMessage(errorMessageKey));
        else
            return new KeyValuePair<string, string>();
    }

    public KeyValuePair<string, string> CreateErrorIf<TResult>(bool condition, Expression<Func<T, TResult>> errorKey, string errorMessageKey)
    {
        var expressionMember = errorKey.Body as MemberExpression;

        return CreateErrorIf(condition, expressionMember.Member.Name, errorMessageKey);
    }
}
Metoda która używa wyrażenia lambda po prostu pobiera z niego nazwę danego pola. A teraz przykładowe wykorzystanie powyższych klas:
public class User : BaseEntity, IEntity, IValidatable
{        
    ...

    public virtual ValidationResult Validate()
    {
        ValidationResult validationResult = new ValidationResult();
        ValidationHelper<User> validationHelper = new ValidationHelper<User>();

        validationResult.AddError(validationHelper.CreateErrorIf(!String.IsNullOrEmpty(Username), f => f.Username, ResourceKey.Common.RequiredField));
        validationResult.AddError(validationHelper.CreateErrorIf(Username.Length >= 256, f => f.Username, ResourceKey.Common.FieldLength));

        ...

        return validationResult;
    }
}

Oprócz pisania tego mechanizmu powolutku uzupełniam brakujące testy w projekcie związane już z gotową funkcjonalnością. To tyle na dziś :)