ASP.NET MVC 2: Lokalizowanie modelu danych przy użyciu Model MetaData Provider

Jak wiadomo w ASP.NET MVC 2 można określać etykiety poszczególnych właściwości modelu poprzez atrybut DisplayName. W przypadku kiedy chcemy mieć z lokalizowany opis to jest mały problem zwłaszcza jeśli jest potrzeba uruchomić to na .NET 3.5 gdzie DisplayName nie posiada dodatkowych możliwości podana zasobów i etykiet z którego może pobrać opis. A co w przypadku kiedy atrybut ten już ma takie możliwości (.NET 4.0) ale nie ma dostępu do zasobów bo jest w zewnętrznej bibliotece a zasoby są dopiero dostępne w samej aplikacji?
Jednym z działających rozwiązań jest następujące:

...
[DisplayName("X:Code")]
public string Code {get; set;}
...

A więc używając atrybutu DisplayName ustaliłem specjalny zapis nazwy - klucza.
Wykorzystują dostępny model providerów w ASP.NET MVC 2 utworzyłem sobie własny provider który przetwarza atrybuty właściwości w modelu danych. Wyprowadziłem sobie własną klasę z DataAnnotationsModelMetadataProvider w której sprawdzam atrybut DisplayName dla danej właściwości modelu danych i jeśli jest według mojego klucza - wzoru to staram się znaleźć tłumaczenie w lokalnych zasobach aplikacji.


public class LocalizedDataAnnotationsModelMetadataProvider : DataAnnotationsModelMetadataProvider
{
protected override ModelMetadata CreateMetadata(IEnumerable attributes, Type containerType, Func < object > modelAccessor, Type modelType, string propertyName)
{
var meta = base.CreateMetadata(attributes, containerType, modelAccessor, modelType, propertyName);
if (string.IsNullOrEmpty(propertyName))
{
return meta;
}
if (meta != null && !string.IsNullOrEmpty(meta.DisplayName) && meta.DisplayName.StartsWith("X:", StringComparison.Ordinal))
{
string key = meta.DisplayName.Replace("X:", "");
meta.DisplayName = Resources.Strings.ResourceManager.GetString(key, Resources.Strings.Culture);
if (string.IsNullOrEmpty(meta.DisplayName))
{
meta.DisplayName = key;
}
}
if (string.IsNullOrEmpty(meta.DisplayName))
{
meta.DisplayName = string.Format(CultureInfo.InvariantCulture, "{0}", propertyName);
}
return meta;
}
}

Potem zostaje mi to zarejestrować w aplikacji poprzez plik Global.asax np w ten sposób:

protected void Application_Start()
{
...
ModelMetadataProviders.Current = new LocalizedDataAnnotationsModelMetadataProvider();
...
}

Dzięki takiemu mechanizmowi mogę dynamicznie tłumaczyć opisy właściwości modelu a i model zdefiniować jako nie zależny od zasobów aplikacji.

NHibernate i audyt encji

Podczas pisania aplikacji bazodanowych często jest potrzeba zapisywania informacji na temat audytu danej encji. Tylko jak zrobić żeby się nie narobić :). W NHibernate które ostatnio zacząłem używać jest bardzo fajny i wygodny sposób na to. Jak wiemy ten system ORM posiada funkcjonalność jak zdarzanie które są odpalane w różnych momentach np przed zapisem, utworzeniem, kasowaniem itd.
Tworzymy więc sobie interfejs który będzie miał zadeklarowane właściwości opisujące audyt np.

public interface IAuditObject
{
DateTime CreatedDateTime { get; set; }

int CreatedUserId { get; set; }

DateTime ModifyDateTime { get; set; }

int ModifyUserId { get; set; }
}

