niedziela, 14 listopada 2010

Podsumowanie dotychczasowych poczynań w projekcie.

Witam, na ostatniej prostej w konkursie byłem dosyć mało aktywny, związane to było z praca i studiami.

Wracając do podsumowania. W początkowej fazie projektu(czyli podczas trwania konkursu :)) skupiłem się głównie na stworzeniu mocno funkcjonalnej podstawy na której można budować aplikacje www. W moim wypadku jest to forum. W ramach tej podstawy wchodzą interfejsy do obsługi bazy danych z pomocą Nhibernate, wiele przydatnych wzorców i narzędzi(Event Aggregator, wzorzec specyfikacji, mechanizmy wspomagające walidacje jak również narzędzia związane bliżej z samym budowaniem stron www tak jak AssetManager czy ViewModele). W zasadzie ten pierwszy etap został zakończony. Oczywiście nie można przewidzieć wszystkiego co będzie potrzebne baza już jakaś jest.

Niestety ostatnio nie mogłem poświecić wiele czasu na projekt. W związku z tym nie jest on funkcjonalny od strony prezentacyjnej. Cóż niestety tak wyszło :) W każdym razie to tyle i życzę powodzenia sobie jak i innym uczestnikom konkursu.

sobota, 2 października 2010

Cache - rozmyślania i implementacja

Aktualnie pracowałem nad projektem UI do mojego forum. W zasadzie takie w miarę proste rozwiązanie już jest gotowe. Wiec postanowiłem zabrać się za implementacje powoli podstawowych widoków. I tutaj pojawił się pierwszy problem. A mianowicie zarządzanie, kompresja i cachowanie plików CSS i JS. Jak że zwykle mamy w projekcie jakieś stałe skrypty(np. jQuery) które są wykorzystywanie przy każdym widoku wypadało by żeby one były cachowane i skompresowane. Wiec zabrałem się za rozwiązanie tego problemu czyli implementacje wrappera do standardowego mechanizmu cache który jest dostępny w ASP.NET. Tak samo jak z HttpContextem chciałbym aby był on silnie typowany.

Interfejs i klasa Cache
Jak się pewnie domyślacie klasa cache bedzie implementowała interfejs ICache. Najpierw przedstawie jak ona wygląda poźniej krótki opis.
public interface ICache
{
    void Set<T>(string key, object value, DateTime absoluteExpiration,
 TimeSpan slidingExpiration);
    void Set<T>(string key, object value, DateTime absoluteExpiration);
    void Set<T>(string key, object value, TimeSpan slidingExpiration);
    T Remove<T>(string key);
    void Clear();
    T Get<T>(string key);
}
Interfejs posiada kilka metod które ustawiają wartość w cache. Różnią się one zmiennymi dotyczącymi wygasania danej wartości z cache. Zmienna AbsoluteExpiration określa bezwzględna datę wygaśnięcia danego obiektu czyli natomiast SlidingExpiration nie określa daty wygaśnięcia a jakiś odcinek czasu po którym ma wygasnąć dany wartość. Reszta nie powinna budzić wątpliwości. Sama implementacja wygląda tak:
public class Cache : ICache
{
 //pomijam deklaracje zmiennych i funkcje set
 ...
 
    public T Remove<T>(string key)
    {
        return (T) _cache.Remove(key);
    }

    public void Clear()
    {
        IDictionaryEnumerator cacheEnumerator = _cache.GetEnumerator();

        while (cacheEnumerator.MoveNext())
        {
            _cache.Remove(cacheEnumerator.Key.ToString());
        }
    }

    public T Get<T>(string key)
    {
        Check.Argument.IsNotEmpty(key, "key");

        lock (_syncObject)
        {
            T cacheItem = (T)_cache.Get(key);

            if (cacheItem == null)
                return default(T);
            else
                return cacheItem;
        }
    }

    private void DoSet<T>(string key, object value, 
DateTime absoluteExpiration, TimeSpan slidingExpiration)
    {
        Check.Argument.IsNotEmpty(key, "key");
        Check.Argument.IsNotNull(value, "value");

        if (_cache[key] == null)
            _cache.Add(key, value, null, absoluteExpiration, slidingExpiration,CacheItemPriority.Normal, null);
        else
            _cache.Insert(key, value, null, absoluteExpiration, 
slidingExpiration);
    }
}
Wstawiłem tylko najważniejsze części kodu, pominąłem wszystkie wersje fukcji set ponieważ odwołują się one do prywatnej funkcji DoSet z różnymi parametrami. Jak widać metody tutaj operują na cache z HttpRuntime.Cache. Funkcja Clear pobiera enumerator z kolekcji i usuwa wszystkie obiekty z cache. Funkcja Get pobiera wartość z cache, jeżeli nie istnieje to zwraca wartość domyślną dla danego obiektu. Jest ona thread-safe(bezpieczna wielowątkowo). Natomiast metoda DoSet wstawia obiekt do cache. Sprawdza ona czy już istnieje obiekt o danym kluczu. Jeżeli tak to zamiast dodawać tylko wstawia nową wartość do niego.

W następnym poście powinienem już pokazać mechanizm związany z cachowaniem plików CSS i JS. Oczywiście dodałem testy związane z cachowaniem. Oprócz tego powoli wrzuciłem interfejs graficzny jaki będzie obowiązywał na forum.

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ś :)