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
Definire stili a livello di libreria in Angular
Sfruttare al massimo i topic space di Event Grid MQTT
Utilizzare Azure AI Studio per testare i modelli AI
Utilizzare database e servizi con gli add-on di Container App
Evitare la command injection in un workflow di GitHub
Miglioramenti agli screen reader e al contrasto in Angular
Come migrare da una form non tipizzata a una form tipizzata in Angular
Eseguire query manipolando le liste contenute in un oggetto mappato verso una colonna JSON
Gestire i null nelle reactive form tipizzate di Angular
Load test di ASP.NET Core con k6
Usare le variabili per personalizzare gli stili CSS
Utilizzare politiche di resiliency con Azure Container App