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.

środa, 11 sierpnia 2010

Event Aggregator - Cóż to takiego jest ?

Dziś chciałbym opisać kolejny wzorzec/mechanizm który będę używal i ma on na celu znaczne uproszczenie kodowania i eliminowanie niepotrzebnych powiązań miedzy obiektami.

Event Aggregator
Jak już wcześniej wspomniałem ten wzorzec/mechanizm ma na celu eliminowanie niepotrzebnych powiązań miedzy obiektami. Stanowi on pojedyncze źródło dla wielu obiektów. W najprostszej formie wygląda to tak że rejestrujemy klasy które dotyczą konkretnego zdarzenia do naszego event aggregatora. Te klasy nazywają się uchwytami(handlerami). Natomiast gdy wyślemy zdarzenie do event aggregatora on wybiera wszystkie uchwyty które danego zdarzenia dotyczą, posyła im to zdarzenie a one już zajmują się odbiorem tych zdarzeń i wykonywaniem akcji związanych z nim. W moim forum będę używał dosyć prostego rozwiązania ponieważ nie potrzebuje niczego skomplikowanego. Zdarzenie jest to zwykly obiekt ktory musi implementowac interfejs IEvent. np.
public class UserCreatedEvent : IEvent { }
Nasz event aggregator czyli główny ośrodek tez nie jest skomplikowana klasa:
public class EventAggregator : IEventAggregator
{
    private readonly SynchronizationContext _context;
    private readonly IListenerService _listenerService;

    public EventAggregator(SynchronizationContext context, IListenerService listenerService)
    {
        Check.Argument.IsNotNull(context, "context");
        Check.Argument.IsNotNull(listenerService, "listenerService");
            
        _context = context;
        _listenerService = listenerService;
    }
        
    public void SendMessage<T>(T message) where T : IEvent
    {
        var listeners = _listenerService.GetListeners<T>();

        SendAction(() => listeners.ForEach(x => SendMessageToListeners<T>(x, message)));
    }

    private void SendMessageToListeners<T>(IListenTo<T> x, T message) where T : IEvent
    {
        x.Handle(message);
    }

    private void SendAction(Action action)
    {
        _context.Send(state => action(), null);
    }
}
Sercem calego Event Aggregatora jest funkcja SendMessageToListeners. Ta funkcja odpowiada za rozsyłanie zdarzeń do odbiorcow. Wszystkie uchwyty(handlery) są rejestrowane prze kontener IoC. Natomiast ListenerService poprostu pobiera wszystkie te które dotyczą danego zdarzenia. Następnie za pomocą funkcji lambda Event aggregator już konkretnie wysyła zdarzenia do konkretnych uchwytów. Wszystko jest oczywiście wątkowo-bezpieczne(thread-safe). W razie gdyby miały być jakieś wywołania asynchroniczne. Strasznie to brzmi po polsku :P Event handler to nic innego jak klasa implementujaca intertejs IListenTo ktory wyglada tak:
public interface IListenTo<T> where T : IEvent
{
    void Handle(T message);
}
Jest to zwykla klasa z metoda Handle ktora ma w argumencie dany event.

A teraz praktyczny przykład. Powiedzmy ze tworzymy nowego użytkownika. Z samym utworzeniem wiąże się, nadanie mu podstawowych uprawnień, wysłanie maila potwierdzającego jego adres e-mail. W normalnym wypadku po prostu z danej metody tworzącej wywołalibyśmy metody z innych klas związane z tymi czynnościami. Czyli:
public void CreateUser()
{
    //tutaj akcje związane z dodawaniem użytkownika
    ...

    _rolesService.AddRole();
    _emailService.SendEmail();
}
Powstają niepotrzebne powiązania z rożnymi obiektami. W razie jakiś modyfikacji usług związanych z rolami czy wysyłaniem maili możliwe jest ze będzie potrzeba zmian w wielu miejscach w kodzie. W małych systemach to nie stanowi problemu natomiast w miarę powiększana się utrudnia prace, modernizacje danej aplikacji. To samo zadanie rozwiązane z pomocą naszego Event Handlera. Mamy zdarzenie CreatedUserEvent.
//tutaj mamy oddzielne klasy nie zwiazane w ogole z metoda CreateUser
class VerificationEmailHandler: IListenTo<CreatedUserEvent> { };
class BasicRoleHandler: IListenTo<CreatedUserEvent> { };
//--------------------------------------------------------
public void CreateUser()
{
    //tutaj akcje związane z dodawaniem użytkownika
    ...

    CreatedUserEvent event = new CreatedUserEvent();
    _eventAggregator.SendMessage(testEvent);
}
Odrazu widac ze usunęliśmy zbędne powiązania miedzy obiektami. Metoda CreatedUser nie ma żadnego pojęcia na temat innych usług(servicow) niezwiązanych konkretnie z nią. Poza tym łatwo taki kod rozszerzać o nowe uchwyty w miarę potrzeb gdyż wystarczy zarejestrować nowy uchwyt i zostanie on wykonany. Nie potrzeba żadnej zmiany w kodzie.

