MVVM - Dependency Injection

In questo capitolo, discuteremo brevemente sull'iniezione di dipendenze. Abbiamo già trattato l'associazione dei dati che separa le viste e i ViewModels l'una dall'altra consentendo loro di comunicare senza sapere esplicitamente cosa sta succedendo all'altra estremità della comunicazione.

Ora abbiamo bisogno di qualcosa di simile per separare il nostro ViewModel dai servizi client.

Agli albori della programmazione orientata agli oggetti, gli sviluppatori hanno dovuto affrontare il problema della creazione e del recupero di istanze di classi nelle applicazioni. Diverse soluzioni sono state proposte per questo problema.

Negli ultimi anni, l'inserimento delle dipendenze e l'inversione del controllo (IoC) hanno guadagnato popolarità tra gli sviluppatori e hanno avuto la precedenza su alcune soluzioni precedenti come il pattern Singleton.

Iniezione di dipendenze / contenitori IoC

IoC e l'inserimento delle dipendenze sono due modelli di progettazione strettamente correlati e il contenitore è fondamentalmente un pezzo di codice dell'infrastruttura che esegue entrambi questi modelli per te.

  • Il modello IoC riguarda la delega della responsabilità per la costruzione e il modello di inserimento delle dipendenze riguarda la fornitura di dipendenze a un oggetto che è già stato costruito.

  • Entrambi possono essere trattati come un approccio in due fasi alla costruzione. Quando si utilizza un contenitore, il contenitore assume diverse responsabilità che sono le seguenti:

    • Costruisce un oggetto quando richiesto.
    • Il contenitore determinerà da cosa dipende quell'oggetto.
    • Costruire quelle dipendenze.
    • Iniettandoli nell'oggetto in costruzione.
    • Processo ricorsivo.

Diamo un'occhiata a come possiamo usare l'inserimento delle dipendenze per interrompere il disaccoppiamento tra ViewModels e i servizi client. Collegheremo il modulo AddEditCustomerViewModel per la gestione del salvataggio utilizzando l'inserimento delle dipendenze correlato a quello.

Per prima cosa dobbiamo creare una nuova interfaccia nel nostro progetto nella cartella Servizi. Se non hai una cartella dei servizi nel tuo progetto, creala prima e aggiungi la seguente interfaccia nella cartella Servizi.

using MVVMHierarchiesDemo.Model; 

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Threading.Tasks;

namespace MVVMHierarchiesDemo.Services { 

   public interface ICustomersRepository { 
      Task<List<Customer>> GetCustomersAsync(); 
      Task<Customer> GetCustomerAsync(Guid id); 
      Task<Customer> AddCustomerAsync(Customer customer); 
      Task<Customer> UpdateCustomerAsync(Customer customer); 
      Task DeleteCustomerAsync(Guid customerId); 
   } 
}

Di seguito è riportata l'implementazione di ICustomersRepository.

using MVVMHierarchiesDemo.Model; 

using System; 
using System.Collections.Generic; 
using System.Linq; using System.Text; 
using System.Threading.Tasks;

namespace MVVMHierarchiesDemo.Services { 

   public class CustomersRepository : ICustomersRepository {
      ZzaDbContext _context = new ZzaDbContext();

      public Task<List<Customer>> GetCustomersAsync() { 
         return _context.Customers.ToListAsync(); 
      }

      public Task<Customer> GetCustomerAsync(Guid id) { 
         return _context.Customers.FirstOrDefaultAsync(c => c.Id == id); 
      }
		
      public async Task<Customer> AddCustomerAsync(Customer customer){ 
         _context.Customers.Add(customer); 
         await _context.SaveChangesAsync(); 
         return customer;
      }

      public async Task<Customer> UpdateCustomerAsync(Customer customer) {
		
         if (!_context.Customers.Local.Any(c => c.Id == customer.Id)) { 
            _context.Customers.Attach(customer); 
         } 
			
         _context.Entry(customer).State = EntityState.Modified;
         await _context.SaveChangesAsync(); 
         return customer;
			
      }

      public async Task DeleteCustomerAsync(Guid customerId) {
         var customer = _context.Customers.FirstOrDefault(c => c.Id == customerId); 
			
         if (customer != null) {
            _context.Customers.Remove(customer); 
         }
			
         await _context.SaveChangesAsync(); 
      } 
   } 
}