Teraz wyposażamy klasę naszej encji w ten interfejs. Oczywiście trzeba uzupełnić pliki mapowania o te informacje.
Do przeprowadzenia audytu wykorzystamy dwa zdarzenia które są dostępne: IPreInsertEventListener oraz IPreUpdateEventListener. Te zdarzenia są wywoływane przed dodaniem/zapisem encji. Omówimy teraz sposób implementacji IPreInsertEventListener bo te drugie zdarzenie będzie praktycznie wyglądało bardzo podobnie a różnica będzie w postaci mniejszej ilości pól do zmienienia. A więc jej implementacja mogła by wyglądać następująco:

public bool OnPreInsert(PreInsertEvent @event)
{
if (UpdateAuditInformation(@event.Entity))
{
var entity = @event.Entity as IAuditObject;

Set(@event.Persister, @event.State, "CreatedDateTime", entity.CreatedDateTime);
Set(@event.Persister, @event.State, "ModifyDateTime", entity.ModifyDateTime);
Set(@event.Persister, @event.State, "CreatedUserId", entity.CreatedUserId);
Set(@event.Persister, @event.State, "ModifyUserId", entity.ModifyUserId);
}
return false;
}

W klasie PreInsertEvent są m.in następujące rzeczy:
- instancja naszej encji na której dokonujemy wpisu audytu @event.Entity
- instancja klasy mapującej i zapisującej naszą encje @event.Persister
- tablica wartości które zostaną dodane/zapisane do bazy @event.State
.

Teraz musimy ustawić informacje na temat audytu w naszej encji oraz tablicy stanów. Aby rozpoznać co gdzie leży w tej tablicy możemy skorzystać z obiektu Persister i jego właściwości PropertyNames.
Przykładowa implementacja metody pomocniczej to:

private static void Set(IEntityPersister persister, object[] state, string propertyName, object value)
{
var index = Array.IndexOf(persister.PropertyNames, propertyName);
if (index != -1)
{
state[index] = value;
}
}


Metoda UpdateAuditInformation ustawia nam na encji odpowiednie wartości czasu utworzenia/modyfikacji oraz np ID użytkownika który
wykonał operacje.
Zostaje nam tylko teraz powiadomienie NHibernate o naszej obsłudze tych zdarzeń a możemy to zrobić np:

...
var configuration = new Configuration();
configuration.Configure();

var auditor = new DatabaseAuditor();
configuration.SetListener(ListenerType.PreInsert, auditor);
configuration.SetListener(ListenerType.PreUpdate, auditor);

_sessionFactory = configuration.BuildSessionFactory();
...

Problem z plikiem .designer.cs

Podczas pracy z visual studio 2008 w projektach ASP.NET często jest problem z "odświeżaniem" pliku .designer należącym do danej strony. Efektem tego jest np dodawanie nowej kontrolki do pliku aspx i nie ma tej zmiany w pliku .designer. Dlaczego tak się dzieje nie ma pojęcia ale znam sposób który pomaga to naprawić.
Najpierw usuwamy istniejący plik .designer. Następnie klikamy prawym przyciskiem na plik aspx w którym chcemy naprawić i wybieramy opcje "Convert to web application". Powstaje poprawny plik .designer i co najważniejsze każda aktualizacja w pliku aspx jest także dokonywana w pliku .designer.

Windows Workflow Foundation - Korelacja parametrów

Czasami tworząc diagram jest potrzeba uzyskania pewnych informacji lub wykonanie pewnych akcji równolegle wykorzystują np kontrolkę Parallel.



Jak widzimy w tym przypadku chce wykorzystać tę samą funkcjonalność aby uzyskać zgodę na daną płatność od dwóch menedżerów. W obu "gałęziach" diagram jest identyczny. Najpierw uruchamiam metodę ConfirmPayment interfejsu IManagerConfirmService aby uzyskać zgodę a następnie czekam na reakcje ze strony menadżera określone poprzez wywołanie jednego z dwóch zdarzeń.Te dwa diagramy są wykonywane jednocześnie. Więc jeśli w odpowiedzi zostanie uruchomione zdarzenie do którego diagramu zostanie przypisane, jeśli oba czekają na te same zdarzenia ?.
Twórcy WF pomyśleli też o tym. Najpierw musimy ustalić nazwę parametru który będzie służył do rozpoznawania kontekstu. Ustala się go na interfejsie w poprzez użycia atrybutu CorrelationParameter:

