Una delle best practice nello sviluppo di applicazioni con il pattern MVC è denominata "thick ViewModels, thin Controllers" e, in buona sostanza, separa le responsabilità di controller e view model nella gestione della response. Immaginiamo per esempio di voler modificare un Customer; in questo scenario:
- il view model implementa la logica di business, deve istanziare il repository, recuperare il dato esistente, apportare le modifiche e procedere al salvataggio;
- il controller, e in particolare la action, deve invece preoccuparsi del flusso della richiesta, restituendo la view di errore nel caso il salvataggio non vada a buon fine o effettuando il redirect verso l'elenco dei customer se tutto è andato bene.
Si tratta di un approccio un po' differente rispetto a quanto, per esempio, troviamo comunemente nella maggior parte degli esempi e delle demo in rete, inclusi i template di default di ASP.NET MVC, ma è senza dubbio il modo più efficace per gestire il layer di UI quando le applicazioni diventano complesse.
Abbiamo già visto in passato (https://www.aspitalia.com/script/1139/Dependency-Injection-ASP.NET-MVC-Ninject.aspx) come, sfruttando Ninject, possiamo iniettare automaticamente le dipendenze all'interno di un controller. Se però la nostra logica di business risiede in massima parte nei view model, è in questi oggetti che avremo bisogno di servizi e repository, invece che nei controller.
Il nostro view model, quindi, finirà per avere un'implementazione di massima simile alla seguente, in cui nel costruttore ci aspettiamo di ottenere la reference al repository, mentre esponiamo un metodo Save che contiene tutta la logica per il salvataggio:
public class EditCustomerViewModel { private IRepository<Customer> _customers; public EditCustomerViewModel(IRepository<Customer> customers) { _customers = customers; } public void Save() { var customer = _customers.Get(this.Id); // .... altro codice qui .... } }
La action di modifica del customer, invece, si limiterà a invocare il metodo Save e a gestire il risultato da reinviare al browser:
[HttpPost] public ActionResult Edit(int id, EditCustomerViewModel model) { if (this.ModelState.IsValid) { model.Save(); return this.RedirectToAction("Index"); } else { return this.View(model); } }
Se proviamo a mettere in piedi questa soluzione, però, otterremo un errore, perché ASP.NET, e in particolare il DefaultModelBinder, richiede che il view model abbia un costruttore senza parametri.
Per risolvere il problema, è necessario sfruttare Ninject anche per la creazione di questi oggetti. Il package Ninject.MVC5 non ha una soluzione built-in per questo, ma con pochissime righe di codice possiamo costruire un nostro NinjectModelBinder adatto allo scopo:
public class NinjectModelBinder : DefaultModelBinder { private IKernel _kernel; public NinjectModelBinder(IKernel kernel) { _kernel = kernel; } protected override object CreateModel( ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType) { return _kernel.Get(modelType); } }
Come ultimo passo, non ci resta che registrarlo, per esempio in NinjectWebCommon.cs:
private static void RegisterServices(IKernel kernel) { ModelBinders.Binders.DefaultBinder = kernel.Get<NinjectModelBinder>(); }
Commenti
Per inserire un commento, devi avere un account.
Fai il login e torna a questa pagina, oppure registrati alla nostra community.
Approfondimenti
Utilizzare Model as a Service su Microsoft Azure
Creare alias per tipi generici e tuple in C#
Generare file PDF da Blazor WebAssembly con iText
Effettuare il deploy di immagini solo da container registry approvati in Kubernetes
Come EF 8 ha ottimizzato le query che usano il metodo Contains
Personalizzare l'errore del rate limiting middleware in ASP.NET Core
Ottimizzazione dei block template in Angular 17
Visualizzare le change sul plan di Terraform tramite le GitHub Actions
Ottimizzare la latenza in Blazor 8 tramite InteractiveAuto render mode
Le novità di Angular: i miglioramenti alla CLI
Limitare le richieste lato server con l'interactive routing di Blazor 8
Definire stili a livello di libreria in Angular