Oprócz samego Event Handlera dodałem oczywiście test związany z nim. Co za tym idzie stworzyłem nowy projekt testowy związany z mForum.Core. To tyle na dziś :)

wtorek, 10 sierpnia 2010

Infrastruktura związana ściśle z Asp.net MVC...

Teraz czas przyszedł na podstawowa infrastrukturę związana z Asp.net MVC. Czyli miedzy innymi: ViewData Model, Commands, przystosowanie widoków do nowej struktury. Oprócz tego omówię krótko automappera.

ViewData Model
Cóz to za potworek wielu pewnie zapyta. Są to modele(klasy) używane do przekazywania danych z kontrolera do widoku. Dzięki niemu widoki mogą być silnie typowane na konkretny model. Możemy dzięki temu uniknąć używania ViewData i mamy wszystkie dane z widoku ładnie w jednym modelu. Znacznie ułatwia to dostęp do danych. Nie będę tutaj się za bardzo rozpisywał na ten temat ale znacznie rozbudowane wytłumaczenie możecie znaleźć na tym blogu

U mnie struktura ViewDataModel wygląda tak:
BaseViewData - jest to model z Site.Mastera czyli wszystkie dane które potrzebujemy do głównego szablonu. Miedzy innym meta słowa, tytuł, menu etc. Wszystkie inne ViewData Modele dziedziczą po tym. Implementacja:
public class BaseViewData
{
    public string SiteTitle { get; set; }

    public string PageTitle { get; set; }

    public string MetaKeywords { get; set; }

    public string MetaDescription { get; set; }
}
Następnie jest konkretny ViewDataModel dotyczący kontrolera np. AccountViewData który to w konstruktorze ustawia tytuł strony. Przykladowa implementacja dla kontrolera Account:
public class AccountViewData : BaseViewData
{
    private string _pageTitle = "Account";

    public AccountViewData()
    {
        PageTitle = _pageTitle;
    }
}
A potem już konkretne widoki posiadają Modele które dziedziczą z danego modelu kontrolera np:
public class LogOnViewData : AccountViewData
{
    [Required]
    [DisplayName("User name")]
    public string UserName { get; set; }

    [Required]
    [DataType(DataType.Password)]
    [DisplayName("Password")]
    public string Password { get; set; }

    [DisplayName("Remember me?")]
    public bool RememberMe { get; set; }
}
Generowanie ViewData modeli odbywa się za pomocą fabryki po to aby konkretny kontroler nie musiał wiedzieć on zmiennych z Site.Mastera. Przykładowe utworzenie ViewData modelu dla kontrolera Account widoku logowania
public ActionResult LogOn()
{
    LogOnViewData view = _viewDataFactory.Create<LogOnViewData>();

    return View(view);
}

Commands
Następny element związany z widokami to commandy. Sa one używane do bindowana danych które przesyła nam formularz do konkretnego kontrolera. Dzięki temu możemy napisać
public ActionResult Register(RegisterCommnad command)
zamiast:
public ActionResult Register(FormCollection formitems)
i z tego wyciagac pola.

Automapper
Automapper jest to mala biblioteka ułatwiajaca mapowanie obiektu na inny obiekt. Przydaje sie to głównie w transformacji z encji na DTO. Samo mapowanie po skonfigurowaniu wyglada tak:
User testUser = new testUser(); 
UserDTO testUserDTO = Mapper.Map<User, UserDTO>(testUser);
Jak widać miło, łatwo i przyjemnie. Teraz pare słów o konfiguracji. Zacznijmy od stworzenia profilu czyli klasy ktora posiada informacje w jaki sposob mapowac te obiekty. Wyglada ona tak:
public class DefaultProfile : Profile
{
    public override string ProfileName
    {
        get
        {
            return "DefaultProfile";
        }
    }

    protected override void Configure()
    {
        // tutaj konfigurujemy typy
        // Przyklad z User na UserDTO
        Mapper.CreateMap<User, UserDTO>()
            .ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.Id))
        //podalem tylko pierwsze pole ale reszta analogicznie
    }
}
Następnie musimy utworzyć Bootstrapper task który będzie nam ta konfiguracje wczytywał na początku:
public class AutoMapperConfiguratorTask : BootstrapperTask
{
    protected override TaskContinuation ExecuteCore(IServiceLocator serviceLocator)
    {
        Mapper.Initialize(x => x.AddProfile<DefaultProfile>());

        return TaskContinuation.Continue;
    }
}

