ASP MVC 3 jest w dużej części spoko - znajdą się elementy bardzo irytujące, ale ogólnie mogę powiedzieć że jestem z pracy z tym frameworkiem raczej zadowolony. Denerwuje mnie jednak to, że pracując nad jedną daną akcją w jakimś kontrolerze muszę śmigać po kilku plikach:
- plik kontrolera
- plik z routingiem
- plik z modelem parametru akcji
- plik z modelem zwracanym przez akcję
- plik z mapowaniami AutoMappera
- plik widoku .cshtml
- plik skryptów .js
- ... o czymś zapomniałem?
Jakiś czas temu postanowiłem wypróbować alternatywne podejście do organizacji kodu w swoim projekcie webowym...
[Przyszło mi ono do głowy zimą podczas przeglądania materiałów dotyczących frameworka FubuMVC, ale właściwie momentalnie zarzuciłem zapoznawanie się z tym rozwiązaniem, więc nie jestem w stanie na dzień dzisiejszy powiedzieć, jak poniższe podejście i praktyki Fubu "ze sobą korespondują";)... co z kolei teraz staram się nadrabiać, eksperymentując ponownie z Fubu]
Najlepiej zobrazuje to przykład, żeby było wiadomo o co mi CHO. Poniżej przedstawiam kawałek kodu odpowiedzialny za akcję "administrator dodaje notatkę do firmy":
1: public partial class CompaniesManagementController
2: {
3: [HttpPost]
4: public virtual ActionResult SaveNote(SaveNoteModel model)
5: {
6: var company = _session.Load<Company>(model.CompanyId);
7:
8: Note newNote = model.Map<Note>();
9:
10: company.AddNote(newNote);
11:
12: return Json(new SaveNoteResponse()
13: {
14: SavedSuccessfully = true,
15: Message = MessagesContent.CompanyNote_SaveSuccess
16: });
17: }
18:
19: public class SaveNoteModel
20: {
21: public int CompanyId { get; set; }
22:
23: [Required]
24: [DisplayName("Notatka")]
25: public string Content { get; set; }
26: }
27:
28: public class SaveNoteResponse
29: {
30: public bool SavedSuccessfully { get; set; }
31: public string Message { get; set; }
32: }
33:
34: public class Routes : IRouteProvider
35: {
36: public void RegisterRoutes(RouteCollection routes)
37: {
38: routes.MapRoute(
39: "Admin_SaveNote",
40: "Admin/ZapiszNotatke",
41: MVC.Administration.CompaniesManagement.SaveNote()
42: );
43: }
44: }
45:
46: public class Mappings : IMappingProvider
47: {
48: public void RegisterMap()
49: {
50: AutoMapper.Mapper.CreateMap<SaveNoteModel, Note>();
51: }
52: }
53: }
Cóż tu widzimy? Jeden plik zawierający prawie wszystko, co jest potrzebne do oprogramowania jednej akcji. Kontroler jest partial - bo w innych plikach (które nazywam CompaniesManagement_SaveNote.cs, CompaniesManagement_DeleteCompany.cs etc...) piszę inne akcje, będące częściami tego samego kontrolera. Modele są zagnieżdżone - więc nigdy nie nastąpi żaden konflikt nazw z innymi mikroskopijnymi klaskami rozsianymi po projekcie. Tak samo definicje routingu oraz mapowań - każda akcja sama sobie definiuje pod jakim URLem chce się znajdować i w jaki sposób skorzystać z AutoMappera.
Dostrzegłem bowiem, że tak naprawdę NIC mi nie daje trzymanie modeli w jednym katalogu - bo nigdy nie potrzebuję dostępu do więcej niż jednego naraz. Tylko powoduje bałagan i zamieszanie podczas nawigacji. To samo z mapowaniami - i tak każda akcja ma własny model (lub dwa), więc i mapowania są wyłącznie dla niej charakterystyczne. A routing... No tutaj wiele już zależy od specyfiki danego systemu, ale generalnie podoba mi się swoboda, jaką daje przedstawione rozwiązanie: hierarchia URLi nie musi wcale pokrywać się z organizacją kontrolerów w "aree" oraz grupowanie akcji w kontrolery.
Wszystkie zależności wymagane przez wszystkie akcje kontrolera znajdują się w jednym konstruktorze, zdefiniowanym w głównym pliku CompaniesManagement.cs, który nie zawiera żadnych akcji. Zadaniem tego pliku jest przyjęcie zależności, dziedziczenie z odpowiedniej klasy bazowej oraz nałożenie atrybutów wspólnych dla wszystkich akcji (jak [Authorize]).
Okazało się, że taka organizacja kodu w projekcie webowym znacznie ułatwia mi pracę. Początkowo podchodziłem do tego bardzo sceptycznie, ale... po jakimś czasie wszystkie nowe funkcjonalności powstawały właśnie w ten sposób.
Zastanawiałem się czy nie pójść o krok dalej i nie "scustomizować" zachowania MVC tak, aby jeszcze bardziej wyeksponować akcję jako główny element rozwiązania webowego. Chodzi o to, aby każda akcja otrzymała osobny katalog, a w nim zarówno przedstawiony wyżej kod C#, jak i widok cshtml. Ale tego jeszcze nie wypróbowałem.
Co o tym myślicie? Tak jak napisałem - w moim przypadku takie coś naprawdę się sprawdza. Pliczki z akcjami są niewielkie i łatwo się po nich poruszać, a mają wszystko co potrzeba.
P.S.: Proszę po powyższym kodzie zbytnio nie jechać - ma on służyć jedynie demonstracji mojej koncepcji i nie jest żywcem wzięty z żadnego systemu.