MVVM - Abhängigkeitsinjektion

In diesem Kapitel werden wir kurz auf die Abhängigkeitsinjektion eingehen. Wir haben bereits behandelt, wie Datenbindungen Views und ViewModels voneinander entkoppeln, sodass sie kommunizieren können, ohne explizit zu wissen, was am anderen Ende der Kommunikation vor sich geht.

Jetzt brauchen wir etwas Ähnliches, um unser ViewModel vom Client-Service zu entkoppeln.

In den frühen Tagen der objektorientierten Programmierung hatten Entwickler das Problem, Instanzen von Klassen in Anwendungen zu erstellen und abzurufen. Für dieses Problem wurden verschiedene Lösungen vorgeschlagen.

In den letzten Jahren haben Abhängigkeitsinjektion und Inversion of Control (IoC) bei Entwicklern an Popularität gewonnen und Vorrang vor einigen älteren Lösungen wie dem Singleton-Muster.

Abhängigkeitsinjektion / IoC-Container

IoC und Abhängigkeitsinjektion sind zwei Entwurfsmuster, die eng miteinander verbunden sind, und der Container ist im Grunde ein Teil des Infrastrukturcodes, der beide Muster für Sie erledigt.

  • Beim IoC-Muster geht es darum, die Verantwortung für die Erstellung zu delegieren, und beim Abhängigkeitsinjektionsmuster geht es darum, Abhängigkeiten für ein Objekt bereitzustellen, das bereits erstellt wurde.

  • Sie können beide als zweiphasiger Konstruktionsansatz behandelt werden. Wenn Sie einen Container verwenden, übernimmt der Container verschiedene Aufgaben:

    • Auf Anfrage wird ein Objekt erstellt.
    • Der Container bestimmt, wovon das Objekt abhängt.
    • Aufbau dieser Abhängigkeiten.
    • Injizieren Sie sie in das zu konstruierende Objekt.
    • Prozess rekursiv ausführen.

Lassen Sie uns einen Blick darauf werfen, wie wir mithilfe der Abhängigkeitsinjektion die Entkopplung zwischen ViewModels und den Clientdiensten aufheben können. Wir werden das AddEditCustomerViewModel-Formular für die Speicherbehandlung mithilfe der dazugehörigen Abhängigkeitsinjektion verkabeln.

Zuerst müssen wir eine neue Schnittstelle in unserem Projekt im Ordner "Dienste" erstellen. Wenn Ihr Projekt keinen Dienstordner enthält, erstellen Sie ihn zuerst und fügen Sie die folgende Schnittstelle in den Dienstordner ein.

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

Es folgt die Implementierung von 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(); 
      } 
   } 
}

Die einfache Möglichkeit, die Behandlung zu speichern, besteht darin, eine neue Instanz von ICustomersRepository in AddEditCustomerViewModel hinzuzufügen und den Konstruktor AddEditCustomerViewModel und CustomerListViewModel zu überladen.

private ICustomersRepository _repo; 

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

Aktualisieren Sie die OnSave-Methode wie im folgenden Code gezeigt.

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

Es folgt das vollständige AddEditCustomerViewModel.

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

Wenn der obige Code kompiliert und ausgeführt wird, wird dieselbe Ausgabe angezeigt, aber jetzt sind ViewModels lockerer entkoppelt.

Wenn Sie auf die Schaltfläche Kunde hinzufügen klicken, wird die folgende Ansicht angezeigt. Wenn der Benutzer ein Feld leer lässt, wird es hervorgehoben und die Schaltfläche Speichern deaktiviert.