I już mamy skonfigurowanego automappera. Jest to bardzo przydatna biblioteczka ułatwiająca życie. Oprócz tych głównych zmian co opisałem we wpisie. Dodałem prosta usługę związana z użytkownikami i odpowiednio zmodyfikowałem kontrolery i widoki. Wiec już proste logowanie działa.

niedziela, 8 sierpnia 2010

Asp.net MVC - IoC i Bootstrapper. Wprowadzenie do biblioteki MVC Extensions

Następnie po interfejsie bazy danych postanowiłem zabrać się za implementowanie Bootstrappera i kontenera IoC. Troszkę opowiem tutaj o wzorcu DI i MVC tytułem wprowadzenia

Dependancy injection i Inversion of Control(IoC)
Dependancy injection(wstrzykiwanie zależności) jest to wzorzec projektowy polegajacy na usuwaniu bezpośrednich zależności miedzy komponentami na rzecz architektury typu plugin. W praktyce to wygląda tak ze stosuje się interfejsy zamiast konkretnych klas co powoduje ze można je bardzo łatwo podmieniać. Poprzez ta technikę możemy tworzyć łatwo testowalne obiekty.

Asp.net MVC
MVC jest architektonicznym wzorcem który rozdziela aplikacje na 3 główne komponenty: Model, Widok i Kontroler. W Asp.net jest on alternatywa do programowania Web Forms.

Model : Jest to komponent który składa się z obiektów biznesowych(encji) jak również logiki biznesowej. Generalnie ta cześć jest odpowiedzialna za wyciąganie i przechowywanie danych w bazie danych.

Widok : Widoki są to komponenty które wyświetlają interfejs użytkownika. Po odpowiednim przetworzeniu kodem wynikowym jest to kod html(w przypadku aplikacji web) i inne z tym związane technologie. Zwykle widoki są generowane w oparci o model danych.

Kontroler : Kontrolery maja za zadanie obsługiwać interakcje z użytkownikiem, pracować z modelem danych i wybierać jaki widok maja wyświetlić.

Po wiecej informacji polecam zajrzeć na stronę Asp.net MVC. Po krótkim wprowadzeniu możemy wrócić do głównego tematu.

Bootstrapper
Coż to takiego jest Bootstraper pewnie zapytacie. To jest obiekt który jest wykonywany na początku aplikacji web. Ma za zadanie tworzyć podstawowe konfiguracje i wykonywać zadania które są niezbędne aby aplikacja mogla zostać uruchomiona np. rejestracja kontenera IoC, skonfigurowanie routingu, dodanie własnej fabryki kontrolerów. Otoż wspomina w tytule biblioteka MVC Extensions bardzo na ułatwia ta czynność ponieważ większość już jest w mniej zawarta. Zacznijmy od początku. Pierwsza czynnością jest zastąpienie obiektu HttpApplication z global.asax odpowiednim z biblioteki Mvc Extensions. W moim wypadku będzie to UnityMvcApplication ponieważ używam kontenera Unity. Global.asax będzie teraz wyglądał tak:
public class MvcApplication : UnityMvcApplication
{

}
Jak widać nic specjalnego. Jednak to powoduje ze nie będziemy musieli np ręcznie tworzyć UnityControllerFactory która jest niezbędna aby nasza aplikacja działa z tym kontenerem. Również ta klasa spowoduje wykonanie zadań podstawowych związanych z Asp.net MVC jak również własnych które możemy zaimplementować. Oprócz tego musimy dodać obiekt który implementuje interfejs IModule. Będzie on służyć do konfiguracji kontenera IoC.
public class RegisterServices : IModule
{
    public void Load(IUnityContainer container)
    {
        //tutaj konfigurujemy kontener. Dodajemy repozytoria,uslugi.
    }
}

Bootstrapper Tasks
Pierwszy zadaniem który jest związany bezpośrednio z Asp.net MVC jest klasa która będzie konfigurowała nam routing. Musi on dziedziczyć po klasie RegisterRoutesBase. Przykładowa implementacja:
public class RegisterRoutes : RegisterRoutesBase
{
    protected override void Register(RouteCollection routes)
    {
        //tutaj zajmujemy sie konfiguracja routingu.
    }
}
Kolejnym ciekawym zadaniem choć nie wymaganym jest klasa która pozwala nam na konfiguracje globalnych filtrów. Krotko mówiąc pozwoli nam to na wstrzykiwanie zależności do filtrów gdyż normalnie nie ma możliwości aby to zrobić. Na temat konkretnego rozwiązania i przykładów będę pisał później. Przykladowa implementacja:
public class RegisterGlobalFilters : ConfigureFiltersBase
{
    protected override void Configure(IFilterRegistry registry)
    {
        //tutaj rejestrujemy globalne filtry
        registry.Register<HomeController, MyCustomAttribute>();
    }
}
Oprócz tych specjalny zadań które muszą być wykonywane przed rozpoczęciem aplikacji możemy definiować własne. Obiekty te muszą dziedziczyć po BootstrapperTask. U mnie takim zadaniem jest np inicjowanie klasy udostępniającej kontener IoC aplikacji:
public class RegisterIoC : BootstrapperTask
{
    protected override TaskContinuation ExecuteCore(IServiceLocator serviceLocator)
    {
        IoC.InitializeWith(serviceLocator);

        return TaskContinuation.Continue;
    }
}
Jedynym niezrozumiałym elementem moze byc tutaj zwracany typ TaskContinuation. Może on przyjąć 3 wartości: Continue, Skip, i Break. Continue powoduje wykonywanie następnych zadań w kolejności normalnie, Skip powoduje ominiecie następnego w kolejności zadania natomiast Break powoduje zaprzestanie wykonywania następnych zadań.