[ExternalDataExchange]
[CorrelationParameter("alias")]
internal interface IManagerConfirmService
{
[CorrelationAlias("alias", "e.Alias")]
event EventHandler Approve;

[CorrelationAlias("alias", "e.Alias")]
event EventHandler Reject;

[CorrelationInitializer]
void ConfirmPayment(string alias);
}

Na każdym zdarzeniu który ma być obsługiwany w kontekście należy zastosować atrybut CorrelationAlias. Jest stosowany aby poinformować WF jaka właściwość klasy przekazywanej w obsłudze zdarzenia przechowuje informacje o wspólnym parametrze. Na koniec musimy przyozdobić jedną metodę lub zdarzenie atrybutem CorrelationInitializer umożliwiają utworzenie kontekstu. Pierwsze uruchomienie tak oznaczonej metody lub zdarzenia, uruchomi w WF utworzenie kontekstu w którym kluczem będzie wartość tego parametru określonego poprzez ten atrybut CorrelationParameter. W tym momencie trzeba przygotować jeszcze kontrolki na diagramie poprzez ustawienie na wszystkich kontrolkach atrybutu CorrelationToken na wspólną wartość. Po ustawieniu tej wartości uaktywni się nam dodatkowe pole OwnerActivityName gdzie trzeba wybrać kontrolkę która zawiera w sobie diagram realizowany w wspólnym kontekście. Używają np zdarzenia z powyższego interfejsu, jego właściwość e.Alias zostanie przekazane do parametru alias przez co zostanie rozpoznany odpowiedni kontekst i zostanie obsłużony odpowiednia część diagramu.

Przykład który to wykorzystuje jest dostępny tutaj CommunicationsWorkflow.zip

SFX-SQLi i zrzut bazy danych do w postaci Xml

Jak wiadomo w MS SQL 2005/2008 można w zapytaniu wykorzystać klauzurę FOR XML.
Na tej stronie przedstawiono metodę na wydobycie całej bazy danych w postaci jednego ciągu znaków. Autor rozwija też program który pozwala na praktyczne przetestowanie podatności własnych systemów na tego typu technikę ataku ( bo można ten atak zaliczyć do SQL injection)
http://www.kachakil.com/papers/sfx-sqli-en.htm

Windows Workflow Foundation i WCF

Jak wiadomo diagramy w WF można wystawić jako usługi sieciowe (web service). Oczywiście musi spełniać warunek następujący: początek diagramu musi zawierać kontrolkę WebServiceInput a na "wyjściu" diagramu musi być kontrolka WebServiceOutput. Standardowy mechanizm publikacji tworzony prosta usługę (asmx). A co w przypadku gdy chcemy mieć większą kontrole nad usługą? Taką kontrole daje nam WCF. W tym momencie trzeba zrobić własny sposób publikacji.
Musimy zdefiniować interfejs oraz opatrzyć atrybutami tak jakbyśmy robili usługę WCF

[ServiceContract()]
interface IKeyValidator
{
[OperationContract(Name = "ValidateKey")]
string ValidateKey(string key);
}

Następnie tworzymy sobie diagram. Ja przygotowałem następujący (pomijam opis jak go poprawnie skonfigurować w oparciu o powyższy interfejs i własne akcje)



WF jest uruchamiany poprzez klase WorkflowWebService która implementuje tworzenie środowiska WF, przygotowanie diagramu, uruchomienie go, przekazanie parametrów do diagramu oraz pobranie z niego wartości zwrotnej. Musimy utworzyć własna klasę która będzie dziedziczyć z tej klasy oraz interfejsu naszej usługi.

