DI 注入文件系统,web平台不支持System.IO.File

master
Cal 2023-12-05 20:11:38 +08:00
parent a02aeca6df
commit 09d8bc7ba8
15 changed files with 246 additions and 52 deletions

View File

@ -0,0 +1,53 @@
using System;
using System.Collections.Generic;
using System.IO;
namespace Notes.Android;
class FileService : IFileService
{
public string Combine(string args0, string args1)
{
return Path.Combine(args0, args1);
}
public string GetRandomFileName()
{
return Path.GetRandomFileName();
}
public IEnumerable<string> EnumerateFiles(string path, string patten)
{
return Directory.EnumerateFiles(path, patten);
}
public string GetFileName(string filename)
{
return Path.GetFileName(filename);
}
public string ReadAllText(string filename)
{
return File.ReadAllText(filename);
}
public DateTime GetLastWriteTime(string filename)
{
return File.GetLastWriteTime(filename);
}
public bool ExistsFile(string filename)
{
return File.Exists(filename);
}
public void Delete(string path)
{
File.Delete(path);
}
public void WriteAllText(string path, string text)
{
File.WriteAllText(path, text);
}
}

View File

@ -3,6 +3,7 @@ using Android.Content.PM;
using Avalonia; using Avalonia;
using Avalonia.Android; using Avalonia.Android;
using Avalonia.ReactiveUI; using Avalonia.ReactiveUI;
using Notes.ViewModels;
namespace Notes.Android; namespace Notes.Android;
@ -16,6 +17,10 @@ public class MainActivity : AvaloniaMainActivity<App>
{ {
protected override AppBuilder CustomizeAppBuilder(AppBuilder builder) protected override AppBuilder CustomizeAppBuilder(AppBuilder builder)
{ {
var services = new Services();
services.Register<IFileService>(new FileService());
App.services = services;
return base.CustomizeAppBuilder(builder) return base.CustomizeAppBuilder(builder)
.WithInterFont() .WithInterFont()
.UseReactiveUI(); .UseReactiveUI();

View File

@ -4,6 +4,9 @@
<RuntimeIdentifier>browser-wasm</RuntimeIdentifier> <RuntimeIdentifier>browser-wasm</RuntimeIdentifier>
<WasmMainJSPath>AppBundle\main.js</WasmMainJSPath> <WasmMainJSPath>AppBundle\main.js</WasmMainJSPath>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<WasmAllowUndefinedSymbols>true</WasmAllowUndefinedSymbols>
<MSBuildDebugEngine>1</MSBuildDebugEngine>
<WasmBuildNative>true</WasmBuildNative>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>

View File

@ -0,0 +1,53 @@
using System;
using System.Collections.Generic;
using System.IO;
namespace Notes.Desktop;
class FileService: IFileService
{
public string Combine(string args0, string args1)
{
return Path.Combine(args0, args1);
}
public string GetRandomFileName()
{
return Path.GetRandomFileName();
}
public IEnumerable<string> EnumerateFiles(string path, string patten)
{
return Directory.EnumerateFiles(path,patten);
}
public string GetFileName(string filename)
{
return Path.GetFileName(filename);
}
public string ReadAllText(string filename)
{
return File.ReadAllText(filename);
}
public DateTime GetLastWriteTime(string filename)
{
return File.GetLastWriteTime(filename);
}
public bool ExistsFile(string filename)
{
return File.Exists(filename);
}
public void Delete(string path)
{
File.Delete(path);
}
public void WriteAllText(string path, string text)
{
File.WriteAllText(path,text);
}
}

View File

@ -1,6 +1,7 @@
using System; using System;
using Avalonia; using Avalonia;
using Avalonia.ReactiveUI; using Avalonia.ReactiveUI;
using Notes.ViewModels;
namespace Notes.Desktop; namespace Notes.Desktop;
@ -10,8 +11,14 @@ sealed class Program
// SynchronizationContext-reliant code before AppMain is called: things aren't initialized // SynchronizationContext-reliant code before AppMain is called: things aren't initialized
// yet and stuff might break. // yet and stuff might break.
[STAThread] [STAThread]
public static void Main(string[] args) => BuildAvaloniaApp() public static void Main(string[] args)
.StartWithClassicDesktopLifetime(args); {
var services = new Services();
services.Register<IFileService>(new FileService());
App.services = services;
BuildAvaloniaApp()
.StartWithClassicDesktopLifetime(args);
}
// Avalonia configuration, don't remove; also used by visual designer. // Avalonia configuration, don't remove; also used by visual designer.
public static AppBuilder BuildAvaloniaApp() public static AppBuilder BuildAvaloniaApp()

View File

@ -8,9 +8,12 @@ namespace Notes;
public partial class App : Application public partial class App : Application
{ {
public static IServices services { get; set; }
public override void Initialize() public override void Initialize()
{ {
AvaloniaXamlLoader.Load(this); AvaloniaXamlLoader.Load(this);
FileSystem.fileService = services.GetSafeAs<IFileService>();
} }
public override void OnFrameworkInitializationCompleted() public override void OnFrameworkInitializationCompleted()

67
Notes/IFileService.cs Normal file
View File

@ -0,0 +1,67 @@
using System;
using System.Collections.Generic;
namespace Notes;
public interface IFileService
{
string Combine(string args0, string args1);
string GetRandomFileName();
IEnumerable<string> EnumerateFiles(string path, string patten);
string GetFileName(string filename);
string ReadAllText(string filename);
DateTime GetLastWriteTime(string filename);
bool ExistsFile(string filename);
void Delete(string path);
void WriteAllText(string path, string text);
}
public static class FileSystem
{
public static IFileService fileService;
public static string Combine(string args0, string args1)
{
return fileService.Combine(args0, args1);
}
public static string GetRandomFileName()
{
return fileService.GetRandomFileName();
}
public static IEnumerable<string> EnumerateFiles(string path, string patten)
{
return fileService.EnumerateFiles(path, patten);
}
public static string GetFileName(string filename)
{
return fileService.GetFileName(filename);
}
public static string ReadAllText(string filename)
{
return fileService.ReadAllText(filename);
}
public static DateTime GetLastWriteTime(string filename)
{
return fileService.GetLastWriteTime(filename);
}
public static bool ExistsFile(string filename)
{
return fileService.ExistsFile(filename);
}
public static void Delete(string filename)
{
fileService.Delete(filename);
}
public static void WriteAllText(string path, string text)
{
fileService.WriteAllText(path, text);
}
}

View File

@ -1,7 +1,6 @@
 
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Linq; using System.Linq;
using Utils; using Utils;
@ -20,37 +19,37 @@ public class Note
{ {
get get
{ {
var appDataDirectory = Path.Combine(AppContext.BaseDirectory, "Data"); var appDataDirectory = FileSystem.Combine(AppContext.BaseDirectory, "Data");
IOUtils.EnsureDirectory(appDataDirectory,false); IOUtils.EnsureDirectory(appDataDirectory,false);
return appDataDirectory; return appDataDirectory;
} }
} }
public Note() public Note()
{ {
Filename = $"{Path.GetRandomFileName()}{Extension}"; Filename = $"{FileSystem.GetRandomFileName()}{Extension}";
Date = DateTime.Now; Date = DateTime.Now;
Text = ""; Text = "";
} }
public void Save() => public void Save() =>
File.WriteAllText(Path.Combine(AppDataDirectory, Filename), Text); FileSystem.WriteAllText(FileSystem.Combine(AppDataDirectory, Filename), Text);
public void Delete() => public void Delete() =>
File.Delete(Path.Combine(AppDataDirectory, Filename)); FileSystem.Delete(FileSystem.Combine(AppDataDirectory, Filename));
public static Note Load(string filename) public static Note Load(string filename)
{ {
filename = Path.Combine(AppDataDirectory, filename); filename = FileSystem.Combine(AppDataDirectory, filename);
if (!File.Exists(filename)) if (!FileSystem.ExistsFile(filename))
throw new FileNotFoundException("Unable to find file on local storage.", filename); throw new InvalidOperationException($"Unable to find file '{filename}' on local storage.");
return return
new() new()
{ {
Filename = Path.GetFileName(filename), Filename = FileSystem.GetFileName(filename),
Text = File.ReadAllText(filename), Text = FileSystem.ReadAllText(filename),
Date = File.GetLastWriteTime(filename) Date = FileSystem.GetLastWriteTime(filename)
}; };
} }
@ -60,13 +59,13 @@ public class Note
string appDataPath = AppDataDirectory; string appDataPath = AppDataDirectory;
// Use Linq extensions to load the *.notes.txt files. // Use Linq extensions to load the *.notes.txt files.
return Directory return FileSystem
// Select the file names from the directory // Select the file names from the directory
.EnumerateFiles(appDataPath, "*.notes.txt") .EnumerateFiles(appDataPath, "*.notes.txt")
// Each file name is used to load a note // Each file name is used to load a note
.Select(filename => Load(Path.GetFileName(filename))) .Select(filename => Load(FileSystem.GetFileName(filename)))
// With the final collection of notes, order them by date // With the final collection of notes, order them by date
.OrderByDescending(note => note.Date); .OrderByDescending(note => note.Date);

View File

@ -14,7 +14,7 @@ public partial class AboutViewModel:ViewModelBase
[RelayCommand] [RelayCommand]
public void ShowAllNotes() public void ShowAllNotes()
{ {
_services.GetAs<MainViewModel>().Navitation<AllNotesViewModel>(this._services); _services.GetSafeAs<MainViewModel>().Navitation<AllNotesViewModel>(this._services);
} }
[Obsolete("Used by designer",true)] [Obsolete("Used by designer",true)]

View File

@ -26,7 +26,7 @@ public partial class AllNotesViewModel: ViewModelBase
{ {
var note = new Note(); var note = new Note();
note.Filename = string.Empty; note.Filename = string.Empty;
this._services.GetAs<MainViewModel>().Navitation<NoteViewModel>(this._services,this,note); this._services.GetSafeAs<MainViewModel>().Navitation<NoteViewModel>(this._services,this,note);
} }
[RelayCommand] [RelayCommand]
public void DeleteNote(ISelectionModel selectionModel) public void DeleteNote(ISelectionModel selectionModel)
@ -43,7 +43,7 @@ public partial class AllNotesViewModel: ViewModelBase
[RelayCommand] [RelayCommand]
public void OpenAbout() public void OpenAbout()
{ {
this._services.GetAs<MainViewModel>().Navitation<AboutViewModel>(this._services); this._services.GetSafeAs<MainViewModel>().Navitation<AboutViewModel>(this._services);
} }
[Obsolete("Used by designer",true)] [Obsolete("Used by designer",true)]

View File

@ -0,0 +1,8 @@
namespace Notes.ViewModels;
public interface IServices
{
void Register<T>(T t);
T? Get<T>();
T GetSafeAs<T>();
}

View File

@ -1,40 +1,10 @@
using System; using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel;
using Utils; using Utils;
namespace Notes.ViewModels; namespace Notes.ViewModels;
public interface IServices
{
void Register<T>(T t);
T? Get<T>();
T GetAs<T>();
}
public sealed class Sevices:IServices
{
private readonly Dictionary<Type, object?> map = new Dictionary<Type, object?>();
public void Register<T>(T t)
{
map.Add(typeof(T), t);
}
public T? Get<T>()
{
map.TryGetValue(typeof(T), out var t);
return (T?)t;
}
public T GetAs<T>()
{
T? foo = this.Get<T?>();
if (foo is { } t)
return t;
throw new InvalidOperationException($"Not find type '{typeof(T)}'");
}
}
public partial class MainViewModel:ViewModelBase public partial class MainViewModel:ViewModelBase
{ {
/// <summary> /// <summary>
@ -47,7 +17,7 @@ public partial class MainViewModel:ViewModelBase
public MainViewModel() public MainViewModel()
{ {
_service = new Sevices(); _service = App.services;
_service.Register(this); _service.Register(this);
_contentViewModel = new AboutViewModel(_service); _contentViewModel = new AboutViewModel(_service);
} }

View File

@ -51,13 +51,13 @@ public partial class NoteViewModel : ViewModelBase
public void Back() public void Back()
{ {
_services.GetAs<MainViewModel>().Navitation<AllNotesViewModel>(this._services); _services.GetSafeAs<MainViewModel>().Navitation<AllNotesViewModel>(this._services);
} }
[RelayCommand] [RelayCommand]
public void OpenNote() public void OpenNote()
{ {
this._services.GetAs<MainViewModel>().Navitation<NoteViewModel>(this._services, this.AllNotesViewModel, this.Note); this._services.GetSafeAs<MainViewModel>().Navitation<NoteViewModel>(this._services, this.AllNotesViewModel, this.Note);
} }

View File

@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
namespace Notes.ViewModels;
public sealed class Services:IServices
{
private readonly Dictionary<Type, object?> map = new Dictionary<Type, object?>();
public void Register<T>(T t)
{
this.map.Add(typeof(T), t);
}
public T? Get<T>()
{
this.map.TryGetValue(typeof(T), out var t);
return (T?)t;
}
public T GetSafeAs<T>()
{
T? foo = this.Get<T?>();
if (foo is { } t)
return t;
throw new InvalidOperationException($"Not find type '{typeof(T)}'");
}
}

View File

@ -9,7 +9,6 @@ public partial class MainView : UserControl
{ {
public MainView() public MainView()
{ {
DataContext = new MainViewModel();
InitializeComponent(); InitializeComponent();
} }
} }