Pisząc aplikację w WPF czy też Silverlighcie warto zapoznać się z Prism który oferuje możliwość tworzenia aplikacji z użyciem wzorca MVVM.
Co oferuje nam prism? Przede wszystkim możliwość budowania bogatych wizualnie aplikacji, elastycznych i łatwych w rozbudowanie w przyszłości. Zasada budowy aplikacji w Prism jest podobna do aplikacji w których korzystamy z pluginów. Rozwiązanie takie pozwala w łatwy i szybki sposób zwiększać możliwości aplikacji przy okazji umożliwiając łatwe zarządzanie już istniejącymi modułami.
Prism to zbiór przede wszystkim dobrych praktyk programistycznych - odrzuca ścisłe powiązania pomiędzy klasami.
Aplikacje które tworzone są przy użyciu Prims nazywane są "Aplikacjami kompozytowymi". Zaletami takich aplikacji są m.in.:
- możliwość tworzenia każdego modułu osobno, co ułatwia testowanie oraz tworzenie nowej funkcjonalności dla modułu
- ułatwiają reużycie gotowych już komponentów
- pozwalają rozdzielić zadania pomiędzy wielu deweloperów
Aplikacja kompozytowa w Prism składa się z następujących części:
Modules - podstawowa jednostka w Prism. Każdy moduł dostarcza pewnej funkcjonalności. Każdy moduł może być osobno modyfikowany, testowany i rozwijany niezależnie od całej aplikacji.
Module catalog - przechowuje moduły, które podczas działania będą załadowane do aplikacji.
Shell - aplikacja do której są ładowane moduły. Przeważnie jest to główne okno do którego będą ładowane poszczególne moduły.
Views - są to kontrolki interfejsu użytkownika. Za pomocą bindingu uaktualniają i pobierają dane z modelu.
View models and presenters - przechowują logikę i stan aplikacji.
Models - logika biznesowa, walidacja itp.
Commands - przechowują funkcjonalność która nie jest powiązana z specyficzną kontrolką w widoku. Dzięki temu są testowalne i przenośne.
Regions - miejsca w które będą ładowane kolejne widoki. Mogą to być kontrolki typu: ContentControl, ItemsControl, ListBox, TabControl
Navigation - proces w którym zmienia się zawartość interfejsu użytkownika.
EventAggregator - pozwala na komunikację między komponentami
Dependency injection container - tworzy zarejestrowane w nim typy
Prism można ściągnąć z CodePlexu: http://compositewpf.codeplex.com/
Wypakowujemy pliki do folderu. W środku znajduje się wszystko co jest potrzebne aby rozpocząć pracę z frameworkiem. Nas interesują pliki zawarte w katalogu {Prism instal location}\Bin\ tutaj znajdują się potrzebne dll do projektu w którym będziemy używali Prisma. W moim przypadku zamierzam się skupić na wersji dla WPF, jednak dla silverlight procedura jest praktycznie taka sama. Do projektu musimy dodać referencje do następujących bibliotek:
Po dodaniu referencji mamy poprawnie przygotowany grunt pod budowę naszej aplikacji.
W następnym artykule przedstawię przykłady wybranych zagadnień z wzorców wykorzystywanych w Prism.
Źródło:
http://compositewpf.codeplex.com/
http://www.codeproject.com/KB/WPF/ViewSwitchingAppsPrism4.aspx
piątek, 11 listopada 2011
sobota, 5 listopada 2011
Darmowy SVN online, czyli gdzie hostować kod projektów
Pracując nad projektem, który angażuje wielu programistów potrzebny jest system SVN (Subversion). Tworząc projekty w firmie informatycznej do dyspozycji otrzymuje się gotowy system SVN. Co jednak jeśli chcemy sami uruchomić projekt a nie mamy na to środków/architektury? W takich przypadkach najlepszym sposobem na rozwiązanie jest google. Po kilku minutach poszukiwań nie znalazłem darmowego i zarazem prywatnego SVNa. Większość dostawców oferujących takie rozwiązania oferuje je w wersji prywatnej po uiszczeniu odpowiedniej opłaty. Szukałem więc dalej i odnalazłem serwis Bitbucket.
Zalety konta:
1. Na stronie https://bitbucket.org/ tworzymy nowe konto:
2. Po zarejestrowaniu odbieramy email i aktywujemy nasze konto.
3. Tworzymy nowe repozytorium:
Możemy wybrać czy chcemy do naszego repozytorium dołączyć:
- track
- wiki
Repozytorium pozostawiamy domyślnie jako Mercurial.
4. Instalujemy oprogramowanie umożliwiające zarządzanie.
Ze strony http://tortoisehg.bitbucket.org/pl/ pobieramy klienta TortoiseHG umożliwiającego zarządzanie Mercurial. Instalujemy go na komputerach deweloperów (Nie ma problemu jeżeli na komputerze jest także zainstalowany TortoiseSVN - nie kolidują ze sobą).
5. Pobranie zawartości repozytorium.
Tworzymy pusty katalog np. SingletonRepozytorium a następnie prawym klawiszem myszki wybieramy z menu kontekstowego Clone:
Po kliknięciu w przycisk zobaczymy następujące okienko:
W miejsce źródło wskazujemy adres znajdujący się na stronie nowo utworzonego repozytorium:
System zapyta nas jeszcze o hasło do repozytorium a po poprawnym wprowadzeniu go pobierze zawartość repozytorium.
Operacje które warto znać:
Wysyłanie naszych zmian do repozytorium - Hg Commit
Uaktualnianie z aktualnie znajdującym się repozytorium na serwerze - Synchronization
Tyle krótkiego wstępu. Pozostałe możliwości pozostawiam pozostawiam Wam do odkrycia :)
Zalety konta:
- nieograniczona ilość repozytoriów
- nieograniczona pojemność
- nieograniczona ilość współużytkowników
- definiowane domeny
- mechanizm zgłaszania błędów
- pobieralnia
- wiki
1. Na stronie https://bitbucket.org/ tworzymy nowe konto:
2. Po zarejestrowaniu odbieramy email i aktywujemy nasze konto.
3. Tworzymy nowe repozytorium:
Możemy wybrać czy chcemy do naszego repozytorium dołączyć:
- track
- wiki
Repozytorium pozostawiamy domyślnie jako Mercurial.
4. Instalujemy oprogramowanie umożliwiające zarządzanie.
Ze strony http://tortoisehg.bitbucket.org/pl/ pobieramy klienta TortoiseHG umożliwiającego zarządzanie Mercurial. Instalujemy go na komputerach deweloperów (Nie ma problemu jeżeli na komputerze jest także zainstalowany TortoiseSVN - nie kolidują ze sobą).
5. Pobranie zawartości repozytorium.
Tworzymy pusty katalog np. SingletonRepozytorium a następnie prawym klawiszem myszki wybieramy z menu kontekstowego Clone:
Po kliknięciu w przycisk zobaczymy następujące okienko:
W miejsce źródło wskazujemy adres znajdujący się na stronie nowo utworzonego repozytorium:
System zapyta nas jeszcze o hasło do repozytorium a po poprawnym wprowadzeniu go pobierze zawartość repozytorium.
Operacje które warto znać:
Wysyłanie naszych zmian do repozytorium - Hg Commit
Uaktualnianie z aktualnie znajdującym się repozytorium na serwerze - Synchronization
Tyle krótkiego wstępu. Pozostałe możliwości pozostawiam pozostawiam Wam do odkrycia :)
wtorek, 1 listopada 2011
Unity - typowe użycie
Unity - DI kontener. W tym poście przedstawię kilka użyć z którymi najczęściej się spotykamy podczas pracy z tym kontenerem.
Jeżeli konstruktor klasy Person zależałby od interfejsu ILogger, w momencie tworzenia obiektu Unity stworzyłby instancję Loggera (zarejestrowanego wcześniej).
Jeżeli w metodzie Resolve podamy nazwę - otrzymamy żądaną instancję nazwaną klasy. Dzięki metodzie ResolveAll możemy pobrać wszystkie zarejestrowane klasy implementujące dany interfejs.
wystarczy wskazać konkretny obiekt i skorzystać z metody BuildUp:
Przedstawione tutaj metody to część z możliwości które oferuje Unity. Pozostałe możliwości można znaleźć w manualu dostarczonego do biblioteki.
1. Rozwiązywanie zależności przez interfejsy
Code:
var unityContainer = new UnityContainer(); //Tworzymy kontener unityContainer.RegisterType<ILogger, Logger>(); //Rejestacja typu var logger = unityContainer.Resolve<ILogger>();//Stworzenie obiektu
2. Tworzenie typu bez rejestracji go w kontenerze
Jeżeli chcemy stworzyć instancję klasy nie musimy jej wcześniej rejestrować w kontenerze Unity wystarczy napisać:
Code:
var person = unityContainer.Resolve<Person>();
Jeżeli konstruktor klasy Person zależałby od interfejsu ILogger, w momencie tworzenia obiektu Unity stworzyłby instancję Loggera (zarejestrowanego wcześniej).
3. Tworzenie singletonu
Jeżeli chcemy mieć możliwość tworzenia jednej instancji klasy - singletonu - wystarczy podczas rejestracji typu jako argument przesłać nową instancję klasy ContainerControlledLifetimeManager:
Code:
unityContainer.RegisterType<ILogger, Logger>(new ContainerControlledLifetimeManager());
4. Rejestracja nazwanych typów
Czasami mamy potrzebę rejestrowania nazwanych typów np. SqlLogger oraz ApplicationLogger. W Unity możemy łatwo zrealizować ten scenariusz:
Code:
unityContainer.RegisterType<ILogger, SqlLogger>("SQLLOGGER"); unityContainer.RegisterType<ILogger, ApplicationLogger>("APPLICATIONLOGGER"); var loggers = unityContainer.ResolveAll<ILogger>(); var sqlLogger = unityContainer.RegisterType<ILogger>("SQLLOGGER");
Jeżeli w metodzie Resolve podamy nazwę - otrzymamy żądaną instancję nazwaną klasy. Dzięki metodzie ResolveAll możemy pobrać wszystkie zarejestrowane klasy implementujące dany interfejs.
5. Rejestrowanie istniejących obiektów
Jeżeli chcemy zarejestrować istniejący obiekt w kontenerze korzystamy z metody RegisterInstance:
Code:
Oczywiście obiekt zostanie zarejestrowany w tym momencie jako singleton. var currentLogger = new SqlLogger();
unityContainer.RegisterInstance(currentLogger);
6. Wstrzyknięcie zależności - właściwości
Jeżeli chcemy do stworzonego obiektu wstrzyknąć zależności np. rozważmy klasę:
Code:
using Microsoft.Practices.Unity; namespace ConsoleApplication4 { public class ComplexCalculations { [Dependency] public ILogger Logger { get; set; } } }
wystarczy wskazać konkretny obiekt i skorzystać z metody BuildUp:
Code:
var complexCalculations = new ComplexCalculations(); unityContainer.BuildUp(complexCalculations);
Przedstawione tutaj metody to część z możliwości które oferuje Unity. Pozostałe możliwości można znaleźć w manualu dostarczonego do biblioteki.
sobota, 22 października 2011
Prism czyli DI
DI czyli Dependency Injection (wstrzykiwanie zależności) to technika usuwająca zależności pomiędzy komponentami. Definicję można znaleźć np. w wikipedi czy też w wielu innych źródłach, jednak dla osób wolących zobaczyć w przykładzie o co chodzi pokażę na przykładzie w jaki sposób można skorzystać z dobrodziejstwa DI.
W celu zaprezentowania działania DI stworzymy prostą aplikację konsolową. Będzie to prosta klasa realizująca wzorzec Repository:
Funkcja main będzie zawierać następujący kod:
Na początku tworzymy nasz kontener. Następnie następuje rejestracja interfejsu wraz z implementacją. Zabieg ten mówi: jeżeli użytkownik zażąda zwrócenia obiektu implementującego IPersonRepository - użytkownik otrzyma konkretną implementację w postaci obiektu klasy PersonRepository.
Następnie następuje pobranie obiektu na podstawie interfejsu - Resolve. Użytkownikowi zostaje zwrócony obiekt klasy PersonRepository na którym operuje jak na zwykłym obiekcie.
Przykład przedstawiony powyżej nie jest wymyślnym przykładem i nie obrazuje możliwości jakie daje DI w bardziej złożonym projekcie. Dlatego spróbujemy trochę bardziej skomplikować przykład:
Do naszego repozytorium dodamy interfejs odpowiedzialny za obliczanie całkowitego wynagrodzenia na podstawie wymyślonego wzoru:
Jak widać w funkcji main dodaliśmy tylko linijki odpowiedzialne za wyświetlenie wartości wynagrodzenia oraz zarejestrowaliśmy nowy typ w kontenerze.
Dzięki temu nie musimy w konstruktorze podawać konkretnego obiektu. Zaleta ta daje nam niezależność w przypadku rozszerzania funkcjonalności klas. Wystarczy że w konstruktorze dodamy odpowiednie obiekty, a w kontenerze zarejestrujemy nowe typy.
http://pl.wikipedia.org/wiki/Wstrzykiwanie_zale%C5%BCno%C5%9Bci
W celu zaprezentowania działania DI stworzymy prostą aplikację konsolową. Będzie to prosta klasa realizująca wzorzec Repository:
Code:
public class Person { public string FirstName { get; set; } public string LastName { get; set; } public int BirthYear { get; set; } public override string ToString() { return string.Format("{0} {1} {2}", FirstName, LastName, BirthYear); } }
Code:
public interface IRepository<T> { void Add(T entity); void Remove(T entity); IEnumerable<T> GetElements(); }
Code:
public interface IPersonRepository : IRepository<Person> { }
Code:
public class PersonRepository : IPersonRepository { private List<Person> lPersonList = new List<Person>(); public void Add(Person entity) { lPersonList.Add(entity); } public void Remove(Person entity) { lPersonList.Remove(entity); } public IEnumerable<Person> GetElements() { return lPersonList; } }
Funkcja main będzie zawierać następujący kod:
Code:
static void Main(string[] args) { var unityContainer = new UnityContainer(); unityContainer.RegisterType<IPersonRepository, PersonRepository>(); var personRepository = unityContainer.Resolve<IPersonRepository>(); personRepository.Add(new Person {FirstName = "Jacek", BirthYear = 2000, LastName = "Kowalski"}); foreach (var person in personRepository.GetElements()) { Console.WriteLine(person); } }
Na początku tworzymy nasz kontener. Następnie następuje rejestracja interfejsu wraz z implementacją. Zabieg ten mówi: jeżeli użytkownik zażąda zwrócenia obiektu implementującego IPersonRepository - użytkownik otrzyma konkretną implementację w postaci obiektu klasy PersonRepository.
Następnie następuje pobranie obiektu na podstawie interfejsu - Resolve. Użytkownikowi zostaje zwrócony obiekt klasy PersonRepository na którym operuje jak na zwykłym obiekcie.
Przykład przedstawiony powyżej nie jest wymyślnym przykładem i nie obrazuje możliwości jakie daje DI w bardziej złożonym projekcie. Dlatego spróbujemy trochę bardziej skomplikować przykład:
Do naszego repozytorium dodamy interfejs odpowiedzialny za obliczanie całkowitego wynagrodzenia na podstawie wymyślonego wzoru:
Code:
public interface IEducationalYears { int EducationYears(Person persons); }
Code:
public class EducationalYears : IEducationalYears { public int EducationYears(Person person) { int sum = 0; int age = DateTime.Now.Year - person.BirthYear; sum += age > 18 ? 11 : age - 7; return sum; } }
Code:
public interface IPersonRepository : IRepository<Person> { decimal CalculateTotalWage(); }
Code:
public class PersonRepository : IPersonRepository { private List<Person> lPersonList = new List<Person>(); private IEducationalYears _educationalYears; public PersonRepository(IEducationalYears educationalYears) { _educationalYears = educationalYears; } public void Add(Person entity) { lPersonList.Add(entity); } public void Remove(Person entity) { lPersonList.Remove(entity); } public IEnumerable<Person> GetElements() { return lPersonList; } public decimal CalculateTotalWage() { decimal sum = decimal.Zero; foreach (var person in lPersonList) { sum += _educationalYears.EducationYears(person) * 125; } return sum; } }
Code:
static void Main(string[] args) { var unityContainer = new UnityContainer(); unityContainer.RegisterType<IPersonRepository, PersonRepository>(); unityContainer.RegisterType<IEducationalYears, EducationalYears>(); var personRepository = unityContainer.Resolve<IPersonRepository>(); personRepository.Add(new Person {FirstName = "Jacek", BirthYear = 2000, LastName = "Kowalski"}); foreach (var person in personRepository.GetElements()) { Console.WriteLine(person); } Console.WriteLine("Totale wage: {0}", personRepository.CalculateTotalWage()); }
Jak widać w funkcji main dodaliśmy tylko linijki odpowiedzialne za wyświetlenie wartości wynagrodzenia oraz zarejestrowaliśmy nowy typ w kontenerze.
Dzięki temu nie musimy w konstruktorze podawać konkretnego obiektu. Zaleta ta daje nam niezależność w przypadku rozszerzania funkcjonalności klas. Wystarczy że w konstruktorze dodamy odpowiednie obiekty, a w kontenerze zarejestrujemy nowe typy.
http://pl.wikipedia.org/wiki/Wstrzykiwanie_zale%C5%BCno%C5%9Bci
MEF czy Unity w Prism
Jak wiadomo Microsoft posiada dwa kontenery umożliwiające wykorzystanie od "ręki" możliwości jakie daje Prism. Dla innych bibliotek jak np. Ninject należy odpowiednio zaimplementować własny Bootstrapper co oczywiście nie jest rzeczą trudną i jeżeli będę posiadał trochę wolnego czasu postaram się pokazać.
Tak więc pytanie dla nie lubiących zbytnio kodowania: co użyć MEF czy Unity? Oczywiście odpowiedź nie jest prosta i należy się zastanowić co dają oba kontenery a w czym są różnice. Przeglądając dokumentację Prism można znaleźć informacje o różnicach w nich występujących. Oprócz tych, które znajdują się w podręczniku podam kilka które znalazłem podczas testowania obu tych kontenerów.
Zacznijmy więc od rzeczy które umożliwiają wspólnie i MEF i Unity:
- rejestrują typy w kontenerze
- rejestrują konkretne instancje w kontenerze
- umożliwiają stworzenie instancji stworzonych typów
- umożliwiają wstrzykiwanie instancji poprzez konstruktor
- umożliwiają wstrzykiwanie instancji do właściwości klas
- posiadają atrybuty do oznaczania typów i związków którymi zarządzają obie biblioteki
- obsługują zależności w grafach obiektów (graf obiektu można sobie wyobrazić jako strukturę zależności pomiędzy kolejnymi klasami: np. mamy klasę która w konstruktorze przyjmuje interfejs, a interfejs ten implementuje kolejna klasa posiadająca w swoim konstruktorze inny interfejs itd.)
Rzeczy które oferuje unity a nie MEF:
- potrafi stworzyć instancję niezarejestrowanego typu
- tworzy typy generyczne
- potrafi przechwycić odwołania do obiektu i dodać dodatkowe funkcjonalności do obiektu docelowego
- domyślnie tworzy za każdym razem nową instancję obiektu
- interfejsy muszą być rejestrowane
Co ma MEF a czego nie ma Unity:
- wyszukuje biblioteki w katalogu (automatycznie)
- pobiera pliki XAP i pobierania bibliotek
- reorganizuje właściwości i kolekcje w trakcie odnajdowania bilbiotek
- automatycznie eksportuje odnalezione typy
- standardowo dostarczany wraz z platformą .NET
- domyślnie tworzy obiekty według wzorca Singleton
- nie trzeba niczego rejestrować, wystarczy atrybut Export
- łatwe zarządzanie i tworzenie aplikacji w stylu pluginowym
Tak więc obie biblioteki mają swoje zalety. Można także tworzyć aplikacje wykorzystujące obie biblioteki na raz, np. Unity do operacji DI a MEF do implementowania pluginów.
Tak więc pytanie dla nie lubiących zbytnio kodowania: co użyć MEF czy Unity? Oczywiście odpowiedź nie jest prosta i należy się zastanowić co dają oba kontenery a w czym są różnice. Przeglądając dokumentację Prism można znaleźć informacje o różnicach w nich występujących. Oprócz tych, które znajdują się w podręczniku podam kilka które znalazłem podczas testowania obu tych kontenerów.
Zacznijmy więc od rzeczy które umożliwiają wspólnie i MEF i Unity:
- rejestrują typy w kontenerze
- rejestrują konkretne instancje w kontenerze
- umożliwiają stworzenie instancji stworzonych typów
- umożliwiają wstrzykiwanie instancji poprzez konstruktor
- umożliwiają wstrzykiwanie instancji do właściwości klas
- posiadają atrybuty do oznaczania typów i związków którymi zarządzają obie biblioteki
- obsługują zależności w grafach obiektów (graf obiektu można sobie wyobrazić jako strukturę zależności pomiędzy kolejnymi klasami: np. mamy klasę która w konstruktorze przyjmuje interfejs, a interfejs ten implementuje kolejna klasa posiadająca w swoim konstruktorze inny interfejs itd.)
Rzeczy które oferuje unity a nie MEF:
- potrafi stworzyć instancję niezarejestrowanego typu
- tworzy typy generyczne
- potrafi przechwycić odwołania do obiektu i dodać dodatkowe funkcjonalności do obiektu docelowego
- domyślnie tworzy za każdym razem nową instancję obiektu
- interfejsy muszą być rejestrowane
Co ma MEF a czego nie ma Unity:
- wyszukuje biblioteki w katalogu (automatycznie)
- pobiera pliki XAP i pobierania bibliotek
- reorganizuje właściwości i kolekcje w trakcie odnajdowania bilbiotek
- automatycznie eksportuje odnalezione typy
- standardowo dostarczany wraz z platformą .NET
- domyślnie tworzy obiekty według wzorca Singleton
- nie trzeba niczego rejestrować, wystarczy atrybut Export
- łatwe zarządzanie i tworzenie aplikacji w stylu pluginowym
Tak więc obie biblioteki mają swoje zalety. Można także tworzyć aplikacje wykorzystujące obie biblioteki na raz, np. Unity do operacji DI a MEF do implementowania pluginów.
czwartek, 20 października 2011
Atrybut Conditional
Czasami potrzebujemy aby jakaś metoda została wykonana w przypadku kiedy zajdzie odpowiednia zmiana w systemie użytkownika. Co prawa istnieje możliwość sprawdzania warunkiem if czy możliwe jest wywołanie metody, jednak to powoduje zwiększenie ilości kodu w metodzie.
Możemy tego uniknąć przez zastosowanie atrybutu Conditional.
Dodanie atrybutu Conditional do metody informuje kompilator, że odwołanie do metody nie powinno zostać skompilowane do języka MSIL dopóki symbol powiązany z atrybutem warunku nie zostanie zdefiniowany.
Dodając atrybut Conditional do atrybutu spowoduje że nie zostanie on dodany do metadanych, dopóki odpowiedni symbol powiązany z warunkiem nie zostanie zdefiniowany.
W jaki sposób możemy zdefiniować warunki?
Zobaczmy na prosty przykład:
Przykład dla linii poleceń kompilatora
We właściwościach projektu definiujemy:
Po wywołaniu otrzymamy na ekranie:
Dzieje się tak dlatego, iż tylko jeden symbol został zdefiniowany
Możemy tego uniknąć przez zastosowanie atrybutu Conditional.
Dodanie atrybutu Conditional do metody informuje kompilator, że odwołanie do metody nie powinno zostać skompilowane do języka MSIL dopóki symbol powiązany z atrybutem warunku nie zostanie zdefiniowany.
Dodając atrybut Conditional do atrybutu spowoduje że nie zostanie on dodany do metadanych, dopóki odpowiedni symbol powiązany z warunkiem nie zostanie zdefiniowany.
W jaki sposób możemy zdefiniować warunki?
- Opcje linii poleceń kompilatora (np. /define:DEBUG)
- Zmienne środowiskowe (set DEBUG=1)
- W kodzie (#define DEBUG, #undef DEBUG)
Zobaczmy na prosty przykład:
Code:
using System; using System.Diagnostics; namespace ConsoleApplication1 { class Program { static void Main() { MyMethod(); MyMethod2(); } [Conditional("C1")] public static void MyMethod() { Console.WriteLine("Hello world"); } [Conditional("C2")] public static void MyMethod2() { Console.WriteLine("Hello world"); } } }
Przykład dla linii poleceń kompilatora
We właściwościach projektu definiujemy:
Po wywołaniu otrzymamy na ekranie:
Dzieje się tak dlatego, iż tylko jeden symbol został zdefiniowany
Atrybuty
Atrybuty czyli metadane umożliwiają umożliwiają udostępnienie dodatkowych informacji klasach, konstruktorach, delegatach, typach wyliczeniowych, zdarzeniach, polach, interfejsach, metodach, parametrach, właściwościach, zwracanych wartościach, strukturach lub innych atrybutach. Jak więc widać atrybuty można przypisać właściwie do każdego elementu który znajdziemy w platformie .NET.
Najczęstsze przykłady użycia atrybutów:
Napisanie własnego atrybutu jest rzeczą bardzo prostą. Przedstawię prosty sposób w jaki można taki atrybut sobie napisać a następnie jak można pobrać informacje o nim w czasie działania programu.
Klasa atrybutu:
Klasa wykorzystująca atrybut - Person
Funkcja Main:
Główne kroki programu:
Tworzenie atrybutów jest bardzo proste. Wykorzystanie ich jest bardzo szerokie zwłaszcza w takich frameworkach jak:
Najczęstsze przykłady użycia atrybutów:
- użycie atrybutów w WebSerwisach do oznaczenia metod które mogą być wywoływane przy użyciu protokołu Soap (np. WebMethod)
- Oznaczenie w jaki sposób mają być reprezentowane typy dla kodu niezarządzanego
- Opis assembly: dodanie informacji o autorze, tytule wersji, opisie
- Oznaczenie klasy do serializacji (za pomocą atrybutu Serializable)
- Dodawanie informacji o wymaganym poziomie uprawnień przy dostępie do metod
- Oznaczenie metod które nie powinny być już używane - atrybut Obsolete
Napisanie własnego atrybutu jest rzeczą bardzo prostą. Przedstawię prosty sposób w jaki można taki atrybut sobie napisać a następnie jak można pobrać informacje o nim w czasie działania programu.
Klasa atrybutu:
Code:
using System; namespace ConsoleApplication2 { public class FriendlyNameAttribute : Attribute { protected string _friendlyName; public string FriendlyName { get { return _friendlyName; } set { _friendlyName = value; } } protected bool _isUsed; public bool IsUsed { get { return _isUsed; } set { _isUsed = value; } } public FriendlyNameAttribute(string friendlyName) { FriendlyName = friendlyName; } } }
Klasa wykorzystująca atrybut - Person
Code:
namespace ConsoleApplication2 { class Person { [FriendlyName("Imię", IsUsed = true)] public string FirstName { get; set; } [FriendlyName("Nazwisko")] public string LastName { get; set; } [FriendlyName("Rok urodzenia")] public int BirthYear { get; set; } } }
Funkcja Main:
Code:
using System; using System.Linq; namespace ConsoleApplication2 { class Program { static void Main(string[] args) { //Get type var personType = typeof (Person); //Get propeteries var propertiesInfo = personType.GetProperties(); foreach (var propertyInfo in propertiesInfo) { //Get all attributes for property var attributtes = propertyInfo.GetCustomAttributes(true); //Selecting FriendlyNameAttribute attributte foreach (var friendlyNameAttribute in attributtes.OfType<FriendlyNameAttribute>()) { Console.WriteLine("{0} = {1}", propertyInfo.Name, friendlyNameAttribute.FriendlyName); } } } } }
Główne kroki programu:
- Stworzenie instancji klasy Type dla klasy Person
- Pobranie właściwości zadeklarowanych w klasie Person
- Przejście po kolekcji właściwości i pobranie dla każdej z nich atrybutów
- Instrukcją LINQ pobieramy atrybuty typu FriendlyNameAttribute
- Wyświetlenie nazwy właściwości i wartości pola Name atrybutu FriendlyNameAttribute
Tworzenie atrybutów jest bardzo proste. Wykorzystanie ich jest bardzo szerokie zwłaszcza w takich frameworkach jak:
- Entity Framework
- LINQ2SQL
- ASP.MVC
Subskrybuj:
Posty (Atom)











