MVVM - injeção de dependência
Neste capítulo, discutiremos brevemente sobre injeção de dependência. Já cobrimos a vinculação de dados que separa Views e ViewModels uns dos outros, o que permite que eles se comuniquem sem saber explicitamente o que está acontecendo na outra extremidade da comunicação.
Agora precisamos de algo semelhante para desacoplar nosso ViewModel dos serviços do cliente.
Nos primeiros dias da programação orientada a objetos, os desenvolvedores enfrentaram o problema de criar e recuperar instâncias de classes em aplicativos. Várias soluções têm sido propostas para este problema.
Nos últimos anos, injeção de dependência e inversão de controle (IoC) ganharam popularidade entre os desenvolvedores e têm precedência sobre algumas soluções mais antigas, como o padrão Singleton.
Dependency Injection / IoC Containers
IoC e injeção de dependência são dois padrões de design intimamente relacionados e o contêiner é basicamente um pedaço de código de infraestrutura que faz os dois padrões para você.
O padrão IoC é sobre delegar responsabilidade pela construção e o padrão de injeção de dependência é sobre fornecer dependências para um objeto que já foi construído.
Ambos podem ser tratados como uma abordagem de construção em duas fases. Quando você usa um contêiner, o contêiner assume várias responsabilidades, que são as seguintes -
- Ele constrói um objeto quando solicitado.
- O contêiner determinará do que esse objeto depende.
- Construindo essas dependências.
- Injetando-os no objeto sendo construído.
- Fazendo processo recursivamente.
Vamos dar uma olhada em como podemos usar injeção de dependência para quebrar o desacoplamento entre ViewModels e os serviços do cliente. Iremos conectar o formulário AddEditCustomerViewModel de manipulação de salvamento usando injeção de dependência relacionada a isso.
Primeiro, precisamos criar uma nova interface em nosso projeto na pasta Services. Se você não tiver uma pasta de serviços em seu projeto, crie-a primeiro e adicione a seguinte interface na pasta Serviços.
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 seguir está a implementação 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();
}
}
}
A maneira simples de fazer o tratamento de Salvar é adicionar uma nova instância de ICustomersRepository em AddEditCustomerViewModel e sobrecarregar o construtor AddEditCustomerViewModel e CustomerListViewModel.
private ICustomersRepository _repo;
public AddEditCustomerViewModel(ICustomersRepository repo) {
_repo = repo;
CancelCommand = new MyIcommand(OnCancel);
SaveCommand = new MyIcommand(OnSave, CanSave);
}
Atualize o método OnSave conforme mostrado no código a seguir.
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 seguir está o 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 o código acima for compilado e executado, você verá a mesma saída, mas agora os ViewModels são desacoplados de maneira mais flexível.
Ao pressionar o botão Adicionar cliente, você verá a seguinte exibição. Quando o usuário deixar algum campo vazio, ele ficará destacado e o botão Salvar ficará desabilitado.