Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
A DI (injeção de dependência) ajuda você a gerenciar o ciclo de vida de seus ViewModels e serviços. Isso torna seu código mais testável e mais fácil de manter. Nesta etapa, você configura o DI em seu aplicativo e atualiza seus modelos para usar um serviço de arquivo para operações de arquivo.
Para obter mais informações sobre a estrutura de injeção de dependência do .NET, consulte injeção de dependência do .NET e o tutorial Use a injeção de dependência no .NET.
Instalar pacotes Microsoft.Extensions:
Adicione suporte de DI aos seus projetos.
Instale
Microsoft.Extensions.DependencyInjectionnos projetos WinUINotes e WinUINotes.Bus :dotnet add WinUINotes package Microsoft.Extensions.DependencyInjection dotnet add WinUINotes.Bus package Microsoft.Extensions.DependencyInjection
Criar uma interface e implementação do serviço de arquivo
No projeto WinUINotes.Bus , crie uma nova pasta chamada Serviços.
Adicionar um arquivo
IFileService.csde interface:using System.Collections.Generic; using System.Threading.Tasks; using Windows.Storage; namespace WinUINotes.Services { public interface IFileService { Task<IReadOnlyList<IStorageItem>> GetStorageItemsAsync(); Task<IReadOnlyList<IStorageItem>> GetStorageItemsAsync(IStorageFolder storageFolder); Task<string> GetTextFromFileAsync(IStorageFile file); Task CreateOrUpdateFileAsync(string filename, string contents); Task DeleteFileAsync(string filename); bool FileExists(string filename); IStorageFolder GetLocalFolder(); } }A interface do serviço de arquivo define métodos para operações de arquivo. Ele abstrai dos ViewModels e Modelos os detalhes do tratamento de arquivos. Os parâmetros e os valores retornados são todos tipos básicos do .NET ou interfaces. Esse design garante que o serviço possa ser facilmente ridicularizado ou substituído em testes de unidade, promovendo acoplamento flexível e testabilidade.
Adicione o arquivo
WindowsFileService.csde implementação:using System; using System.Collections.Generic; using System.Threading.Tasks; using Windows.Storage; namespace WinUINotes.Services { public class WindowsFileService : IFileService { public StorageFolder storageFolder; public WindowsFileService(IStorageFolder storageFolder) { this.storageFolder = (StorageFolder)storageFolder; if (this.storageFolder is null) { throw new ArgumentException("storageFolder must be of type StorageFolder", nameof(storageFolder)); } } public async Task CreateOrUpdateFileAsync(string filename, string contents) { // Save the note to a file. StorageFile storageFile = (StorageFile)await storageFolder.TryGetItemAsync(filename); if (storageFile is null) { storageFile = await storageFolder.CreateFileAsync(filename, CreationCollisionOption.ReplaceExisting); } await FileIO.WriteTextAsync(storageFile, contents); } public async Task DeleteFileAsync(string filename) { // Delete the note from the file system. StorageFile storageFile = (StorageFile)await storageFolder.TryGetItemAsync(filename); if (storageFile is not null) { await storageFile.DeleteAsync(); } } public bool FileExists(string filename) { StorageFile storageFile = (StorageFile)storageFolder.TryGetItemAsync(filename).AsTask().Result; return storageFile is not null; } public IStorageFolder GetLocalFolder() { return storageFolder; } public async Task<IReadOnlyList<IStorageItem>> GetStorageItemsAsync() { return await storageFolder.GetItemsAsync(); } public async Task<IReadOnlyList<IStorageItem>> GetStorageItemsAsync(IStorageFolder folder) { return await folder.GetItemsAsync(); } public async Task<string> GetTextFromFileAsync(IStorageFile file) { return await FileIO.ReadTextAsync(file); } } }
A WindowsFileService implementação fornece operações de arquivo concretas usando as APIs de armazenamento do WinRT (Windows Runtime) e do .NET:
-
Injeção de construtor: o serviço aceita um
IStorageFolderem seu construtor. Essa abordagem permite que você configure o local de armazenamento ao instanciar o serviço. Essa abordagem torna o serviço flexível e testável. -
CreateOrUpdateFileAsync(): esse método usaTryGetItemAsync()para verificar se já existe um arquivo. Se isso acontecer, o método atualizará o arquivo existente. Caso contrário, ele criará um novo arquivo usandoCreateFileAsync(). Essa abordagem manipula cenários de criação e atualização em um único método. -
DeleteFileAsync(): antes de excluir um arquivo, esse método verifica se o arquivo existe usandoTryGetItemAsync(). Essa verificação impede que exceções sejam geradas ao tentar excluir arquivos inexistentes. -
FileExists(): esse método síncrono verifica a existência do arquivo chamando o assíncronoTryGetItemAsync()e bloqueando com.Result. Embora essa abordagem geralmente não seja recomendada, ela é usada aqui para dar suporte aoCanDelete()método de validação no ViewModel, que deve ser síncrono. -
Métodos de item de armazenamento: os métodos
GetStorageItemsAsync()eGetTextFromFileAsync()fornecem acesso a arquivos e seu conteúdo usando as APIs de armazenamento do WinRT. Esses métodos permitem que os Modelos carreguem e enumerem anotações.
Ao implementar a IFileService interface, você pode facilmente substituir essa classe por uma implementação simulada para teste ou um provedor de armazenamento diferente, se necessário.
Saiba mais nos documentos:
Configurar a injeção de dependência no App.xaml.cs
Antes de atualizar os modelos e ViewModels para usar o serviço de arquivo, configure a injeção de dependência para que o serviço possa ser resolvido e injetado nos construtores.
Atualize o App.xaml.cs arquivo para configurar o contêiner de DI:
using System;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.UI.Xaml;
using WinUINotes.ViewModels;
namespace WinUINotes;
public partial class App : Application
{
private readonly IServiceProvider _serviceProvider;
public App()
{
Services = ConfigureServices();
this.InitializeComponent();
}
private static IServiceProvider ConfigureServices()
{
var services = new ServiceCollection();
// Services
services.AddSingleton<Services.IFileService>(x =>
ActivatorUtilities.CreateInstance<Services.WindowsFileService>(x,
Windows.Storage.ApplicationData.Current.LocalFolder)
);
// ViewModels
services.AddTransient<AllNotesViewModel>();
services.AddTransient<NoteViewModel>();
return services.BuildServiceProvider();
}
protected override void OnLaunched(LaunchActivatedEventArgs args)
{
m_window = new MainWindow();
m_window.Activate();
}
public IServiceProvider Services { get; }
private Window? m_window;
public new static App Current => (App)Application.Current;
}
Essa configuração configura o contêiner de injeção de dependência com todos os serviços necessários:
-
ConfigureServices()método: um método estático que cria e configura a coleção de serviços. Separar esse método torna a configuração mais mantenedível e mais fácil de testar. -
ServicesPropriedade: Uma propriedade de instância que contém oIServiceProvider. O construtor define essa propriedade chamandoConfigureServices(). -
App.Currentpropriedade estática: fornece acesso conveniente à instância atualApp, o que é útil quando modelos ou outras classes precisam acessar o provedor de serviços. -
IFileServiceregistro: usaActivatorUtilities.CreateInstancepara criar uma instância deWindowsFileServicecomApplicationData.Current.LocalFoldercomo parâmetro. Essa abordagem permite que o parâmetro do construtor seja injetado no momento do registro. Registre o serviço como um singleton, pois as operações de arquivo são sem estado e uma única instância pode ser compartilhada dentro do aplicativo. - Cadastro de ViewModels: registre ambos os ViewModels como transitórios, ou seja, uma nova instância é criada sempre que uma for solicitada. Essa abordagem garante que cada página obtenha sua própria instância ViewModel com estado limpo.
Modelos e outras classes podem acessar o provedor de serviços através de App.Current.Services.GetService() para recuperar serviços registrados quando necessário.
Saiba mais nos documentos:
Atualizar modelos para usar o serviço de arquivo
Agora que o serviço de arquivo está disponível por meio da injeção de dependência, atualize as classes de modelo para usá-lo. Os modelos recebem o serviço de arquivo e o utilizam para todas as operações de arquivos.
Atualizar o modelo Note
Atualize a Note classe para aceitar o serviço de arquivo e usá-lo para salvar, excluir e operações de existência de arquivo:
using System;
using System.Threading.Tasks;
using WinUINotes.Services;
namespace WinUINotes.Models;
public class Note
{
private IFileService fileService;
public string Filename { get; set; } = string.Empty;
public string Text { get; set; } = string.Empty;
public DateTime Date { get; set; } = DateTime.Now;
public Note(IFileService fileService)
{
Filename = "notes" + DateTime.Now.ToBinary().ToString() + ".txt";
this.fileService = fileService;
}
public async Task SaveAsync()
{
await fileService.CreateOrUpdateFileAsync(Filename, Text);
}
public async Task DeleteAsync()
{
await fileService.DeleteFileAsync(Filename);
}
public bool NoteFileExists()
{
return fileService.FileExists(Filename);
}
}
O Note modelo agora recebe o serviço de arquivo por meio de injeção de construtor:
-
Construtor: aceita um
IFileServiceparâmetro, tornando a dependência explícita e necessária. Esse design promove a testabilidade e garante que o modelo sempre tenha acesso ao serviço de arquivo de que precisa. - Geração de nome de arquivo: o construtor gera automaticamente um nome de arquivo exclusivo usando o timestamp atual, garantindo que cada anotação tenha um nome de arquivo distinto.
-
Operações de arquivo: os
SaveAsync(),DeleteAsync(), eNoteFileExists()todos os métodos delegam para o serviço de arquivo injetado, mantendo o modelo focado em coordenar operações em vez de implementar detalhes de I/O de arquivo.
Essa abordagem elimina a necessidade de o modelo usar o padrão do localizador de serviço (acessando App.Services diretamente), o que melhora a capacidade de teste e deixa as dependências claras.
Atualizar o modelo do AllNotes
Atualize a AllNotes classe para carregar anotações do armazenamento usando o serviço de arquivo:
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Threading.Tasks;
using Windows.Storage;
using WinUINotes.Services;
namespace WinUINotes.Models;
public class AllNotes
{
private IFileService fileService;
public ObservableCollection<Note> Notes { get; set; } = [];
public AllNotes(IFileService fileService)
{
this.fileService = fileService;
}
public async Task LoadNotes()
{
Notes.Clear();
await GetFilesInFolderAsync(fileService.GetLocalFolder());
}
private async Task GetFilesInFolderAsync(IStorageFolder folder)
{
// Each StorageItem can be either a folder or a file.
IReadOnlyList<IStorageItem> storageItems =
await fileService.GetStorageItemsAsync(folder);
foreach (IStorageItem item in storageItems)
{
if (item.IsOfType(StorageItemTypes.Folder))
{
// Recursively get items from subfolders.
await GetFilesInFolderAsync((IStorageFolder)item);
}
else if (item.IsOfType(StorageItemTypes.File))
{
IStorageFile file = (IStorageFile)item;
Note note = new(fileService)
{
Filename = file.Name,
Text = await fileService.GetTextFromFileAsync(file),
Date = file.DateCreated.DateTime
};
Notes.Add(note);
}
}
}
}
O modelo AllNotes recebe o serviço de arquivo através de injeção de construtor, assim como o modelo Note. Como essa classe está no WinUINotes.Bus projeto, ela não pode acessar App.Current.Services do WinUINotes projeto (devido a restrições de referência do projeto).
O LoadNotes() método chama o método privado GetFilesInFolderAsync() para enumerar recursivamente todos os arquivos na pasta de armazenamento local e suas subpastas. Para cada item de armazenamento:
- Se for uma pasta, o método chama a si mesmo recursivamente para processar o conteúdo da pasta
- Se for um arquivo, ele criará uma nova
Noteinstância com o serviço de arquivo injetado - A anotação
Filenameé definida como o nome do arquivo - A anotação
Texté preenchida lendo o conteúdo do arquivo usandoGetTextFromFileAsync() - A anotação
Dateestá definida como a data de criação do arquivo - A anotação é adicionada à
Notescoleção observável
Essa abordagem garante que todas as anotações carregadas do armazenamento tenham acesso ao serviço de arquivos de que precisam para operações futuras de salvamento e exclusão.
Atualizar ViewModels para usar o serviço de arquivo
Com os modelos agora usando o serviço de arquivo, você precisa atualizar os ViewModels. No entanto, como os modelos lidam diretamente com as operações de arquivo, os ViewModels se concentram principalmente na orquestração dos modelos e no gerenciamento de propriedades observáveis.
Atualizar AllNotesViewModel
Atualize o AllNotesViewModel para trabalhar com o modelo atualizado AllNotes
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using System.Collections.ObjectModel;
using System.Threading.Tasks;
using WinUINotes.Models;
using WinUINotes.Services;
namespace WinUINotes.ViewModels
{
public partial class AllNotesViewModel : ObservableObject
{
private readonly AllNotes allNotes;
[ObservableProperty]
private ObservableCollection<Note> notes;
public AllNotesViewModel(IFileService fileService)
{
allNotes = new AllNotes(fileService);
notes = new ObservableCollection<Note>();
}
[RelayCommand]
public async Task LoadAsync()
{
await allNotes.LoadNotes();
Notes.Clear();
foreach (var note in allNotes.Notes)
{
Notes.Add(note);
}
}
}
}
O que mudou desde a Etapa 2?
A alteração principal é a adição do parâmetro IFileService ao construtor. Na Etapa 2, o ViewModel instanciou AllNotes com um construtor sem parâmetros (allNotes = new AllNotes()). Agora que o modelo AllNotes exige que o serviço de arquivo execute suas operações, o ViewModel recebe IFileService por meio da injeção do construtor e passa IFileService para o modelo.
Essa alteração mantém o fluxo de dependência adequado – o serviço de arquivo é injetado no nível superior (ViewModel) e flui para baixo para o modelo. O ViewModel continua focado em coordenar o processo de carregamento e manter a coleção observável Notes sincronizada com os dados do modelo, sem precisar saber os detalhes de implementação de como os arquivos são carregados.
Atualizar NoteViewModel
Atualize o NoteViewModel para injetar o serviço de arquivos e use o sistema de mensagens do Kit de Ferramentas MVVM:
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
using System;
using System.Threading.Tasks;
using WinUINotes.Models;
using WinUINotes.Services;
namespace WinUINotes.ViewModels
{
public partial class NoteViewModel : ObservableObject
{
private Note note;
private IFileService fileService;
[ObservableProperty]
[NotifyCanExecuteChangedFor(nameof(SaveCommand))]
[NotifyCanExecuteChangedFor(nameof(DeleteCommand))]
private string filename = string.Empty;
[ObservableProperty]
[NotifyCanExecuteChangedFor(nameof(SaveCommand))]
private string text = string.Empty;
[ObservableProperty]
private DateTime date = DateTime.Now;
public NoteViewModel(IFileService fileService)
{
this.fileService = fileService;
this.note = new Note(fileService);
this.Filename = note.Filename;
}
public void InitializeForExistingNote(Note note)
{
this.note = note;
this.Filename = note.Filename;
this.Text = note.Text;
this.Date = note.Date;
}
[RelayCommand(CanExecute = nameof(CanSave))]
private async Task Save()
{
note.Filename = this.Filename;
note.Text = this.Text;
note.Date = this.Date;
await note.SaveAsync();
// Check if the DeleteCommand can now execute
// (it can if the file now exists)
DeleteCommand.NotifyCanExecuteChanged();
}
private bool CanSave()
{
return note is not null
&& !string.IsNullOrWhiteSpace(this.Text)
&& !string.IsNullOrWhiteSpace(this.Filename);
}
[RelayCommand(CanExecute = nameof(CanDelete))]
private async Task Delete()
{
await note.DeleteAsync();
note = new Note(fileService);
// Send a message from some other module
WeakReferenceMessenger.Default.Send(new NoteDeletedMessage(note));
}
private bool CanDelete()
{
// Note: This is to illustrate how commands can be
// enabled or disabled.
// In a real application, you shouldn't perform
// file operations in your CanExecute logic.
return note is not null
&& !string.IsNullOrWhiteSpace(this.Filename)
&& this.note.NoteFileExists();
}
}
}
O que mudou desde a Etapa 2?
Várias alterações importantes dão suporte à injeção de dependência e à comunicação entre ViewModel:
Injeção do serviço de arquivos: o construtor agora aceita
IFileServicecomo um parâmetro e o armazena em um campo. Esse serviço é passado para oNotemodelo ao criar novas instâncias, garantindo que todas as anotações possam executar operações de arquivo.WeakReferenceMessenger: o
Delete()método agora usa o Kit de Ferramentas MVVMWeakReferenceMessenger.Default.Send()para enviar umNoteDeletedMessageapós a exclusão de uma anotação. Essa abordagem permite o acoplamento flexível entre ViewModels – outras partes do aplicativo (comoNotePage) podem escutar essa mensagem e responder adequadamente (por exemplo, navegando de volta para a lista de anotações, que foi atualizada) sem aNoteViewModelnecessidade de uma referência direta a elas.
O WeakReferenceMessenger é um recurso fundamental do Kit de Ferramentas MVVM que impede vazamentos de memória usando referências fracas. Os componentes podem assinar mensagens sem criar referências fortes que impeçam a coleta de lixo.
Saiba mais nos documentos:
Criar a classe NoteDeletedMessage
A WeakReferenceMessenger precisa de uma classe de mensagem para enviar entre componentes. Crie uma nova classe para representar o evento de exclusão de notas:
No projeto WinUINotes.Bus , adicione um novo arquivo
NoteDeletedMessage.csde classe:using CommunityToolkit.Mvvm.Messaging.Messages; using WinUINotes.Models; namespace WinUINotes { public class NoteDeletedMessage : ValueChangedMessage<Note> { public NoteDeletedMessage(Note note) : base(note) { } } }
Essa classe de mensagem herda de ValueChangedMessage<Note>, que é um tipo de mensagem especializado fornecido pelo Kit de Ferramentas MVVM para carregar notificações de alteração de valor. O construtor aceita um Note e o passa para a classe base, tornando-o disponível para destinatários de mensagens por meio da Value propriedade. Quando NoteViewModel envia essa mensagem, qualquer componente que se inscreve para NoteDeletedMessage a recebe e pode acessar a nota excluída por meio da propriedade Value.
Como funciona o sistema de mensagens no Kit de Ferramentas do MVVM:
-
Remetente: O método
NoteViewModel.Delete()envia a mensagem usandoWeakReferenceMessenger.Default.Send(new NoteDeletedMessage(note)). -
Receptor: páginas (como
NotePage) podem se registrar para receber mensagens implementandoIRecipient<NoteDeletedMessage>e registrando-se com o mensageiro. Quando a mensagem é recebida, a página pode navegar de volta para a lista de anotações. - Acoplamento solto: o remetente não precisa saber quem (se alguém) está ouvindo. O receptor não precisa de uma referência direta ao remetente. Essa configuração mantém seus componentes independentes e testáveis.
A abordagem de referência fraca significa que, se um componente for coletado como lixo, sua assinatura de mensagens será automaticamente limpa, sem causar vazamentos de memória.
Atualize páginas para usar a injeção de dependência
Atualize os construtores de página para receber os ViewModels por meio da DI.
Atualizar AllNotesPage.xaml.cs
using Microsoft.Extensions.DependencyInjection;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Navigation;
using WinUINotes.ViewModels;
namespace WinUINotes.Views
{
public sealed partial class AllNotesPage : Page
{
private AllNotesViewModel? viewModel;
public AllNotesPage()
{
this.InitializeComponent();
viewModel = App.Current.Services.GetService<AllNotesViewModel>();
}
private void NewNoteButton_Click(object sender, RoutedEventArgs e)
{
Frame.Navigate(typeof(NotePage));
}
private void ItemsView_ItemInvoked(ItemsView sender, ItemsViewItemInvokedEventArgs args)
{
Frame.Navigate(typeof(NotePage), args.InvokedItem);
}
protected override async void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
if (viewModel is not null)
{
await viewModel.LoadAsync();
}
}
}
}
O que mudou desde a Etapa 2?
O aplicativo agora obtém o AllNotesViewModel do contêiner de injeção de dependência usando App.Current.Services.GetService<AllNotesViewModel>() em vez de criá-lo diretamente com new AllNotesViewModel(). Essa abordagem tem vários benefícios:
-
Resolução automática de dependência: o contêiner de DI fornece automaticamente a
IFileServicedependência necessária queAllNotesViewModelrequer em seu construtor. - Gerenciamento do ciclo de vida: o contêiner de DI gerencia o ciclo de vida do ViewModel de acordo com a forma como ele foi registrado (como transitório nesse caso, fornecendo uma nova instância).
- Testabilidade: esse padrão facilita a troca de implementações ou dependências simuladas em testes.
- Manutenção: se as dependências do ViewModel forem alteradas no futuro, você só precisará atualizar a configuração de DI, não todos os locais onde o ViewModel é criado.
O restante do código permanece o mesmo. O OnNavigatedTo() método ainda chama LoadAsync() para atualizar a lista de anotações quando o usuário navega até esta página.
Atualizar NotePage.xaml.cs
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Navigation;
using WinUINotes.Models;
using WinUINotes.ViewModels;
namespace WinUINotes.Views
{
public sealed partial class NotePage : Page
{
private NoteViewModel? noteVm;
public NotePage()
{
this.InitializeComponent();
}
public void RegisterForDeleteMessages()
{
WeakReferenceMessenger.Default.Register<NoteDeletedMessage>(this, (r, m) =>
{
if (Frame.CanGoBack)
{
Frame.GoBack();
}
});
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
noteVm = App.Current.Services.GetService<NoteViewModel>();
RegisterForDeleteMessages();
if (e.Parameter is Note note && noteVm is not null)
{
noteVm.InitializeForExistingNote(note);
}
}
}
}
O que mudou desde a Etapa 2?
Várias alterações importantes integram recursos de injeção de dependência e mensagens:
-
ViewModel do contêiner de DI:
NoteViewModelagora é recuperado do contêiner de injeção de dependência usandoApp.Current.Services.GetService<NoteViewModel>()no métodoOnNavigatedTo()em vez de ser instanciado diretamente. Essa abordagem garante que o ViewModel receba automaticamente sua dependência necessáriaIFileService. -
Registro de mensagem: O novo
RegisterForDeleteMessages()método assinaNoteDeletedMessageusando oWeakReferenceMessenger. Quando uma anotação é excluída (doNoteViewModel.Delete()método), essa página recebe a mensagem e navega de volta para a lista de anotações usandoFrame.GoBack(). -
Padrão de mensagens: esse padrão demonstra o acoplamento flexível habilitado pelo sistema de mensagens do Kit de Ferramentas MVVM. O
NoteViewModelnão precisa saber sobre a navegação ou a estrutura da página – ele simplesmente envia uma mensagem quando uma anotação é excluída e a página manipula a resposta de navegação de forma independente. -
Tempo de ciclo de vida: o ViewModel é instanciado e o registro de mensagem ocorre em
OnNavigatedTo(), garantindo que tudo seja inicializado corretamente quando a página se tornar ativa.
Esse padrão separa as preocupações efetivamente: o ViewModel se concentra na lógica de negócios e nas operações de dados, enquanto a página lida com preocupações específicas da interface do usuário, como navegação.
Windows developer