Jak widać biblioteka MVC Extensions bardzo nam ułatwiła życie gdyż posiada wiele ciekawych gotowych rozwiązań. Bardzo polecam zapoznać się z nią. Poza tym wiele rozwiązań z te biblioteki ujrzymy w Asp.net MVC 3.0

Interfejs dostepu do bazy danych ciąg dalszy...

Jak już wcześniej wspominałem następnym krokiem w implementacji interfejsu związanego z baza danych będzie podstawowe repozytorium. Będzie ono posiadała cześć wspólna wszystkich repozytoriów. Czyli np, operacje CRUD(wstawianie, dodawanie,usuwanie,modyfikowanie), pobieranie po id oraz zwracanie elementów z danego przedziału(stronicowanie elementów). Oto implementacja tej klasy:
public class BaseRepository<TEntity> : IRepository<TEntity>, IDisposable where TEntity : class, IEntity
{
    private DatabaseContext _databaseContext;

    public BaseRepository(IDatabaseFactory databaseFactory)
    {
        Check.Argument.IsNotNull(databaseFactory, "databaseFactory");

        DatabaseFactory = databaseFactory;
    }

    protected IDatabaseFactory DatabaseFactory
    {
        get;
        private set;
    }

    protected DatabaseContext DatabaseContext
    {
        get
        {
            return _databaseContext ?? (_databaseContext = DatabaseFactory.Get());
        }
    }

    public void Dispose()
    {
        if (DatabaseContext != null)
            GC.SuppressFinalize(DatabaseContext);

        if (DatabaseFactory != null)
            GC.SuppressFinalize(DatabaseFactory);
    }

    public IEnumerable<TEntity> GetBySpec(ISpecification<TEntity> specification)
    {
        Check.Argument.IsNotNull(specification, "specification");

        IEnumerable<TEntity> result = DatabaseContext.CreateQuery<TEntity>().Where(specification.SatisfiedBy());

        return result;
    }

    public virtual void Add(TEntity entity)
    {
        Check.Argument.IsNotNull(entity, "entity");

        DatabaseContext.Save<TEntity>(entity);
    }

    public virtual void Delete(Guid id)
    {
        Check.Argument.IsNotNull(id, "id");

        TEntity entity = GetById(id);

        if (entity != null)
            DatabaseContext.Delete<TEntity>(entity);
    }

    public virtual void Delete(TEntity entity)
    {
        Check.Argument.IsNotNull(entity, "entity");

        DatabaseContext.Delete<TEntity>(entity);
    }

    public virtual TEntity GetById(Guid id)
    {
        return DatabaseContext.CreateQuery<TEntity>().SingleOrDefault<TEntity>(x => x.Id == id);
    }

    public virtual IEnumerable<TEntity> All()
    {
        return DatabaseContext.CreateQuery<TEntity>();
    }

    public IEnumerable<TEntity> GetPagedElements<S>(int pageIndex, int pageSize, Expression<Func<TEntity, S>> orderExpression, bool ascending)
    {
        Check.Argument.IsNotNegativeOrZero(pageIndex, "pageIndex");
        Check.Argument.IsNotNegative(pageSize, "pageSize");
        Check.Argument.IsNotNull(orderExpression, "orderExpression");

        IQueryable<TEntity> objectQuery = DatabaseContext.CreateQuery<TEntity>();

        return (ascending) ? objectQuery.OrderBy(orderExpression)
                                        .Skip((pageIndex - 1) * pageSize)
                                        .Take(pageSize)
                                        .ToList()

                            : objectQuery.OrderByDescending(orderExpression)
                                        .Skip((pageIndex - 1) * pageSize)
                                        .Take(pageSize)
                                        .ToList();

    }

