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

wtorek, 24 sierpnia 2010

Nhibernate - relacje jeden do wielu i pare zmian kosmetycznych

Witam po dosyć długiej przerwie spowodowanej wakacjami :) Dziś postanowiłem troszeczkę opisać bardzo uproszczony model logiki związanej ścisłe z forum czyli encje takie jak Kategoria forum, Forum, Temat, Post. Oprócz tego opisze parę kosmetycznych.

Logika związana z forum
Forum będzie składało się z kategorii. Dzielą one całe forum na części związane ze sobą tematycznie. Każda kategoria możne posiadać wiele forów. W skład forów wchodzą tematy które zaś maja wiele postów. Aktualnie modele są bardzo proste. Wraz z rozwojem projektu i zwiększaniem funkcjonalności będą się powiększać
Klasa Kategorii forum:
public class ForumCategory : BaseEntity
{
    public virtual string Name { get; set; }

    public virtual IEnumerable<Forum> Forums { get; set; }
}
Klasa Forum:
public class Forum : BaseEntity
{
    public virtual string Name { get; set; }

    public virtual string Description { get; set; }

    public virtual ForumCategory Category { get; set; }

    public virtual IEnumerable<Topic> Topics { get; set; }
}
Klasa Temat:
public class Topic : BaseEntity
{
    public virtual string Name { get; set; }

    public virtual Forum Forum { get; set; }

    public virtual IEnumerable<Post> Posts { get; set; }
}
Klasa Post:
public class Post : BaseEntity
{
    public virtual string Name { get; set; }

    public virtual Topic Topic { get; set; }
}
Nhibernate - relacje jeden do wielu
Jak widać powyżej w naszym prostym modelu encji parę relacji: jeden do wielu. W Fluent Nhibernate relacje te mapuje się przez metody References i HasMany. References oznacza to samo co metoda HasOne czyli że encja posiada 1 obiekt w danej relacji. Natomiast HasMany oznacza ze dana encja posiada wiele obiektów z danej relacji. Kombinacja tych 2 metod powoduje powstanie relacji 1 do wielu. Teraz prosty przykład mapowanie relacji jeden do wielu: ForumCategory -> Forum
public class ForumCategoryMap : ClassMap<ForumCategory>
{
    public ForumCategoryMap()
    {
        Id(p => p.Id).GeneratedBy.Guid();

        Map(p => p.Name).Not.Nullable().Length(256);

        HasMany(p => p.Forums);
    }
}
A tutaj mamy druga końcówkę naszej relacji czyli klasę Forum:
public class ForumMap : ClassMap<Forum>
{
    public ForumMap()
    {
        Id(p => p.Id).GeneratedBy.Guid();

        Map(p => p.Name).Not.Nullable().Length(256);
        Map(p => p.Description).Not.Nullable().Length(256);

        References(p => p.Category);
        HasMany(p => p.Topics);
    }
}
Jak widać w bardzo prostu sposób za pomocą Fluent Nhibernate jesteśmy w stanie stworzyć relacje jeden do wielu.

Oprócz tych zmian wprowadziłem podstawowy layout forum. Usunąłem commandy. Ich role przejma teraz DataTransferObjecty. Podmieniłem walidacje po stronie klienta z standardowej Microsoftu na jquery. Wymagało to ściagniecia MVC Futures i podmiany w widokach pliku MicrosoftMvcJQueryValidation.js zamiast MicrosoftMvcValidation.js.

sobota, 14 sierpnia 2010

Zasoby(Resources) - czyli wielojezyczne strony

Dziś krotki wpis na temat wielojęzycznych stron. Będę opierał się na plikach .resx. Mechanizm jest wbudowany w sama platformę net ale opisze co i jak rozwiązałem w praktyce.

ResourceHelper
Jest to nasz obiekt pomocniczy ułatwiający pobieranie wartości z plików .resx. Jest rownież odpowiedzialny za inicjalizacje ResourceManagera czyli głównego obiektu od strony platformy .NET który pozwala zarządzać zasobami. Sama klasa nie jest skomplikowana. Oprócz metody inicjalizujacej posiada tylko jedna metodę która pobiera wartość typu string z naszych plików z zasobami. Klasa wygląda ona tak:
public static class ResourceHelper
{
    private static ResourceManager _resourceManager;

    public static void Initialize(ResourceManager resourceManager)
    {
        Check.Argument.IsNotNull(resourceManager, "resourceManager");

        _resourceManager = resourceManager;
    }

    public static string GetErrorMessage(string key)
    {
        string errorMessage = _resourceManager.GetString(key);

        if (errorMessage == null)
            throw new ArgumentException(_resourceManager.GetString(ResourceKey.Common.InvalidMessage));

        return errorMessage;
    }
Sama klasa jest statyczna. Jak można zauważyć występuje tutaj również klasa o nazwie ResourceKey. Ta klasa posiada wszystkie informacje na temat nazw danych zmiennych w plikach .resx. Po prostu nie musimy zawsze wpisywać danego stringa tylko używamy klasy pomocniczej i w wypadku jakiś zmian tylko w jednym miejscu będzie to potrzebne. Takie małe ułatwienie :) W nowo dodanym projekcje oprócz plików .resx jest tez fabryka która generuje nam odpowiedniego resourceManagera którym to później inicjujemy naszego helpera. Wygląda ona bardzo prosto:
public class ResourceManagerFactory : IResourceManagerFactory
{
    private ResourceManager _resourceManager;

    public ResourceManagerFactory()
    {

    }

    public ResourceManager CreateResourceManager()
    {
        if (_resourceManager == null)
            _resourceManager = new ResourceManager("mForum.Resources.ErrorsAndExceptions.ErrorMessages", Assembly.GetExecutingAssembly());

        return _resourceManager;
    }
}
Sam konstruktor ResourceManagera przyjmuje 2 argumenty. 1 to nazwa danej klasy która została wygenerowana wraz z utworzeniem pliku .resx. Drugi to dana biblioteka w której się znajduje ten plik.

Natomiast w widokach przyjąłem troszeczkę inne podejście jeżeli chodzi o zasoby. Zasoby standartowo sa internal czyli dostępne tylko z poziomu danej biblioteki natomiast u mnie wszystkie zasoby zwiazane z warstwa prezentacji będą publiczne co powoduje ze możemy się bezpośrednio odwoływać do pliku .resx z naszych widoków.

Oprócz tych zmian oczywiście dodałem testy związane z naszym ResourceHelperem.