[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)]
public class KeyValidatorService : WorkflowWebService, IKeyValidator
{
public KeyValidatorService()
: base(typeof(Workflow1))
{
}
public string ValidateKey(string key)
{
return (string)base.Invoke(typeof(IKeyValidator), "ValidateKey", true, new object[] { key })[0];
}
}

Cały mechanizm uruchamiający diagram jest to wywołanie metody Invoke gdzie parametrami są typ interfejsu usługi sieciowej, nazwa metody którą wywołujemy, flaga oznaczająca czy usługa aktywuje diagram oraz tablica parametrów która jest przykazywana do diagramu.
Klasa WorkflowWebService niestety wymaga środowiska ASP.NET to poprawnego działania stąd dodatkowy atrybut AspNetCompatibilityRequirements na klasie KeyValidatorService. Teraz możemy przygotować aplikacje webową oraz utworzyć plik dla usługi sieciowej która może wyglądać tak

<%@ ServiceHost Language="C#" Service="WebServiceTest.KeyValidatorService" %>

Musimy jeszcze przygotować plik konfiguracyjny aplikacji webowej. Poniżej fragmenty konfiguracji które należy wstawić do web.config aby móc hostować diagramy WF

<configuration>
<configSections>
<section name="WorkflowRuntime" type="System.Workflow.Runtime.Configuration.WorkflowRuntimeSection, System.Workflow.Runtime, Version=3.0.00000.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
</configSections>
<WorkflowRuntime Name="WorkflowServiceContainer">
<Services>
<add type="System.Workflow.Runtime.Hosting.ManualWorkflowSchedulerService, System.Workflow.Runtime, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
<add type="System.Workflow.Runtime.Hosting.DefaultWorkflowCommitWorkBatchService, System.Workflow.Runtime, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
</Services>
</WorkflowRuntime>
<system.web>
<httpModules>
<add type="System.Workflow.Runtime.Hosting.WorkflowWebHostingModule, System.Workflow.Runtime, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" name="WorkflowHost"/>
</httpModules>
</system.web>
<system.serviceModel>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" />
<system.serviceModel>
</configuration>

Przygotowałem też mały program testowy napisany w WPF




Kompletny przykład jest dostępny tutaj WFWebService.zip

Windows Workflow Foundation - State machine - wykorzystanie do kontroli interfejsu użytkownika

W WF mamy do dyspozycji dwa typy diagramów: sekwencyjny i maszyna stanów. Maszyna stanu umożliwia definiowanie akcji określających przechodzenie między poszczególnymi stanami. Każdy stan składa się z następujących bloków :
- działanie inicjujące stan
- lista zdarzeń które mogą być wykonane w tym stanie
- działanie które jest wykonywane przy wyjściu z danego stanu



Każda obsługa zdarzeń jest to sekwencyjny diagram który realizuje przepływ. Pierwszym elementem który musi być w tym diagramie jest to element który ma zaimplementowany interfejs IEventActivity. W WF są dostępne trzy akcje które go mają , są to: Delay, HandleExternalEvent oraz WebServiceInput. Dla naszego przykładu zrobiłem taki diagram stanu



Mamy dwie metody na obsłużenie kontrolek na formatce ze względu na obecny stan diagramu
1) Wykorzystujemy kolejkę akcji danego stanu. W przybliżeniu każdy stan posiada kolejkę działań które może wykonać. Możemy ją odczytać poprzez użycie metody GetWorkflowQueueData z klasy WorkflowInstance. Zwraca jest nam lista zadań czekający w kolejce. Rzutując właściwość QueueName na klasę EventQueueName możemy pobrać te akcje które czekają na poszczególne zdarzenia.

ReadOnlyCollection<WorkflowQueueInfo> queues = stateMachineInstance.WorkflowInstance.GetWorkflowQueueData();

Collection<string> MessagesAllowed = new Collection<string>();

