MVVM - Injection de dépendances

Dans ce chapitre, nous discuterons brièvement de l'injection de dépendances. Nous avons déjà couvert la liaison de données qui découpe les vues et les ViewModels les uns des autres, ce qui leur permet de communiquer sans savoir explicitement ce qui se passe à l'autre extrémité de la communication.

Nous avons maintenant besoin de quelque chose de similaire pour découpler notre ViewModel des services client.

Au début de la programmation orientée objet, les développeurs ont été confrontés au problème de la création et de la récupération d'instances de classes dans les applications. Différentes solutions ont été proposées à ce problème.

Au cours des dernières années, l'injection de dépendances et l'inversion de contrôle (IoC) ont gagné en popularité parmi les développeurs et ont pris le pas sur certaines solutions plus anciennes telles que le modèle Singleton.

Injection de dépendances / conteneurs IoC

L'IoC et l'injection de dépendances sont deux modèles de conception étroitement liés et le conteneur est essentiellement un morceau de code d'infrastructure qui exécute ces deux modèles pour vous.

  • Le modèle IoC consiste à déléguer la responsabilité de la construction et le modèle d'injection de dépendances consiste à fournir des dépendances à un objet qui a déjà été construit.

  • Ils peuvent tous deux être traités comme une approche de construction en deux phases. Lorsque vous utilisez un conteneur, le conteneur assume plusieurs responsabilités qui sont les suivantes -

    • Il construit un objet lorsqu'il est demandé.
    • Le conteneur déterminera de quoi dépend cet objet.
    • Construire ces dépendances.
    • Les injecter dans l'objet en cours de construction.
    • Processus récursif.

Voyons comment nous pouvons utiliser l'injection de dépendances pour rompre le découplage entre ViewModels et les services client. Nous allons câbler le formulaire AddEditCustomerViewModel de gestion de sauvegarde en utilisant l'injection de dépendance liée à cela.

Nous devons d'abord créer une nouvelle interface dans notre projet dans le dossier Services. Si vous n'avez pas de dossier de services dans votre projet, créez-le d'abord et ajoutez l'interface suivante dans le dossier Services.

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); 
   } 
}

Voici l'implémentation de 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(); 
      } 
   } 
}

Le moyen simple de gérer la sauvegarde consiste à ajouter une nouvelle instance de ICustomersRepository dans AddEditCustomerViewModel et à surcharger le constructeur AddEditCustomerViewModel et CustomerListViewModel.

private ICustomersRepository _repo; 

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

Mettez à jour la méthode OnSave comme indiqué dans le code suivant.

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; 
}

Voici le AddEditCustomerViewModel complet.

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; 
         }
      } 
   } 
}

Lorsque le code ci-dessus est compilé et exécuté, vous verrez la même sortie, mais les ViewModels sont maintenant plus lâchement découplés.

Lorsque vous appuyez sur le bouton Ajouter un client, vous verrez la vue suivante. Lorsque l'utilisateur laisse un champ vide, il sera mis en surbrillance et le bouton Enregistrer sera désactivé.