Nota
O acesso a esta página requer autorização. Podes tentar iniciar sessão ou mudar de diretório.
O acesso a esta página requer autorização. Podes tentar mudar de diretório.
A injeção de dependência (DI) ajuda você a gerenciar o ciclo de vida de seus ViewModels e serviços. Isso torna seu código mais testável e fácil de manter. Nesta etapa, você configura a 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 Usar injeção de dependência no .NET .
Instalar os pacotes Microsoft.Extensions
Adicione suporte de DI aos seus projetos.
Instale
Microsoft.Extensions.DependencyInjectionem projetos WinUINotes e WinUINotes.Bus :dotnet add WinUINotes package Microsoft.Extensions.DependencyInjection dotnet add WinUINotes.Bus package Microsoft.Extensions.DependencyInjection
Criar uma interface de serviço de arquivo e implementação
No projeto WinUINotes.Bus , crie uma nova pasta chamada Services.
Adicione um ficheiro de interface
IFileService.cs: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 de serviço de arquivo define métodos para operações de arquivo. Abstrai os detalhes da manipulação de arquivos nos ViewModels e Models. Os parâmetros e valores de retorno são todos tipos básicos de .NET ou interfaces. Este design garante que o serviço possa ser facilmente simulado ou substituído em testes de unidade, promovendo acoplamento solto e estabilidade.
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 o Tempo de Execução do Windows (WinRT) e APIs de armazenamento .NET:
-
Injeção do construtor: O serviço aceita um
IStorageFolderem seu construtor. Essa abordagem permite configurar o local de armazenamento quando você instancia o serviço. Essa abordagem torna o serviço flexível e testável. -
CreateOrUpdateFileAsync(): Este método é usadoTryGetItemAsync()para verificar se um arquivo já existe. Se isso acontecer, o método atualiza o arquivo existente. Caso contrário, ele criará um novo arquivo usandoCreateFileAsync(). Essa abordagem lida com 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 lançadas ao tentar excluir arquivos inexistentes. -
FileExists(): Este método síncrono verifica a existência do arquivo chamando o asyncTryGetItemAsync()e bloqueando com.Result. Embora essa abordagem geralmente não seja recomendada, ela é usada aqui para dar suporteCanDelete()ao método de validação no ViewModel, que deve ser síncrono. -
Métodos de item de armazenamento: Os
GetStorageItemsAsync()métodos eGetTextFromFileAsync()fornecem acesso a arquivos e seu conteúdo usando APIs de armazenamento do WinRT. Esses métodos permitem que os modelos carreguem e enumerem notas.
Ao implementar a interface, você pode facilmente substituir essa classe por uma implementação simulada IFileService 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 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 fácil de manter e testar. -
Servicesproperty: uma propriedade de instância que manté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. -
IFileServiceregisto: 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. Registe o serviço como um singleton, pois as operações de ficheiro são sem estado e uma única instância pode ser partilhada em toda a aplicação. - Registro de ViewModels: registre ambos os ViewModels como transitórios, o que significa que uma nova instância é criada sempre que uma é solicitada. Essa abordagem garante que cada página obtenha sua própria instância ViewModel com estado limpo.
Modelos e outras classes podem aceder ao 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 de injeção de dependência, atualize as classes de modelo para usá-lo. Os modelos recebem o serviço de ficheiros e utilizam-no em todas as operações com ficheiros.
Atualizar o modelo de notas
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 da injeção do construtor:
-
Construtor: Aceita um
IFileServiceparâmetro, tornando a dependência explícita e necessária. Esse design promove a capacidade de teste e garante que o modelo sempre tenha acesso ao serviço de arquivos de que precisa. - Geração de nome de arquivo: O construtor gera automaticamente um nome de arquivo exclusivo usando o carimbo de data/hora atual, garantindo que cada nota tenha um nome de arquivo distinto.
-
Operações de arquivo: Os
SaveAsync(),DeleteAsync(), eNoteFileExists()todos os métodos delegam ao serviço de arquivo injetado, mantendo o modelo focado na coordenação de operações em vez de implementar detalhes de E/S de arquivo.
Essa abordagem elimina a necessidade de o modelo usar o padrão do localizador de serviços (acessando App.Services diretamente), o que melhora a capacidade de teste e torna as dependências claras.
Atualizar o modelo AllNotes
Atualize a AllNotes classe para carregar notas 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 AllNotes modelo recebe o serviço de arquivo por meio da injeção do construtor, assim como o Note modelo. Como essa classe está no WinUINotes.Bus projeto, ela não pode acessar App.Current.Services a partir 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 se chama recursivamente para processar o conteúdo da pasta
- Se for um arquivo, ele cria uma nova
Noteinstância com o serviço de arquivo injetado - A nota
Filenameé definida como o nome do ficheiro - A nota é
Textpreenchida lendo o conteúdo do ficheiro usandoGetTextFromFileAsync() - A nota
Dateestá definida para a data de criação do ficheiro - A nota é adicionada à coleção observável
Notes
Essa abordagem garante que todas as anotações carregadas do armazenamento tenham acesso ao serviço de arquivos de que precisam para futuras operações 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 o ViewModels. No entanto, como os modelos lidam diretamente com as operações de arquivo, os ViewModels se concentram principalmente em orquestrar os modelos e gerenciar 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 o Passo 2?
A principal alteração é a adição do IFileService parâmetro ao construtor. Na etapa 2, o ViewModel instanciou AllNotes com um construtor sem parâmetros (allNotes = new AllNotes()). Agora que o modelo AllNotes requer o serviço de ficheiros para executar as suas operações, o ViewModel recebe a IFileService através da injeção no construtor e passa-a para o modelo.
Esta alteração mantém o fluxo de dependência adequado - o serviço de arquivo é injetado no nível mais alto (modelo de visualização) e flui para baixo até o modelo. O ViewModel continua a se concentrar em coordenar o processo de carregamento e manter a coleção observável Notes sincronizada com os dados do modelo, sem a necessidade de saber os detalhes da implementação de como os arquivos são carregados.
Atualizar NoteViewModel
Atualize o NoteViewModel para injetar o serviço de arquivo e use o sistema de mensagens do MVVM Toolkit:
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 o Passo 2?
Várias alterações importantes suportam a injeção de dependência e a comunicação entre ViewModel:
Injeção de serviço de arquivo: O construtor agora aceita
IFileServicecomo parâmetro e o armazena em um campo. Esse serviço é passado para oNotemodelo ao criar novas instâncias, garantindo que todas as notas possam executar operações de arquivo.WeakReferenceMessenger: O
Delete()método agora usa o MVVM ToolkitWeakReferenceMessenger.Default.Send()para transmitir umaNoteDeletedMessageapós a exclusão de uma nota. Essa abordagem permite o acoplamento flexível entre ViewModels - outras partes do aplicativo (comoNotePage) podem ouvir essa mensagem e responder adequadamente (por exemplo, navegando de volta para a lista de notas, que foi atualizada) sem aNoteViewModelnecessidade de uma referência direta a elas.
O WeakReferenceMessenger é um recurso chave do MVVM Toolkit que evita vazamentos de memória usando referências fracas. Os componentes podem assinar mensagens sem criar referências fortes que impediriam a coleta de lixo.
Saiba mais nos documentos:
Criar a classe NoteDeletedMessage
O WeakReferenceMessenger precisa de uma classe de mensagem para enviar entre componentes. Crie uma nova classe para representar o evento de exclusão de nota:
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) { } } }
Esta classe de mensagem herda de ValueChangedMessage<Note>, que é um tipo de mensagem especializado fornecido pelo MVVM Toolkit 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 através da Value propriedade. Quando NoteViewModel envia esta mensagem, qualquer componente que se inscreva NoteDeletedMessage a recebe e pode acessar a nota excluída através da Value propriedade.
Como o sistema de mensagens funciona no MVVM Toolkit:
-
Remetente: O
NoteViewModel.Delete()método envia a mensagem usandoWeakReferenceMessenger.Default.Send(new NoteDeletedMessage(note)). -
Destinatário: 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 todas as notas. - Acoplamento solto: O remetente não precisa saber quem (se alguém) está ouvindo. O recetor não precisa de uma referência direta ao remetente. Esta configuração mantém os componentes independentes e testáveis.
A abordagem de referência fraca significa que, se um componente for coletado pelo coletor de lixo, a sua assinatura de mensagem será limpa automaticamente, sem causar vazamentos de memória.
Atualizar páginas para usar a injeção de dependência
Atualize seus construtores de página para receber os ViewModels por meio de 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 o Passo 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 criar diretamente com new AllNotesViewModel(). Esta abordagem tem várias vantagens:
-
Resolução automática de dependência: O contêiner DI fornece automaticamente a
IFileServicedependência queAllNotesViewModelrequer em seu construtor. - Gerenciamento do ciclo de vida: O contêiner DI gerencia o ciclo de vida do ViewModel de acordo com a forma como ele foi registrado (como transitório, neste caso, fornecendo uma nova instância).
- Testabilidade: Este padrão facilita a troca de implementações ou dependências mockadas em testes.
- Capacidade de manutenção: Se as dependências do ViewModel mudarem no futuro, você só precisará atualizar a configuração de DI, não todos os locais onde o ViewModel é criado.
O resto 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 para 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 o Passo 2?
Várias alterações importantes integram recursos de injeção de dependência e mensagens:
-
ViewModel do container DI: O
NoteViewModelé agora obtido do container 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. -
Registo de mensagens: o novo
NoteDeletedMessagemétodo inscreve-se usando oWeakReferenceMessenger. Quando uma nota é excluída (doNoteViewModel.Delete()método), esta página recebe a mensagem e navega de volta para a lista de todas as anotações usandoFrame.GoBack(). -
Padrão de mensagens: esse padrão demonstra o acoplamento solto habilitado pelo sistema de mensagens do MVVM Toolkit. O
NoteViewModelnão precisa saber sobre a navegação ou a estrutura da página - ele simplesmente envia uma mensagem quando uma nota é excluída e a página lida com a resposta de navegação de forma independente. -
Tempo de ciclo de vida: O ViewModel é instanciado e o registro de mensagens ocorre no
OnNavigatedTo(), garantindo que tudo seja inicializado corretamente quando a página se tornar ativa.
Esse padrão separa as preocupações de forma eficaz: 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