You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Hi and thanks for taking the time to read this. I am new to Avalonia but do have some experience with ASP NET core web MVC. Anyway, the title says its all. I have redone the ToDo list sample from your documentation, added in Reactive toolkit, and tried my best to implement MVVM with DI and Repository pattern + UnitOfWork with a MYSQL Database (Using Dapper as well). Just looking for guidance on if my implementation is correct. So here goes. I will post enough code below and then ask the few questions that i have. I will leave out all my Repository interfaces and UnitOfWork class because I can ask my questions without them. But i can host the repo on GitHub if anyone wants the full code?
ServiceCollectionExtensions.cs
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using MySqlConnector;
using ToDoReactive.Repository;
using ToDoReactive.Repository.IRepository;
using ToDoReactive.ViewModels;
namespace ToDoReactive.Services
{
public static class ServiceCollectionExtensions
{
public static void AddCommonServices(this IServiceCollection collection)
{
//appsettings.json
IConfiguration config = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.Build();
// Get a configuration section
IConfigurationSection section = config.GetSection("ConnectionStrings");
collection.AddMySqlDataSource(section["DefaultConnection"]);
collection.AddSingleton<IUnitOfWork, UnitOfWork>();
collection.AddTransient<MainWindowViewModel>();
}
}
}
App.axaml.cs
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
using Microsoft.Extensions.DependencyInjection;
using ToDoReactive.Services;
using ToDoReactive.ViewModels;
using ToDoReactive.Views;
namespace ToDoReactive
{
public partial class App : Application
{
public override void Initialize()
{
AvaloniaXamlLoader.Load(this);
}
public override void OnFrameworkInitializationCompleted()
{
// Register all the services needed for the application to run
ServiceCollection collection = new ServiceCollection();
collection.AddCommonServices();
// Creates a ServiceProvider containing services from the provided IServiceCollection
var services = collection.BuildServiceProvider();
var vm = services.GetRequiredService<MainWindowViewModel>();
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
desktop.MainWindow = new MainWindow
{
DataContext = vm,
};
}
base.OnFrameworkInitializationCompleted();
}
}
}
In Models folder i have ToDoITem.cs and ToDoItemMethods.cs
using System.ComponentModel.DataAnnotations.Schema;
using System.ComponentModel.DataAnnotations;
namespace ToDoReactive.Models
{
[Table("to_do_list")]
public class ToDoItem
{
public ToDoItem(bool isChecked, string content, int? id = null)
{
IsChecked = isChecked;
Content = content;
Id = id;
}
public ToDoItem() { }
[Key]
[Column("Id")]
public int? Id { get; set; }
[Column("IsChecked")]
public bool IsChecked { get; set; }
[Column("Content")]
public string? Content { get; set; }
}
}
using System.Collections.Generic;
using System.Threading.Tasks;
using ToDoReactive.Repository.IRepository;
namespace ToDoReactive.Models
{
public class ToDoItemMethods
{
private readonly IUnitOfWork _unitOfWork;
public ToDoItemMethods(IUnitOfWork unitOfWork)
{
_unitOfWork = unitOfWork;
}
public async Task<bool> AddToDo(ToDoItem item)
{
bool success = await _unitOfWork.ToDoList.Add(item);
return success;
}
public async Task<List<ToDoItem>> GetAllToDo()
{
return (List<ToDoItem>)await _unitOfWork.ToDoList.GetAll();
}
public async Task<bool> Delete(int? id)
{
if(id == null)
{
return false;
}
return await _unitOfWork.ToDoList.Delete(id);
}
public async Task<bool> DeleteAll()
{
return await _unitOfWork.ToDoList.DeleteAll();
}
public async Task<bool> Update(ToDoItem item)
{
return await _unitOfWork.ToDoList.Update(item);
}
}
}
ToDoItemViewModel.cs
using ReactiveUI;
namespace ToDoReactive.ViewModels
{
public class ToDoItemViewModel : ViewModelBase
{
private bool _isChecked;
private string? _content;
private int? _id;
public int? Id
{
get => _id;
set => this.RaiseAndSetIfChanged(ref _id, value);
}
public string? Content
{
get => _content;
set => this.RaiseAndSetIfChanged(ref _content, value);
}
public bool IsChecked
{
get => _isChecked;
set => this.RaiseAndSetIfChanged(ref _isChecked, value);
}
}
}
And MainWindowViewModel.cs -- (My DI is working so guess i set that up correctly)
using ReactiveUI;
using System;
using System.Collections.ObjectModel;
using System.Collections.Generic;
using System.Reactive;
using ToDoReactive.Models;
using ToDoReactive.Repository.IRepository;
using System.Diagnostics;
namespace ToDoReactive.ViewModels
{
public class MainWindowViewModel : ViewModelBase
{
private readonly IUnitOfWork _unitOfWork;
private readonly ToDoItemMethods _toDoItemMethods;
public MainWindowViewModel(IUnitOfWork unitOfWork)
{
_unitOfWork = unitOfWork;
_toDoItemMethods = new ToDoItemMethods(_unitOfWork);
IObservable<bool> CanAddItem = this.WhenAnyValue(
x => x.NewItemContent,
x => !string.IsNullOrEmpty(x) && x.Length >= 3
);
AddItemCommand = ReactiveCommand.Create(() =>
{
AddItem();
},CanAddItem);
RemoveItemCommand = ReactiveCommand.Create((ToDoItemViewModel item) => {
RemoveItem(item);
});
RemoveAllCommand = ReactiveCommand.Create(() => {
RemoveAll();
});
SaveProgressCommand = ReactiveCommand.Create((ToDoItemViewModel item) => {
SaveProgress(item);
});
GetAllToDo();
}
public ReactiveCommand<Unit, Unit> AddItemCommand { get; }
public ReactiveCommand<ToDoItemViewModel, Unit> RemoveItemCommand { get; }
public ReactiveCommand<Unit, Unit> RemoveAllCommand { get; }
public ReactiveCommand<ToDoItemViewModel, Unit> SaveProgressCommand { get; }
public ObservableCollection<ToDoItemViewModel> ToDoItems { get; } = new ObservableCollection<ToDoItemViewModel>();
private string? _newItemContent;
public string? NewItemContent
{
get => _newItemContent;
set => this.RaiseAndSetIfChanged(ref _newItemContent, value);
}
private async void GetAllToDo()
{
List<ToDoItem> allToDo = await _toDoItemMethods.GetAllToDo();
foreach (ToDoItem item in allToDo)
{
ToDoItemViewModel toDoItemViewModel = new()
{
IsChecked = item.IsChecked,
Content = item.Content,
Id = item.Id,
};
ToDoItems.Add(toDoItemViewModel);
}
}
private async void AddItem()
{
// Add new item to data base
ToDoItem newItem = new ToDoItem(false, NewItemContent);
bool success = await _toDoItemMethods.AddToDo(newItem);
// if sussess get all from database to update UI
if (success)
{
//clear list first otherwise list will be displayed twice
ToDoItems.Clear();
GetAllToDo();
}
// reset the NewItemContent
NewItemContent = null;
}
private async void RemoveItem(ToDoItemViewModel item)
{
bool success = await _toDoItemMethods.Delete(item.Id);
if (success)
{
//clear list first otherwise list will be displayed twice
ToDoItems.Clear();
GetAllToDo();
}
}
private async void RemoveAll()
{
bool success = await _toDoItemMethods.DeleteAll();
if (success)
{
ToDoItems.Clear();
}
}
private async void SaveProgress(ToDoItemViewModel item)
{
ToDoItem updateItem = new ToDoItem(item.IsChecked, item.Content, item.Id);
bool success = await _toDoItemMethods.Update(updateItem);
if (success)
{
//clear list first otherwise list will be displayed twice
ToDoItems.Clear();
GetAllToDo();
}
}
}
}
Ok now the few Questions i have
Not sure this follows the correct pattern but it seemed to work best to have ToDoItem.cs as a class that only models the data (just the columns that map to the database) and have the class ToDoItemMethods.cs handle all the CRUD methods. Reason being is I needed a way to pass _unitOfWork to the class that handles CRUD methods but also still needed a convenient way to pass the ToDoItem objects from the ViewModel to the Model for data processing and having UnitOfWork in the ToDoItem class constructor really complicated that. Is this an ok approach or am i missing something obvious?
In my GetAllToDo method in the MainWindowViewModel class I needed to map the ToDoItem's that get returned from the database back into ToDoItemViewModel's and add them to the ToDoItems ObservableCollection. I could not find a way to cast a List of ToDoItem's to ObservableCollection of ToDoItemViewModel's. Is there a better way? I guess in general is seems like there is a lot of mapping between ToDoItem objects and the ToDoItemViewModel objects. Guessing that is really the only option?
is Singleton correct lifetime for UnitOfWork. I know in web apps we use Scoped but i would assume to use Singleton for desktop? And what lifetime should be used for the AddMySqlDataSource ? (in code above connection Lifetime defaults to Transient and dataSourceLifetime defaults to Singleton) Maybe Singleton for both connection and datasource? Any knowledge here is much appreciated.
It would be great if we could have an official Avalonia MVVM guide on adding in a real database with DI and Repository + UnitOfWork pattern. Seems like a question that gets asked a lot and I haven't found too much information on the best way to do it. If the code i have is written well enough I wouldn't mind sharing the full repo. or maybe even helping write the tutorial if i have time.
Hope this all makes sense and thank you in advanced for taking a look. I know the post is a bit long..
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
-
Hi and thanks for taking the time to read this. I am new to Avalonia but do have some experience with ASP NET core web MVC. Anyway, the title says its all. I have redone the ToDo list sample from your documentation, added in Reactive toolkit, and tried my best to implement MVVM with DI and Repository pattern + UnitOfWork with a MYSQL Database (Using Dapper as well). Just looking for guidance on if my implementation is correct. So here goes. I will post enough code below and then ask the few questions that i have. I will leave out all my Repository interfaces and UnitOfWork class because I can ask my questions without them. But i can host the repo on GitHub if anyone wants the full code?
ServiceCollectionExtensions.cs
App.axaml.cs
In Models folder i have ToDoITem.cs and ToDoItemMethods.cs
ToDoItemViewModel.cs
And MainWindowViewModel.cs -- (My DI is working so guess i set that up correctly)
Ok now the few Questions i have
Not sure this follows the correct pattern but it seemed to work best to have ToDoItem.cs as a class that only models the data (just the columns that map to the database) and have the class ToDoItemMethods.cs handle all the CRUD methods. Reason being is I needed a way to pass _unitOfWork to the class that handles CRUD methods but also still needed a convenient way to pass the ToDoItem objects from the ViewModel to the Model for data processing and having UnitOfWork in the ToDoItem class constructor really complicated that. Is this an ok approach or am i missing something obvious?
In my GetAllToDo method in the MainWindowViewModel class I needed to map the ToDoItem's that get returned from the database back into ToDoItemViewModel's and add them to the ToDoItems ObservableCollection. I could not find a way to cast a List of ToDoItem's to ObservableCollection of ToDoItemViewModel's. Is there a better way? I guess in general is seems like there is a lot of mapping between ToDoItem objects and the ToDoItemViewModel objects. Guessing that is really the only option?
is Singleton correct lifetime for UnitOfWork. I know in web apps we use Scoped but i would assume to use Singleton for desktop? And what lifetime should be used for the AddMySqlDataSource ? (in code above connection Lifetime defaults to Transient and dataSourceLifetime defaults to Singleton) Maybe Singleton for both connection and datasource? Any knowledge here is much appreciated.
It would be great if we could have an official Avalonia MVVM guide on adding in a real database with DI and Repository + UnitOfWork pattern. Seems like a question that gets asked a lot and I haven't found too much information on the best way to do it. If the code i have is written well enough I wouldn't mind sharing the full repo. or maybe even helping write the tutorial if i have time.
Hope this all makes sense and thank you in advanced for taking a look. I know the post is a bit long..
Beta Was this translation helpful? Give feedback.
All reactions