    public IEnumerable<TEntity> GetPagedElements<S>(int pageIndex, int pageSize, Expression<Func<TEntity, S>> orderExpression, ISpecification<TEntity> specification, bool ascending)
    {
        Check.Argument.IsNotNegativeOrZero(pageIndex, "pageIndex");
        Check.Argument.IsNotNegative(pageSize, "pageSize");
        Check.Argument.IsNotNull(orderExpression, "orderExpression");
        Check.Argument.IsNotNull(specification, "specification");

        IQueryable<TEntity> objectQuery = DatabaseContext.CreateQuery<TEntity>();

        return (ascending) ? objectQuery.Where(specification.SatisfiedBy())
                                        .OrderBy(orderExpression)
                                        .Skip((pageIndex - 1) * pageSize)
                                        .Take(pageSize)
                                        .ToList()

                            : objectQuery.Where(specification.SatisfiedBy())
                                        .OrderByDescending(orderExpression)
                                        .Skip((pageIndex - 1) * pageSize)
                                        .Take(pageSize)
                                        .ToList();

    }
}
Jak widac wiekszosc metod tutaj to proste operacje na bazie danych za pomoca LINQ.
Oprocz tego jest metoda ktora za pomoca wczesniej wspomnianej specyfikacji wyciaga dane z bazy. Teraz przyklad prostego repozytorium uzytkownika:
public class UserRepository : BaseRepository<User>, IUserRepository
{
    public UserRepository(IDatabaseFactory dbFactory)
        : base(dbFactory)
    {

    }

    public int CountAllUsers()
    {
        return DatabaseContext.Users.Count();
    }

    public int CountNumberOfOnlineUsers()
    {
        UserOnlineUsersSpecification specification = new UserOnlineUsersSpecification();

        return this.GetBySpec(specification).Count<User>();
    }

    public IEnumerable<User> GetAllUsersOnline()
    {
        UserOnlineUsersSpecification specification = new UserOnlineUsersSpecification();

        return this.GetBySpec(specification);
    }

    public User FindUserByUsername(string username)
    {
        Check.Argument.IsNotEmpty(username, "username");

        UserUsernameSpecification specification = new UserUsernameSpecification(username);

        return this.GetBySpec(specification).SingleOrDefault<User>();
    }


    public User FindUserByEmail(string email)
    {
        Check.Argument.IsNotEmpty(email, "email");
        Check.Argument.IsNotInvalidEmail(email, "email");

        UserEmailSpecification specification = new UserEmailSpecification(email);

        return this.GetBySpec(specification).SingleOrDefault<User>();
    }
}
W tym przykładzie możemy zobaczyć wykorzystanie już tych tajemniczych specyfikacji. Jak widać to rozwiązanie dosyć fajnie się prezentuje i oddziela logikę od samej bazy danych. W dodatku można ich wielokrotnie używać w rożnych miejscach naszego repozytorium. Dodałem 3 rodzaje specyfikacji do projektu związane z użytkownikiem. Wiec można sobie tez oglądnąć. To repozytorium ich właśnie używa.

W zasadzie cały interfejs dostępu do bazy danych już jest prawie gotowy. Możliwe są ewentualnie małe poprawki. W projekcie testów NHibernate dodałem testy związane z klasami powyżej.

sobota, 7 sierpnia 2010

NHibernate 3.0 i FluentNHibernate pierwsze kroki i pierwsze problemy...

Jako że postanowiłem zastosować do mojej aplikacji NHibernate 3.0 w konsekwencji również chciałem móc używać FluentNhibernate. I tutaj pojawił się mały problem. Ponieważ oficjalnie autorzy nie wspierają jeszcze nowej wersji NHibernate ponieważ jest to wersja alpha. Natomiast gdy chciałem użyć aktualnej wersji to wyrzucał błędy ze wersja biblioteki mu nie odpowiada. Troszeczkę poszperałem w internecie i znalazłem rozwiązanie. Otóż wystarczy ściągnąć źródła FluentNHibernate, następnie w owym projekcie podmienić stare biblioteki na nowe z NHibernate 3.0. Następnie zbudować i wszystko działa.

Jak już uporałem się z wcześniejszym problem napisałem podstawowe klasy obsługi bazy danych takie jak: DatabaseContext i DatabaseFactory. W dużej mierze były one wzorowane na projekcie shrinkr z codeplexa.

Database Context
DatabaseContext zawiera podstawowe metody takie jak zapisywanie, usuwanie obiektów z bazy, zatwierdzanie zmian.
public class DatabaseContext : IDisposable
{
    private readonly ISession _session;
    private ITransaction _transaction;

    private IQueryable<User> _users;

    public DatabaseContext(ISession session)
    {
        Check.Argument.IsNotNull(session, "session");

        _session = session;
    }

