Not
Åtkomst till denna sida kräver auktorisation. Du kan prova att logga in eller byta katalog.
Åtkomst till denna sida kräver auktorisation. Du kan prova att byta katalog.
Beroendeinmatning (DI) hjälper dig att hantera livscykeln för dina ViewModels och tjänster. Det gör koden mer testbar och enklare att underhålla. I det här steget konfigurerar du DI i din app och uppdaterar dina modeller så att de använder en filtjänst för filåtgärder.
Mer bakgrund om .NET-beroendeinmatningsramverket finns i .NET-beroendeinmatning och självstudien Använd beroendeinmatning i .NET .
Installera Microsoft.Extensions-paket
Lägg till DI-stöd i dina projekt.
Installera
Microsoft.Extensions.DependencyInjectioni både WinUINotes - och WinUINotes.Bus-projekt :dotnet add WinUINotes package Microsoft.Extensions.DependencyInjection dotnet add WinUINotes.Bus package Microsoft.Extensions.DependencyInjection
Skapa ett filtjänstgränssnitt och en implementering
I Projektet WinUINotes.Bus skapar du en ny mapp med namnet Tjänster.
Lägg till en gränssnittsfil
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(); } }Filtjänstgränssnittet definierar metoder för filåtgärder. Den abstraherar bort information om filhantering från ViewModels och Models. Parametrarna och returvärdena är antingen grundläggande .NET-typer eller gränssnitt. Den här designen säkerställer att tjänsten enkelt kan hånas eller ersättas i enhetstester, vilket främjar lös koppling och testbarhet.
Lägg till implementeringsfilen
WindowsFileService.cs: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); } } }
Implementeringen WindowsFileService ger konkreta filåtgärder med hjälp av Api:erna Windows Runtime (WinRT) och .NET Storage:
-
Konstruktorinmatning: Tjänsten accepterar en
IStorageFolderi konstruktorn. Med den här metoden kan du konfigurera lagringsplatsen när du instansierar tjänsten. Den här metoden gör tjänsten flexibel och testbar. -
CreateOrUpdateFileAsync(): Den här metoden använderTryGetItemAsync()för att kontrollera om en fil redan finns. Om den gör det uppdaterar metoden den befintliga filen. Annars skapas en ny fil med hjälp avCreateFileAsync(). Den här metoden hanterar både scenarier för skapa och uppdatera i en och samma metod. -
DeleteFileAsync(): Innan du tar bort en fil verifierar den här metoden att filen finns med hjälpTryGetItemAsync()av . Den här kontrollen förhindrar att undantag utlöses vid försök att ta bort filer som inte finns. -
FileExists(): Den här synkrona metoden söker efter filexistens genom att anropa asynkroniseringenTryGetItemAsync()och blockera med.Result. Även om den här metoden vanligtvis inte rekommenderas används denCanDelete()här för att stödja valideringsmetoden i ViewModel, som måste vara synkron. -
Metoder för lagringsobjekt: Metoderna
GetStorageItemsAsync()ochGetTextFromFileAsync()ger åtkomst till filer och deras innehåll med hjälp av WinRT-lagrings-API:er. Med de här metoderna kan modellerna läsa in och räkna upp anteckningar.
Genom att implementera IFileService gränssnittet kan du enkelt ersätta den här klassen med en falsk implementering för testning eller en annan lagringsprovider om det behövs.
Läs mer i dokumenten:
Konfigurera beroendeinmatning i App.xaml.cs
Innan du uppdaterar modellerna och ViewModels för att använda filtjänsten, konfigurerar du beroendeinjektion så att tjänsten kan lösas och injiceras i konstruktorerna.
App.xaml.cs Uppdatera filen för att konfigurera DI-containern:
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;
}
Den här konfigurationen konfigurerar containern för beroendeinmatning med alla nödvändiga tjänster:
-
ConfigureServices()metod: En statisk metod som skapar och konfigurerar tjänstsamlingen. Genom att separera den här metoden blir konfigurationen mer underhållsbar och enklare att testa. -
Servicesegenskap: En instansegenskap som innehållerIServiceProvider. Konstruktorn anger den här egenskapen genom att anropaConfigureServices(). -
App.Currentstatisk egenskap: Ger bekväm åtkomst till den aktuellaAppinstansen, vilket är användbart när modeller eller andra klasser behöver åtkomst till tjänstleverantören. -
IFileServiceregistrering: AnvändsActivatorUtilities.CreateInstanceför att skapa enWindowsFileServiceinstans med parameternApplicationData.Current.LocalFolder. Med den här metoden kan konstruktorparametern matas in vid registreringen. Registrera tjänsten som en singleton eftersom filåtgärderna är tillståndslösa och en enda instans kan delas mellan programmet. - ViewModels-registrering: Registrera båda ViewModels som tillfälliga, vilket innebär att en ny instans skapas varje gång en begärs. Den här metoden säkerställer att varje sida får en egen ViewModel-instans med rent tillstånd.
Modeller och andra klasser kan komma åt tjänstleverantören via App.Current.Services.GetService() för att hämta registrerade tjänster vid behov.
Läs mer i dokumenten:
Uppdatera modeller för att använda filtjänsten
Nu när filtjänsten är tillgänglig via beroendeinmatning uppdaterar du modellklasserna för att använda den. Modellerna tar emot filtjänsten och använder den för alla filåtgärder.
Uppdatera note-modellen
Note Uppdatera klassen för att acceptera filtjänsten och använd den för att spara, ta bort och filexistensåtgärder:
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);
}
}
Modellen Note tar nu emot filtjänsten via konstruktorinmatning:
-
Konstruktor: Accepterar en
IFileServiceparameter, vilket gör beroendet explicit och obligatoriskt. Den här designen främjar testbarheten och säkerställer att modellen alltid har åtkomst till den filtjänst den behöver. - Filnamnsgenerering: Konstruktorn genererar automatiskt ett unikt filnamn med hjälp av den aktuella tidsstämpeln, vilket säkerställer att varje anteckning har ett distinkt filnamn.
-
Filåtgärder: metoderna
SaveAsync(),DeleteAsync()ochNoteFileExists()delegerar alla till den inmatade filtjänsten, vilket gör att modellen fokuserar på att samordna åtgärder i stället för att implementera fil-I/O-information.
Den här metoden eliminerar behovet av att modellen använder tjänstlokaliserarmönstret (direktåtkomst App.Services ), vilket förbättrar testbarheten och gör beroenden tydliga.
Uppdatera AllNotes-modellen
AllNotes Uppdatera klassen för att läsa in anteckningar från lagring med hjälp av filtjänsten:
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);
}
}
}
}
Modellen AllNotes tar emot filtjänsten via konstruktorinmatning, precis som Note modellen. Eftersom den här klassen finns i WinUINotes.Bus-projektet kan den inte komma åt App.Current.Services från WinUINotes-projektet (på grund av begränsningar i projektreferenser).
Metoden LoadNotes() anropar den privata GetFilesInFolderAsync() metoden för att rekursivt räkna upp alla filer i den lokala lagringsmappen och dess undermappar. För varje lagringsobjekt:
- Om det är en mapp anropar metoden rekursivt sig själv för att bearbeta mappens innehåll
- Om det är en fil skapas en ny
Noteinstans med filtjänsten matad in - Anteckningens
Filenameär inställd på filens namn - Anteckningens
Textfylls i genom att filens innehåll läses med hjälp avGetTextFromFileAsync() - Anteckningens
Dateär inställd på filens skapandedatum - Anteckningen läggs till i den
Notesobserverbara samlingen
Den här metoden säkerställer att alla anteckningar som läses in från lagring har åtkomst till den filtjänst de behöver för framtida åtgärder för att spara och ta bort.
Uppdatera ViewModels för att använda filtjänsten
När modellerna nu använder filtjänsten måste du uppdatera ViewModels. Men eftersom modellerna hanterar filåtgärderna direkt fokuserar ViewModels främst på att orkestrera modellerna och hantera observerbara egenskaper.
Uppdatera AllNotesViewModel
Uppdatera AllNotesViewModel för att fungera med den uppdaterade AllNotes-modellen.
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);
}
}
}
}
Vad har ändrats sedan steg 2?
Nyckeländringen är tillägget av parametern IFileService till konstruktorn. ViewModel instansierades i steg 2 med en parameterlös konstruktor (AllNotes) (allNotes = new AllNotes()). Nu när AllNotes modellen kräver att filtjänsten utför sina åtgärder tar ViewModel emot IFileService via konstruktorinmatningen och skickar den till modellen.
Den här ändringen upprätthåller rätt beroendeflöde – filtjänsten matas in på den översta nivån (ViewModel) och flödar ned till modellen. ViewModel fortsätter att fokusera på att samordna inläsningsprocessen och hålla den observerbara Notes samlingen synkroniserad med modellens data, utan att behöva känna till implementeringsinformationen om hur filer läses in.
Uppdatera NoteViewModel
Uppdatera NoteViewModel för att infoga filjänsten och använd MVVM Toolkits meddelandesystem.
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();
}
}
}
Vad har ändrats sedan steg 2?
Flera viktiga ändringar stöder beroendeinmatning och kommunikation mellan ViewModel:
Filtjänstinmatning: Konstruktorn accepterar
IFileServicenu som en parameter och lagrar den i ett fält. Den här tjänsten skickas tillNotemodellen när du skapar nya instanser, vilket säkerställer att alla anteckningar kan utföra filåtgärder.WeakReferenceMessenger: Metoden
Delete()använder nu MVVM ToolkitsWeakReferenceMessenger.Default.Send()för att sända enNoteDeletedMessageefter att ha raderat en anteckning. Den här metoden möjliggör lös koppling mellan ViewModels – andra delar av programmet (till exempelNotePage) kan lyssna efter det här meddelandet och svara på rätt sätt (till exempel genom att gå tillbaka till listan med anteckningar, som har uppdaterats) utanNoteViewModelatt behöva en direkt referens till dem.
WeakReferenceMessenger Är en viktig funktion i MVVM Toolkit som förhindrar minnesläckor med hjälp av svaga referenser. Komponenter kan prenumerera på meddelanden utan att skapa starka referenser som förhindrar skräpinsamling.
Läs mer i dokumenten:
Skapa klassen NoteDeletedMessage
Behöver WeakReferenceMessenger en meddelandeklass för att skicka mellan komponenter. Skapa en ny klass som representerar anteckningsborttagningshändelsen:
I Projektet WinUINotes.Bus lägger du till en ny klassfil
NoteDeletedMessage.cs:using CommunityToolkit.Mvvm.Messaging.Messages; using WinUINotes.Models; namespace WinUINotes { public class NoteDeletedMessage : ValueChangedMessage<Note> { public NoteDeletedMessage(Note note) : base(note) { } } }
Den här meddelandeklassen ärver från ValueChangedMessage<Note>, vilket är en specialiserad meddelandetyp som tillhandahålls av MVVM Toolkit för att skicka meddelanden om värdeändring. Konstruktorn accepterar en Note och skickar den till basklassen, vilket gör den tillgänglig för meddelandemottagare via egenskapen Value . När NoteViewModel skickar det här meddelandet tar alla komponenter som prenumererar NoteDeletedMessage på det och kan komma åt den borttagna anteckningen via egenskapen Value .
Så här fungerar meddelanden i MVVM Toolkit:
-
Avsändare: Metoden
NoteViewModel.Delete()skickar meddelandet med hjälpWeakReferenceMessenger.Default.Send(new NoteDeletedMessage(note))av . -
Mottagare: Sidor (till exempel
NotePage) kan registrera sig för att ta emot meddelanden genom att implementeraIRecipient<NoteDeletedMessage>och registrera med messenger. När meddelandet tas emot kan sidan gå tillbaka till listan med alla anteckningar. - Lös koppling: Avsändaren behöver inte veta vem (om någon) lyssnar. Mottagaren behöver ingen direkt referens till avsändaren. Den här konfigurationen håller dina komponenter oberoende och testbara.
Den svaga referensmetoden innebär att om en komponent är skräpinsamling rensas dess meddelandeprenumeration automatiskt utan att orsaka minnesläckor.
Uppdatera sidor för att använda beroendeinjektion
Uppdatera sidkonstruktorerna för att ta emot ViewModels via DI.
Uppdatera 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();
}
}
}
}
Vad har ändrats sedan steg 2?
Appen hämtar AllNotesViewModel nu från containern för beroendeinmatning genom att använda App.Current.Services.GetService<AllNotesViewModel>() i stället för att skapa den direkt med new AllNotesViewModel(). Den här metoden har flera fördelar:
-
Automatisk beroendelösning: DI-containern tillhandahåller automatiskt det
IFileServiceberoende somAllNotesViewModelbehöver i konstruktorn. - Livscykelhantering: DI-containern hanterar ViewModels livscykel enligt hur den registrerades (som en tillfällig i det här fallet, vilket ger en ny instans).
- Testbarhet: Det här mönstret gör det enklare att växla implementeringar eller simulera beroenden i tester.
- Underhåll: Om ViewModels beroenden ändras i framtiden behöver du bara uppdatera DI-konfigurationen, inte alla platser där ViewModel skapas.
Resten av koden förblir densamma. Metoden OnNavigatedTo() anropar LoadAsync() fortfarande för att uppdatera anteckningslistan när användaren navigerar till den här sidan.
Uppdatera 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);
}
}
}
}
Vad har ändrats sedan steg 2?
Flera viktiga ändringar integrerar funktioner för beroendeinmatning och meddelanden:
-
ViewModel från DI-container:
NoteViewModelhämtas nu från beroendeinjektionscontainern med hjälp avApp.Current.Services.GetService<NoteViewModel>()iOnNavigatedTo()-metoden istället för att instansieras direkt. Den här metoden säkerställer att ViewModel automatiskt erhåller sitt nödvändigaIFileServiceberoende. -
Meddelanderegistrering: Den nya metoden
RegisterForDeleteMessages()prenumererar påNoteDeletedMessagegenom att användaWeakReferenceMessenger. När en anteckning tas bort (frånNoteViewModel.Delete()-metoden) tar den här sidan emot meddelandet och navigerar tillbaka till listan med alla anteckningar med hjälp avFrame.GoBack(). -
Meddelandemönster: Det här mönstret visar den lösa kopplingen som aktiveras av MVVM Toolkits meddelandesystem. Behöver
NoteViewModelinte känna till navigeringen eller sidstrukturen – den skickar bara ett meddelande när en anteckning tas bort och sidan hanterar navigeringssvaret oberoende av varandra. -
Livscykeltidsinställning: ViewModel instansieras och meddelanderegistrering sker i
OnNavigatedTo(), vilket säkerställer att allt initieras korrekt när sidan blir aktiv.
Det här mönstret separerar problem effektivt: ViewModel fokuserar på affärslogik och dataåtgärder, medan sidan hanterar användargränssnittsspecifika problem som navigering.
Windows developer