Nota:
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
La inyección de dependencias (DI) le ayuda a administrar el ciclo de vida de los ViewModels y los servicios. Hace que el código sea más fácil de probar y mantener. En este paso, configurará la inserción de dependencias en la aplicación y actualizará los modelos para usar un servicio de archivos para las operaciones de archivos.
Para obtener más información sobre el marco de inserción de dependencias de .NET, consulte Inserción de dependencias de .NET y el tutorial Uso de la inserción de dependencias en .NET .
Instalación de paquetes de Microsoft.Extensions
Agregue compatibilidad con di a los proyectos.
Instale
Microsoft.Extensions.DependencyInjectionen proyectos winUINotes y WinUINotes.Bus :dotnet add WinUINotes package Microsoft.Extensions.DependencyInjection dotnet add WinUINotes.Bus package Microsoft.Extensions.DependencyInjection
Creación de una interfaz e implementación del servicio de archivos
En el proyecto WinUINotes.Bus , cree una carpeta denominada Services.
Agregue un archivo
IFileService.csde interfaz :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(); } }La interfaz del servicio de archivos define métodos para las operaciones de archivo. Abstrae los detalles de la gestión de archivos de los ViewModels y Models. Los parámetros y los valores devueltos son todos los tipos o interfaces básicos de .NET. Este diseño garantiza que el servicio se pueda simular o reemplazar fácilmente en pruebas unitarias, lo que promueve el acoplamiento flexible y la capacidad de prueba.
Agregue el archivo
WindowsFileService.csde implementación :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); } } }
La WindowsFileService implementación proporciona operaciones de archivo concretas mediante las API de almacenamiento de Windows Runtime (WinRT) y .NET:
-
Inserción de constructores: el servicio acepta un
IStorageFolderen su constructor. Este enfoque le permite configurar la ubicación de almacenamiento al crear una instancia del servicio. Este enfoque hace que el servicio sea flexible y testeable. -
CreateOrUpdateFileAsync(): este método usaTryGetItemAsync()para comprobar si ya existe un archivo. Si es así, el método actualiza el archivo existente. De lo contrario, crea un nuevo archivo medianteCreateFileAsync(). Este enfoque controla los escenarios de creación y actualización en un único método. -
DeleteFileAsync(): antes de eliminar un archivo, este método comprueba que el archivo existe medianteTryGetItemAsync(). Esta comprobación impide que se produzcan excepciones al intentar eliminar archivos no existentes. -
FileExists(): este método sincrónico comprueba la existencia de archivos llamando al método asincrónicoTryGetItemAsync()y bloqueando con.Result. Aunque este enfoque generalmente no se recomienda, se usa aquí para admitir elCanDelete()método de validación en ViewModel, que debe ser sincrónico. -
Métodos de elemento de almacenamiento: los
GetStorageItemsAsync()métodos yGetTextFromFileAsync()proporcionan acceso a los archivos y su contenido mediante las API de almacenamiento de WinRT. Estos métodos permiten que los modelos carguen y enumeren notas.
Al implementar la IFileService interfaz, puede reemplazar fácilmente esta clase por una implementación ficticia para realizar pruebas o un proveedor de almacenamiento diferente si es necesario.
Obtenga más información en los documentos:
Configuración de la inyección de dependencias en App.xaml.cs
Antes de actualizar los modelos y ViewModels para usar el servicio de archivos, configure la inyección de dependencias para que el servicio se pueda resolver e inyectar en los constructores.
Actualice el App.xaml.cs archivo para configurar el contenedor de inserción de dependencias:
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;
}
Esta configuración configura el contenedor de inserción de dependencias con todos los servicios necesarios:
-
ConfigureServices()method: método estático que crea y configura la colección de servicios. Separar este método hace que la configuración sea más fácil de mantener y probar. -
Servicesproperty: una propiedad de instancia que contiene elIServiceProvider. El constructor establece esta propiedad llamando aConfigureServices(). -
App.Currentpropiedad estática: proporciona un acceso cómodo a la instancia actualApp, que es útil cuando los modelos u otras clases necesitan acceder al proveedor de servicios. -
IFileServiceregistration: utilizaActivatorUtilities.CreateInstancepara crear unaWindowsFileServiceinstancia con elApplicationData.Current.LocalFoldercomo parámetro. Este enfoque permite insertar el parámetro de constructor en el momento del registro. Registre el servicio como singleton, ya que las operaciones de archivo no tienen estado y se puede compartir una sola instancia en la aplicación. - Registro de ViewModels: registre ambos ViewModels como transitorios, lo que significa que se crea una nueva instancia cada vez que se solicita uno. Este enfoque garantiza que cada página obtenga su propia instancia de ViewModel con estado limpio.
Los modelos y otras clases pueden acceder al proveedor de servicios a través de App.Current.Services.GetService() para recuperar los servicios registrados cuando sea necesario.
Obtenga más información en los documentos:
Actualizar modelos para usar el servicio de archivos
Ahora que el servicio de archivos está disponible a través de la inserción de dependencias, actualice las clases de modelo para usarlas. Los modelos reciben el servicio de archivos y lo usan para todas las operaciones de archivo.
Actualización del modelo de nota
Actualice la Note clase para aceptar el servicio de archivos y úsela para las operaciones de guardado, eliminación y existencia de archivos:
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);
}
}
El Note modelo recibe ahora el servicio de archivos mediante la inserción de constructores:
-
Constructor: acepta un
IFileServiceparámetro, lo que hace que la dependencia sea explícita y necesaria. Este diseño promueve la capacidad de prueba y garantiza que el modelo siempre tiene acceso al servicio de archivos que necesita. - Generación de nombre de archivo: el constructor genera automáticamente un nombre de archivo único mediante la marca de tiempo actual, lo que garantiza que cada nota tenga un nombre de archivo distinto.
-
Operaciones de archivo: los
SaveAsync()métodos ,DeleteAsync()yNoteFileExists()todos se delegan al servicio de archivos insertado, manteniendo el modelo centrado en coordinar operaciones en lugar de implementar detalles de E/S de archivo.
Este enfoque elimina la necesidad de que el modelo use el patrón de localizador de servicios (acceder App.Services directamente), lo que mejora la testabilidad y deja claras las dependencias.
Actualizar el modelo de AllNotes
Actualice la AllNotes clase para cargar notas del almacenamiento mediante el servicio de archivos:
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);
}
}
}
}
El AllNotes modelo recibe el servicio de archivos a través de la inserción de constructores, al igual que el Note modelo. Dado que esta clase está en el WinUINotes.Bus proyecto, no puede acceder App.Current.Services desde el WinUINotes proyecto (debido a restricciones de referencia del proyecto).
El LoadNotes() método llama al método privado GetFilesInFolderAsync() para enumerar de forma recursiva todos los archivos de la carpeta de almacenamiento local y sus subcarpetas. Para cada elemento de almacenamiento:
- Si es una carpeta, el método se llama a sí mismo recursivamente para procesar el contenido de la carpeta.
- Si es un archivo, crea una nueva
Noteinstancia con el servicio de archivos insertado. - La nota
Filenamese establece en el nombre del archivo. - La nota
Textse rellena leyendo el contenido del archivo medianteGetTextFromFileAsync() - La nota
Datese establece en la fecha de creación del archivo. - La nota es añadida a la colección observable
Notes.
Este enfoque garantiza que todas las notas cargadas desde el almacenamiento tengan acceso al servicio de archivos que necesitan para futuras operaciones de guardado y eliminación.
Actualizar ViewModels para usar el servicio de archivos
Ahora que los modelos usan el servicio de archivos, debe actualizar los ViewModels. Sin embargo, dado que los modelos controlan directamente las operaciones de archivo, viewModels se centran principalmente en la orquestación de los modelos y la administración de propiedades observables.
Actualizar AllNotesViewModel
Actualice AllNotesViewModel para que funcione con el modelo AllNotes actualizado.
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);
}
}
}
}
¿Qué ha cambiado desde el paso 2?
El cambio de clave es la adición del IFileService parámetro al constructor. En el paso 2, el ViewModel instanció AllNotes con un constructor sin parámetros (allNotes = new AllNotes()). Ahora que el AllNotes modelo requiere que el servicio de archivos realice sus operaciones, ViewModel recibe el IFileService a través de la inserción de constructores y lo pasa al modelo.
Este cambio mantiene el flujo de dependencia adecuado: el servicio de archivos se inserta en el nivel superior (ViewModel) y fluye hacia abajo hasta el modelo. ViewModel sigue centrándose en coordinar el proceso de carga y mantener la colección observable Notes sincronizada con los datos del modelo, sin necesidad de conocer los detalles de implementación de cómo se cargan los archivos.
Actualizar NoteViewModel
Actualice el NoteViewModel para inyectar el servicio de archivos y utilice el sistema de mensajería de 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();
}
}
}
¿Qué ha cambiado desde el paso 2?
Varios cambios importantes admiten la inserción de dependencias y la comunicación entre ViewModel:
Inyección de servicio de archivos: el constructor ahora acepta
IFileServicecomo parámetro y lo almacena en un campo. Este servicio se pasa alNotemodelo al crear nuevas instancias, lo que garantiza que todas las notas pueden realizar operaciones de archivo.WeakReferenceMessenger: el método
Delete()ahora utiliza el kit de herramientas MVVMWeakReferenceMessenger.Default.Send()para transmitir unaNoteDeletedMessagedespués de eliminar una nota. Este enfoque permite el acoplamiento flexible entre ViewModels: otras partes de la aplicación (comoNotePage) pueden escuchar este mensaje y responder adecuadamente (por ejemplo, volviendo a la lista de notas, que se ha actualizado) sinNoteViewModelnecesidad de una referencia directa a ellos.
WeakReferenceMessenger es una característica clave del kit de herramientas de MVVM que evita pérdidas de memoria mediante referencias débiles. Los componentes pueden suscribirse a mensajes sin crear referencias fuertes que impidan la recolección de basura.
Obtenga más información en los documentos:
Creación de la clase NoteDeletedMessage
WeakReferenceMessenger Necesita una clase de mensaje para enviar entre componentes. Cree una nueva clase para representar el evento de eliminación de notas:
En el proyecto WinUINotes.Bus , agregue un nuevo archivo
NoteDeletedMessage.csde clase :using CommunityToolkit.Mvvm.Messaging.Messages; using WinUINotes.Models; namespace WinUINotes { public class NoteDeletedMessage : ValueChangedMessage<Note> { public NoteDeletedMessage(Note note) : base(note) { } } }
Esta clase de mensaje hereda de ValueChangedMessage<Note>, que es un tipo de mensaje especializado proporcionado por el kit de herramientas de MVVM para llevar notificaciones de cambio de valor. El constructor acepta Note y lo pasa a la clase base, lo que hace que esté disponible para los destinatarios del mensaje a través de la Value propiedad . Cuando NoteViewModel envía este mensaje, cualquier componente que se suscribe a NoteDeletedMessage lo recibe y puede acceder a la nota eliminada a través de la Value propiedad .
Funcionamiento de la mensajería en el kit de herramientas de MVVM:
-
Remitente: el
NoteViewModel.Delete()método envía el mensaje medianteWeakReferenceMessenger.Default.Send(new NoteDeletedMessage(note)). -
Receptor: Las páginas (como
NotePage) se pueden registrar para recibir mensajes mediante la implementaciónIRecipient<NoteDeletedMessage>y el registro con el mensajero. Cuando se recibe el mensaje, la página puede volver a navegar a la lista de todas las notas. - Acoplamiento flexible: el remitente no necesita saber quién (si alguien) está escuchando. El receptor no necesita una referencia directa al remitente. Esta configuración mantiene los componentes independientes y comprobables.
El enfoque de referencia débil significa que, si un componente se recicla por el recolector de basura, su suscripción de mensaje se limpia automáticamente sin causar pérdidas de memoria.
Actualizar páginas para usar la inyección de dependencias
Actualice los constructores de página para recibir ViewModels mediante DI.
Actualizar 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();
}
}
}
}
¿Qué ha cambiado desde el paso 2?
La aplicación ahora obtiene el AllNotesViewModel del contenedor de inyección de dependencias usando App.Current.Services.GetService<AllNotesViewModel>() en lugar de crearlo directamente con new AllNotesViewModel(). Este enfoque tiene varias ventajas:
-
Resolución automática de dependencias: el contenedor de inserción de dependencias proporciona automáticamente la
IFileServicedependencia queAllNotesViewModelrequiere en su constructor. - Administración del ciclo de vida: el contenedor de DI administra el ciclo de vida de ViewModel según cómo se registró (como transitorio en este caso, proporcionando una nueva instancia).
- Capacidad de prueba: este patrón facilita el intercambio de implementaciones o dependencias simuladas en pruebas.
- Mantenimiento: si las dependencias de ViewModel cambian en el futuro, solo tiene que actualizar la configuración de DI, no todos los lugares donde se crea el ViewModel.
El resto del código permanece igual. El método OnNavigatedTo() todavía llama a LoadAsync() para actualizar la lista de notas cuando el usuario navega a esta página.
Actualizar 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);
}
}
}
}
¿Qué ha cambiado desde el paso 2?
Varios cambios importantes integran las características de inserción de dependencias y mensajería:
-
ViewModel desde el contenedor de inyección de dependencias:
NoteViewModelahora se recupera del contenedor de inyección de dependencias medianteApp.Current.Services.GetService<NoteViewModel>()en elOnNavigatedTo()método en lugar de instanciarse directamente. Este enfoque garantiza que ViewModel reciba automáticamente su dependencia necesariaIFileService. -
Registro de mensajes: el nuevo
RegisterForDeleteMessages()método se suscribe aNoteDeletedMessagemediante elWeakReferenceMessenger. Cuando se elimina una nota (delNoteViewModel.Delete()método ), esta página recibe el mensaje y vuelve a la lista de notas medianteFrame.GoBack(). -
Patrón de mensajería: este patrón muestra el acoplamiento flexible habilitado por el sistema de mensajería del kit de herramientas de MVVM.
NoteViewModelNo es necesario conocer la navegación o la estructura de la página: simplemente envía un mensaje cuando se elimina una nota y la página controla la respuesta de navegación de forma independiente. -
Tiempo de ciclo de vida: se crea una instancia de ViewModel y se produce el registro de mensajes en
OnNavigatedTo(), lo que garantiza que todo se inicializa correctamente cuando la página se activa.
Este patrón separa eficazmente los problemas: ViewModel se centra en las operaciones de datos y lógica de negocios, mientras que la página controla preocupaciones específicas de la interfaz de usuario, como la navegación.