    public IQueryable<User> Users
    {
        [DebuggerStepThrough]
        get
        {
            return _users ?? (_users = CreateQuery<User>());
        }
    }

    public virtual IQueryable<TEntity> CreateQuery<TEntity>() where TEntity : class, IEntity
    {
        return _session.Query<TEntity>();
    }

    public virtual TEntity GetById<TEntity>(long id) where TEntity : class, IEntity
    {
        Check.Argument.IsNotNegative(id, "id");
        return _session.Get<TEntity>(id);
    }

    public virtual void Save<TEntity>(TEntity entity) where TEntity : class, IEntity
    {
        Check.Argument.IsNotNull(entity, "entity");
        EnsureTransaction();
        _session.SaveOrUpdate(entity);
    }

    public virtual void Delete<TEntity>(TEntity entity) where TEntity : class, IEntity
    {
        Check.Argument.IsNotNull(entity, "entity");
        EnsureTransaction();
        _session.Delete(entity);
    }

    public virtual void Commit()
    {
        EnsureTransaction();
        _transaction.Commit();
    }

    private void EnsureTransaction()
    {
        if (_transaction == null || !_transaction.IsActive || _transaction.WasCommitted || _transaction.WasRolledBack)
        {
            _transaction = _session.BeginTransaction();
        }
    }
}


Database Factory
Natomiast DatabaseFactory jest głównym obiektem związanym z zarządzaniem baza ponieważ to w nim odbywa się konfiguracja całego Nhibernate za pomoc wcześniej wspomnianej składni Fluent. Jest ona umieszczona w metodzie prywatnej jednak nie będę jej tutaj pokazywał. Interfejs tej klasy posiada tylko 1 metodę z pomocą której możemy otrzymać DatabaseContext.
public interface IDatabaseFactory
{
    DatabaseContext Get();
}

Unit of Work
Ostatnim obiektem związanym z baza który dziś dodałem jest UnitOfWork. Ponieważ większość operacji na danych odbywa się za pomocą transakcji. Musimy mieć obiekt pozwalający nam zatwierdzać zmiany. Po to właśnie ten obiek został stworzony. Interfejs wyglada tak:
public interface IUnitOfWork
{
    void Commit();
}
Po implementacje samej klasy zapraszam do źródeł.

Acha i jeszcze dodałem do solucji projekt testowy związany z Nhibernatem. To tyle na dziś. W kolejnym wpisie będzie co nieco na temat implementacji bazowych repozytoriów i pewnie wstepna implementacja repozytorium użytkownika.

Projektowanie interfejsu zwiazanego z baza danych.

Dzisiaj zajmiemy się projektowaniem podstawowych interfejsów związanych z logika biznesowa i baza danych. Miedzy innymi implementacja encji, interfejsu repozytorium.

Encje
Zacznijmy od tworzenia encji. Na potrzeby budowania architektury aplikacji narazie stworze tylko 1 encje. Będzie nią użytkownik. Zacznijmy od zaimplementowania interfejsu encji. Częścią wspólna każdej encji jest id. Więc nasz interfejs będzie posiadał właśnie to pole.
public interface IEntity
{
    Guid Id { get; set; }
}
Aby ułatwić sobie sprawę stworzymy bazowa encje która będzie implementowała powyższy interfejs. Spowoduje to ze nie będziemy musieć w każdej encji wypisywać pola id. W razie gdyby pojawiły się inne pola które będą wspólne równie łatwo będzie można modyfikowac bazowa encje co spowoduje zmiany w wszystkich encjach.
Oto implementacja bazowej encji:
public abstract class BaseEntity : IEntity
{
    public virtual Guid Id { get; set; }
}

Jak juz mamy podstawowe typy z których będą dziedziczyły nasze encje możemy teraz zaimplementować pierwsza z nich:
public class User : BaseEntity, IEntity
{        
    public virtual string Username { get; set; }

    public virtual string Password { get; set; }

    public virtual string Email { get; set; }

    public virtual string PasswordQuestion { get; set; }

    public virtual string PasswordAnswer { get; set; }

    public virtual bool isLockedOut { get; set; }

    public virtual DateTime LastActivityDate { get; set; }

    public virtual DateTime LastLoginDate { get; set; }

    public virtual DateTime LastLockedOutDate { get; set; }

    public virtual DateTime CreatedDate { get; set; }

}

