MVVM: inyección de dependencia

En este capítulo, analizaremos brevemente la inyección de dependencia. Ya hemos cubierto la vinculación de datos que desacopla las vistas y los modelos de vista entre sí, lo que les permite comunicarse sin saber explícitamente qué está sucediendo en el otro extremo de la comunicación.

Ahora necesitamos algo similar para desacoplar nuestro ViewModel de los servicios al cliente.

En los primeros días de la programación orientada a objetos, los desarrolladores se han enfrentado al problema de crear y recuperar instancias de clases en aplicaciones. Se han propuesto varias soluciones para este problema.

Durante los últimos años, la inyección de dependencia y la inversión de control (IoC) han ganado popularidad entre los desarrolladores y han tenido prioridad sobre algunas soluciones más antiguas, como el patrón Singleton.

Inyección de dependencias / Contenedores de IoC

IoC y la inyección de dependencia son dos patrones de diseño que están estrechamente relacionados y el contenedor es básicamente un fragmento de código de infraestructura que realiza ambos patrones por usted.

  • El patrón de IoC se trata de delegar la responsabilidad de la construcción y el patrón de inyección de dependencia se trata de proporcionar dependencias a un objeto que ya se ha construido.

  • Ambos pueden tratarse como un enfoque de construcción de dos fases. Cuando usa un contenedor, el contenedor asume varias responsabilidades que son las siguientes:

    • Construye un objeto cuando se le pregunta.
    • El contenedor determinará de qué depende ese objeto.
    • Construyendo esas dependencias.
    • Inyectándolos en el objeto que se está construyendo.
    • Hacer proceso de forma recursiva.

Echemos un vistazo a cómo podemos usar la inyección de dependencia para romper el desacoplamiento entre ViewModels y los servicios del cliente. Conectaremos el formulario AddEditCustomerViewModel de manejo de guardado utilizando la inyección de dependencia relacionada con eso.

Primero necesitamos crear una nueva interfaz en nuestro proyecto en la carpeta Servicios. Si no tiene una carpeta de servicios en su proyecto, primero créela y agregue la siguiente interfaz en la carpeta Servicios.

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

A continuación se muestra la implementación 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(); 
      } 
   } 
}

La forma más sencilla de realizar el manejo de guardado es agregar una nueva instancia de ICustomersRepository en AddEditCustomerViewModel y sobrecargar el constructor AddEditCustomerViewModel y CustomerListViewModel.

private ICustomersRepository _repo; 

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

Actualice el método OnSave como se muestra en el siguiente código.

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

A continuación se muestra el 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; 
         }
      } 
   } 
}

Cuando se compila y ejecuta el código anterior, verá el mismo resultado, pero ahora los ViewModels están más desacoplados.

Cuando presione el botón Agregar cliente, verá la siguiente vista. Cuando el usuario deja un campo vacío, se resaltará y el botón de guardar se desactivará.