foreach (WorkflowQueueInfo s in queues)
{
EventQueueName eventQueueName = s.QueueName as EventQueueName;
if (eventQueueName != null)
{
MessagesAllowed.Add(eventQueueName.MethodName);
}
}


A tak wygląda diagram który obsługuje konkretne zdarzenie



Tylko te akcje które wykorzystują kontrolkę HandleExternalEvent przekazują nazwę zdarzenia w
eventQueueName.MethodName. Po tym możemy włączyć lub wyłączyć elementy interfejsu na formatce np.

if (MessagesAllowed.Contains("ButtonPlayPressed"))
this.StartButton.IsEnabled = true;

if (MessagesAllowed.Contains("ButtonPausePressed"))
this.PauseButton.IsEnabled = true;

if (MessagesAllowed.Contains("ButtonStopPressed"))
this.StopButton.IsEnabled = true;

2) Ta metoda opiera się na akcji którą możemy wykonać przy wejściu do danego stanu.
Można tam uruchomić dowolną metodę np z parametrem określający jak jest aktualny stan diagramu i w tym momencie uruchomić jakieś zdarzenie które będzie zaimplementowane w serwisie. Formatka może obsługiwać te zdarzenie i ustawić odpowiednio kontrolki.




Która metoda lepsza? Pierwsza daje możliwość ustawienia elementów interfejsu od oczekujących zdarzeń i tak naprawdę nie jest ściśle powiązania z konkretnym stanem. Druga metoda wymaga zaś przy każdym stanie był kod powiadomi aplikacji w jakim jest stanie diagram aby aplikacja mogła ustawić odpowiednio swoje kontrolki.

Przykład który wykorzystuje obie metody do odpowiedniego przełączania kontrolek jest dostępny tutaj StateMachineUI.zip

.NET Framework Rootkits

Ostatnio natknąłem się na dość ciekawy artykuł opisujący możliwość ataku na aplikacje napisane w .NET link :
http://www.applicationsecurity.co.il/english/NETFrameworkRootkits/tabid/161/Default.aspx

Jedna rzecz przykuła mi uwagę. Wiadomo że zestawy są identyfikowane przez silne nazwę. Generuje się ją m.in poprzez użycie prywatnych kluczy. Po wstępnej lekturze zawartego tam artykułu można dowiedzieć o pewnej "słabości" mechanizmu sprawdzania podpisu , mianowicie jeśli uda się umieścić w odpowiednim folderze GAC nowy zestaw który będzie repliką istniejącego zestawu (czyli np poprzez zdekompilowanie, z dołożonym nowym kodem tu czy tam i ponownie skompilowanym z własnym kluczem prywatnym) i umieści się w odpowiednim folderze GAC to mechanizm sprawdzający silną nazwę nie sprawdza tego w wgrywanym zestawie !. Wystarczy mu odpowiednia nazwa folderu w którym jest położonym ten zestaw :]. Oczywiście aby to zrobić trzeba mieć uprawnienia administracyjne...
Praktyczny sposób wykorzystania ?
Proszę bardzo :).Spora cześć aplikacji korzysta z metody FormsAuthentication.HashPasswordForStoringInConfigFile
aby wygenerować np skrót funkcji MD5 i przesyła zakodowane hasło dalej powiedzmy do bazy. Dokładając na początek tej metody kod do przechwytywania argumentów, można zgromadzić spory zbiór haseł...
Pobawię się tym i zobaczę jak to się ma w praktyce.

Game of life

Napisałem sobie w Silverlight 3 prosty automat komórkowy i opublikowałem jego kod na codeplex.com.

Link do projektu : http://gameoflife.codeplex.com/


Projekt wymaga trochę rozbudowania np poprzez obsłużenie innych "klocków" jak trójkąty czy sześciany. Jest zastosowany prosty (opisany wcześniejszym poście) acz łatwy w użyciu mechanizm do lokalizowania elementów interfejsu oraz komunikatów programu.

Inne podejście w IDE