Repozytoria
Następnie chciałbym co nieco powiedzieć na temat obiektów które będzie można znaleźć w moim projekcie. Są to repozytoria. Repozytorium jest to klasa która bezpośrednio ma dostęp do bazy danych. Udostępnia on podstawowe operacje CRUD jak i inne bezpośrednio związane z dana encja. Pośredniczy miedzy baza a usługami które te dane będą potrzebowały. Pozwala to na uniezależniane projektu od konkretnego ORM-a. Podmiana wymaga jedynie zaimplementowania kontekstu bazy danych oraz poszczególnych repozytoriów. Implementacja interfejsu repozytorium wygląda tak:
public interface IRepository<TEntity> where TEntity : class
{
    TEntity GetById(Guid id);
    IEnumerable<TEntity> GetBySpec(ISpecification<TEntity> spec);
    void Add(TEntity entity);
    void Delete(Guid id);
    void Delete(TEntity entity);
    IEnumerable<TEntity> GetPagedElements<S>(int pageIndex, int pageSize, Expression<Func<TEntity, S>> orderExpression, bool ascending);
    IEnumerable<TEntity> GetPagedElements<S>(int pageIndex, int pageSize, Expression<Func<TEntity, S>> orderExpression, ISpecification<TEntity> specification, bool ascending);
    IEnumerable<TEntity> All();
}
Większość funkcji tutaj jest raczej zrozumiała. Jedyna zagadka możne być ISpecifcation interfejs. Spójrzmy na przykładowa implementacje:
public class UserOnlineUsersSpecification : Specification<User>
{
    private DateTime _onlineTime;

    public UserOnlineUsersSpecification()
    {
           
    }

    public override Expression<Func<User, bool>> SatisfiedBy()
    {
        _onlineTime = DateTime.Now.AddMinutes(-5);

        return x => x.LastActivityDate >= _onlineTime;
    }
}
Ta specyfikacja ma za zadanie zwrócić wyrażenie lamba które pozwoli nam znaleźć aktualnie użytkowników online. Jak widzymy kazda specyfikacja sklada sie metody SatisfiedBy która zwraca wyrażenie lamba. To wyrażenie jest wykorzystywane w bazie do pobrania odpowiednich danych. Krótko mówiac jest to mała cześć logiki biznesowej opakowana w klase co powoduje że może byc używana w wielu miejscach i unikamy powtórzeń w kodzie. Wiecej na temat tego wzorca mozna znaleść na Wikipedi

W nastepnych wpisach zajmiemy sie juz konkretna implementacja klas zwiazanych z NHibernate i baza danych.

piątek, 6 sierpnia 2010

Struktura projektu oraz podstawowe klasy pomocnicze i rozszerzenia.

W tym poście postaram się przybliżyć główna strukturę projektu jak również opisać podstawowe klasy pomocnicze i rozszerzenia które używam w większości moich projektów.

Struktura Projektu:
- mForum.Core - Tutaj będą znajdować się wszystkie klasy/struktury/metody które są wspólne dla całej solucji i będą używane w każdym z projektów.
- mForum.Domain - W tym projekcie będzie znajdować się wszystko co związane z logika biznesowa czyli np. encje, specyfikacje.
- mForum.Infrasturcture.Automapper - W tym projekcie będą klasy potrzebne do zainicjowana biblioteki AutoMapper
- mForum.Infrastructure.NHibernate - Tutaj znowuż znajdziemy wszystko co związane z mapowaniem danych i baza. Czyli implementacje repozytoriów, obiekty inicjujące baze danych.
- mForum.Services - Jest to warstwa pośrednicząca miedzy logika biznesowa a warstwa prezentacji która jest Asp.net MVC w tym projekcie. Znajdziemy tutaj glownie wszystkie klasy, metody które będą pośredniczyły w obiegu danych miedzy warstwa logiki biznesowej a warstwa prezentacji.
- mForum.Web - Tutaj mamy warstwę prezentacji. W tym projekcie znajdziemy widoki, skrypty js, szablony css. Generalnie wszystko co związane z projektowanie stron www od strony graficznej.
- mForum.Web.Common - Jest to projekt w którym są obiekty bezpośrednio związane z warstwa prezentacji od strony programistycznej. Będziemy tutaj mogli znaleźć własne atrybuty, kontrolery mvc, zadania które będą wykonywane tuz przed rozpoczęciem aplikacji(tzw. bootstrapper taski).

Jak wspominałem wcześniej dodałem już klasy/metody które używam zwykle w każdym projekcie, takie klasy uniwersalne wspomagające prace.
Pierwsza z nich jest klasa Check. Ta klasa posiada metody sprawdzajace dane wejsciowe i w wypadku niezgodnosci warunku w danej metodzie wyrzuca odpowiedni wyjatek. Prosty przyklad sprawdzania czy dane wejsciowe nie sa null:
void testMethod(object testProperty)
{
    if(testProperty == null)
        throw new ArgumentException("testProperty cannot be null: ");
    ...
}
W wypadku pojedynczego argumenty nie jest to zbyt uciążliwe natomiast przy ich większej ilości może powodować lekkie nagromadzenie się instrukcji warunkowych. To samo rozwiązane za pomocą klasy Check:
void testMethod(object testProperty)
{
    Check.Argument.IsNotNull(testProperty, "testProperty");
    ...
}
Klasa Check posiada znacznie więcej metod walidacji danych wejściowych. Po więcej zapraszam do zapoznania sie z kodem.