Il modo semplice per eseguire la gestione del salvataggio è aggiungere una nuova istanza di ICustomersRepository in AddEditCustomerViewModel e sovraccaricare il costruttore AddEditCustomerViewModel e CustomerListViewModel.

private ICustomersRepository _repo; 

public AddEditCustomerViewModel(ICustomersRepository repo) { 
   _repo = repo; 
   CancelCommand = new MyIcommand(OnCancel);
   SaveCommand = new MyIcommand(OnSave, CanSave); 
}

Aggiorna il metodo OnSave come mostrato nel codice seguente.

private async void OnSave() { 
   UpdateCustomer(Customer, _editingCustomer); 
	
   if (EditMode) 
      await _repo.UpdateCustomerAsync(_editingCustomer); 
   else 
      await _repo.AddCustomerAsync(_editingCustomer); 
   Done(); 
} 

private void UpdateCustomer(SimpleEditableCustomer source, Customer target) { 
   target.FirstName = source.FirstName; 
   target.LastName = source.LastName; 
   target.Phone = source.Phone; 
   target.Email = source.Email; 
}

Di seguito è riportato il AddEditCustomerViewModel completo.

using MVVMHierarchiesDemo.Model; 
using MVVMHierarchiesDemo.Services; 

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text;
using System.Threading.Tasks;

namespace MVVMHierarchiesDemo.ViewModel { 

   class AddEditCustomerViewModel : BindableBase { 
      private ICustomersRepository _repo; 
		
      public AddEditCustomerViewModel(ICustomersRepository repo) { 
         _repo = repo;
         CancelCommand = new MyIcommand(OnCancel); 
         SaveCommand = new MyIcommand(OnSave, CanSave); 
      } 
		
      private bool _EditMode; 
		
      public bool EditMode { 
         get { return _EditMode; } 
         set { SetProperty(ref _EditMode, value); } 
      }

      private SimpleEditableCustomer _Customer; 
		
      public SimpleEditableCustomer Customer { 
         get { return _Customer; } 
         set { SetProperty(ref _Customer, value); } 
      }
		
      private Customer _editingCustomer = null;

      public void SetCustomer(Customer cust) { 
         _editingCustomer = cust; 
			
         if (Customer != null) Customer.ErrorsChanged -= RaiseCanExecuteChanged; 
         Customer = new SimpleEditableCustomer();
         Customer.ErrorsChanged += RaiseCanExecuteChanged;
         CopyCustomer(cust, Customer); 
      }

      private void RaiseCanExecuteChanged(object sender, EventArgs e) { 
         SaveCommand.RaiseCanExecuteChanged(); 
      }

      public MyIcommand CancelCommand { get; private set; } 
      public MyIcommand SaveCommand { get; private set; }

      public event Action Done = delegate { };
		
      private void OnCancel() { 
         Done(); 
      }

      private async void OnSave() { 
         UpdateCustomer(Customer, _editingCustomer); 
			
         if (EditMode) 
            await _repo.UpdateCustomerAsync(_editingCustomer); 
         else 
            await _repo.AddCustomerAsync(_editingCustomer); 
         Done(); 
      }

      private void UpdateCustomer(SimpleEditableCustomer source, Customer target) { 
         target.FirstName = source.FirstName; 
         target.LastName = source.LastName; 
         target.Phone = source.Phone; 
         target.Email = source.Email; 
      }

      private bool CanSave() { 
         return !Customer.HasErrors; 
      }
		
      private void CopyCustomer(Customer source, SimpleEditableCustomer target) { 
         target.Id = source.Id; 
			
         if (EditMode) { 
            target.FirstName = source.FirstName; 
            target.LastName = source.LastName; 
            target.Phone = source.Phone; 
            target.Email = source.Email; 
         }
      } 
   } 
}

Quando il codice precedente viene compilato ed eseguito, vedrai lo stesso output ma ora i ViewModels sono disaccoppiati in modo più lasco.

Quando premi il pulsante Aggiungi cliente, vedrai la seguente vista. Quando l'utente lascia un campo vuoto, questo verrà evidenziato e il pulsante di salvataggio verrà disabilitato.