Znalazłem filmik pokazujący pewną zmianę w środowisku edytorów dla programistów. Zapowiada się dość ciekawe podejście




Oryginalny tekst opisujący działanie można znaleźć tutaj

Silverlight i lokalizowanie interfejsu graficznego

Po przeszukaniu kilku stron na temat lokalizowania w Silverlight mam pewną krótką ale dość skuteczną metodę na ten problem. Tworzymy sobie zasoby zawierające tekst oraz zlokalizowane zasoby z identyfikatorami kultur które obsługujemy



Ustawiamy opcjach edytora zasobów dla zasobu którego będziemy się odwoływać (w tym przypadku dla pliku ApplicationStrings.resx ) pole Access Modifier na Public w pozostałych plikach dla poszczególnych kultur na No code generation ponieważ nie jest nam potrzebny ich wygenerowany kod (menadżer zasobów wgrywa odpowiedni plik dla wybranej kultury)

Stworzyłem sobie klasę która zawiera zasób z łańcuchami oraz ma zaimplementowany interfejs INotifyPropertyChanged który będzie informował o zmianach jakie zajdą w przypadku zmiany kultury


using System.ComponentModel;

public class LocalizationString : INotifyPropertyChanged
{
#region Fields
private static GameOfLife.Assets.Resources.ApplicationStrings _strings = new GameOfLife.Assets.Resources.ApplicationStrings();
#endregion Fields
#region Events
public event PropertyChangedEventHandler PropertyChanged;
#endregion Events
#region Properties
public GameOfLife.Assets.Resources.ApplicationStrings Strings
{
get
{
return _strings;
}
set
{
_strings = value;
OnPropertyChanged("Strings");
}
}
#endregion Properties
#region Methods
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion Methods
}

Następnie umieszczamy tą klase jako zasób aplikacji aby była dostępna dla całej aplikacji

<ResourceDictionary>
<res:LocalizationString x:Key="LocalizationString" />
</ResourceDictionary>

Następnie podpinamy poprzez wyrażenie bindowania kontrolkach:

<Button x:Name="PrepareButton" Content="{Binding Path=Strings.PrepareButton, Source={StaticResource LocalizationString}}" />

Co daje nam taka konstrukcja ? Ano daje nam to że zmieniając właściwość Strings w klasie LocalizationString wszystkie kontrolki mające odwołanie do tej właściwości zostaną automatycznie zaktualizowane i wyświetlą tekst zgodnie z wybraną kulturą.

Przykładowy kod który uruchomi ten mechanizm to :

LocalizationString localizationString = Application.Current.Resources["LocalizationString"] as LocalizationString;
if (localizationString != null)
{
CultureInfo newCulture = new CultureInfo(lang);
Thread.CurrentThread.CurrentCulture = newCulture;
Thread.CurrentThread.CurrentUICulture = newCulture;
ApplicationStrings.Culture = newCulture;
localizationString.Strings = new GameOfLife.Assets.Resources.ApplicationStrings();
}

Silverlight i wątki

Swoją przygodę z Silverlight zacząłem poprze napisanie małego programu który później opublikuje na codeplex.com. Miałem tam pewny problem, mianowicie z poziomu wątku musiałem zaktualizować stan kontrolek. W pierwszym podejściu korzystałem klasy BackgroundWorker i z niego poprzez właściwość kontrolki Dispatcher wykonywałem metodę BeginInvoke w której przekazywałem kod do aktualizacji. Jednak to się okazała mało skuteczne, więcej kod do obsługi wątków w Silverlight został trochę uszczuplony więc nie można było poczekać na zakończenie wykonywania kodu przekazanego w BeginInvoke.

Rozwiązaniem okazało się użycie klasy DispatcherTimer który działa tak jak klasa Dispatcher w wątku interfejsu UI więc można było odrazu aktualizować stan kontrolki. Z tym że DispatcherTimer umożliwia wykonywania podanego kodu cyklicznie.