Oprócz tego dodałem parę metod rozszerzających do klas:
- DateTime: metoda sprawdzająca poprawność daty. Klasa Check bezpośrednio korzysta z tych metod.
- String: metody takie jak sprawdzanie poprawności danego łańcucha znaków jako adres url czy adres e-mail. Klasa Check również z nich korzysta.
- IEnumerable: Dodałem rozszerzenie które pozwala w 1 linijce kodu wykonać iteracje po wszystkich elementach kolekcji i wykonanie jakieś akcji na nich. Petla foreach w skondensowanej formie.

To tyle na dzisiaj. Nadchodzi weekend wiec prawdopodobnie można spodziewać się większej ilości wpisów. Acha i jeszcze jedno właśnie opublikowałem projekt na codeplex wiec już każdy będzie miał dostęp do niego. Co prawda niewiele tam jeszcze jest ale coż :)

środa, 4 sierpnia 2010

Kilka słów na temat planowania aplikacji i wybranych technologii.

Ja już wcześniej wspominałem będę tworzył rozbudowany system forum. Można spytać dlaczego akurat forum skoro jest ich pełno? Głównie dlatego że już od dosyć dawna miałem w planach napisanie właśnie takiego projektu ale jakoś nie potrafiłem się zebrać i zacząć pisać. Konkurs był właśnie takim impulsem do rozpoczęcia.

Główne środowisko w którym będę pisał:
- Visual Web Developer 2010 Express
- Microsoft Sql Server 2008 Express

Technologie wykorzystane:
Kolejna rzeczą jaka bym chciał poruszyć to kilka słów na temat wybranych technologi do tworzenia tego projektu:
- ASP.NET MVC 2 : Tutaj sprawa była dosyć prosta ponieważ zdecydowałem się napisać ten projekt na platformie .NET to miałem do wyboru ASP.NET Webforms i właśnie MVC. Osobiście nigdy nie przypadło mi do gustu styl i sposób programowania w webforms. Wiele problemów powodował np brak bezpośredniego się wpływu na generowany kod, utrudniona mozliwosci stosowania wzorcow projektowych co powodowalo trudniejsze testowanie aplikacji.
- NHibernate : Przejdźmy teraz do kwestii ORM-ów. Brałem pod uwagę 2 NHibernate i Entity Framework. Po dogłębnym przeanalizowaniu ich możliwości i sposobu tworzenia w nim projektów uważam ze Entity Framework nie dorównuje NHibernate w aktualnej wersji. Natomiast ostatnio zaczęły pojawiać się wersje CTP EF-a tzw Code-First i muszę przyznać ze zapowiada się dosyć dobrze. Możliwe ze przyszłe wersje dorownaja NHibernate. Jedynym problemem w Nhibernate jest słabszy LINQ provider niz w EF aczkolwiek mam zamiar uzyc do tego projektu wersji alpha Nhibernate która niedawno się ukazała. W tej powinien być poprawiony. Będzie co testowac :)
- Automapper : Tutaj to niema co dyskutować. Nic bardziej nie ułatwia życia programisty jak automatyczne mapowanie np. z obiektow DTO na obiekty domenowe. Do tego celu służy właśnie ta biblioteka.
- Microsoft Unity Container : Jako mój kontener IoC wybrałem Unity. Głównie z powodu żę dosyć długo już go używam i odpowiada mi sposób jego konfiguracji i prostota używania.
- MVC Extensions : W tej bibliotece znajduje się wiele przydanych narzędzi które mogą ułatwić/pomoc w projektowaniu aplikacji jak np. Bootstrapping, Globalne filtry etc. Więcej na ten temat pewnie będę opisywał w czasie projektowania aplikacji.

Ta lista może systematycznie się powiększać w miarę rozwoju projektu. Na razie to tyle. W następnym wpisie postaram się już przybliżyć ogólna strukturę projektu i oficjalnie opublikować źródła(na razie głownie puste:)) na codeplex.

wtorek, 3 sierpnia 2010

Powoli zaczynamy...

Witam na blogu projektu programistycznego na konkurs "Daj się poznać" Macieja Ansierowicza. Po wiecej informacji odsyłam na strone konkursu

Na tym blogu będę publikował poczynania związane z rozwojem projektu "mForum". Jest to projekt rozbudowanego systemu forumowego. Będzie on pisany w C# z wykorzystaniem Asp.net MVC.

To tyle jeżeli chodzi o pierwszy wpis. Za kilka dni na dobre postaram sie rozpocząć prace nad projektem :)