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.