StateHasChanged() does not update database values - c#

Using the code you posted in your new answer. But same error when I add a file and the method UpdateVergadering is called.
vergaderingRepository:
private readonly IDbContextFactory<ApplicationDbContext> _factory;
public VergaderingRepository(IDbContextFactory<ApplicationDbContext> dbContextFactory, IDbContextFactory<ApplicationDbContext> factory)
{
_factory = factory;
}
public async ValueTask<int> UpdateVergadering(Vergadering vergadering)
{
using var dbContext = _factory.CreateDbContext();
dbContext.Set<Vergadering>().Update(vergadering);
return await dbContext.SaveChangesAsync();
}
public async ValueTask<Vergadering> GetVergaderingVoorLiveNotulenAsync (int vergaderingId)
{
using var dbContext = _factory.CreateDbContext();
dbContext.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
return await dbContext.Set<Vergadering>().SingleOrDefaultAsync(x => x.Id == vergaderingId);
}
The error I get:
System.InvalidOperationException: 'The instance of entity type 'Bestuurslid' cannot be tracked because another instance with the same key value for {'Id'} is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached.

You code never completes the component render, you loop within OnInitialized. You also confuse OnInitialized and OnInitializedAsync.
Here's a demo page that shows how to use the System.Timers.Timer with an event handler hooked up to the timer to handle the data get and UI update. OnInitializedAsync does the initial data get, sets up the timer, wires up the event handler and completes.
#page "/"
#implements IDisposable
<PageTitle>Index</PageTitle>
<h1>Hello, world!</h1>
Welcome to your new app.
<div class="alert alert-success">
#_message
</div>
#code {
private string? _message = "Not Set";
private System.Timers.Timer _timer = new System.Timers.Timer(2000);
protected async override Task OnInitializedAsync()
{
// Initial data get
_message = await GetData();
// set uo the timer and hook up the event handler
_timer.AutoReset = true;
_timer.Elapsed += this.OnTimerElapsed;
_timer.Start();
}
// Event handler for the timer
private async void OnTimerElapsed(object? sender, System.Timers.ElapsedEventArgs e)
{
_message = await GetData();
// must call this like this as the timer may be running on a different thread
await this.InvokeAsync(StateHasChanged);
}
private async ValueTask<string> GetData()
{
// emulate an async call to a Db or API
await Task.Delay(100);
return DateTime.Now.ToLongTimeString();
}
// Dispose of the event handler when the Renderer has finished with the component
public void Dispose()
=> _timer.Elapsed -= this.OnTimerElapsed;
}
Update on DbContexts and Async behaviour
Set up a DbContextFactory:
services.AddDbContextFactory<MyDbContext>(
options =>
options.UseSqlServer(#"Server=(localdb)\mssqllocaldb;Database=Test"));
And then use the factory to get Db context instances as you need them.
public sealed class MeetingBroker
{
private readonly IDbContextFactory<MyDbContext> _factory;
public MeetingBroker(IDbContextFactory<MyDbContext> factory)
{
_factory = factory;
}
public ValueTask<Vergadering> GetVergaderingByIdAsync(int vergaderingId)
{
using var dbContext = _factory.CreateDbContext();
// if you aren't editing the data then you don't need tracking. Imporves performance
dbContext.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
return await dbContext.Set<TRecord>().SingleOrDefaultAsync(x => x.Id == vergaderingId));
}
DbContextFctory Update
You've implemented the factory, but not the "Unit of Work" pattern. You're implementation uses the same context for all activity within the repository and will cause usage clashes.
Blazor lives in an async world so you need to code for situations where you have parallel processes running on the same resources.
Your Repository Pattern should look like this:
private readonly IDbContextFactory<ApplicationDbContext> _factoy;
public VergaderingRepository(IDbContextFactory<ApplicationDbContext> dbContextFactory)
{
// assigned the factory not a context
_factory = dbContextFactory;
}
public async ValueTask<Vergadering> GetVergaderingVoorLiveNotulenAsync (int vergaderingId)
{
// creates a context for each transaction
using dbContext = dbContextFactory.CreateDbContext();
return await dbContext.Set<Vergadering>().SingleOrDefaultAsync(x => x.Id == vergaderingId);
}

private readonly IDbContextFactory<ApplicationDbContext> _factory;
public VergaderingRepository(IDbContextFactory<ApplicationDbContext> dbContextFactory, IDbContextFactory<ApplicationDbContext> factory)
=> _factory = factory;
public async ValueTask<Vergadering> GetVergaderingVoorLiveNotulenAsync (int vergaderingId)
{
using var dbContext = _factory.CreateDbContext();
// Turning off tracking as this is only a query.
dbContext.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
return await dbContext.Set<Vergadering>().SingleOrDefaultAsync(x => x.Id == vergaderingId);
}
public async ValueTask<int> UpdateVergadering(Vergadering vergadering)
{
using var dbContext = _factory.CreateDbContext();
// Tracking is required for updates
//dbContext.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
dbContext.Set<Vergadering>().Update(vergadering);
return await dbContext.SaveChangesAsync();
}

Related

ObjectDisposedException: How to use an injected DbCcontext on a background thread?

I was working with SignalR, and created timer that will execute following code where I need to get the number of unread messages from database
[Route("api/[controller]")]
[ApiController]
public class PorukeHubController : ControllerBase
{
private readonly IHubContext<PorukeHub> _hub;
private readonly TimerManager _timer;
private DnevnikContext _context { get; set; }
public PorukeHubController(IHubContext<PorukeHub> hub,
TimerManager timer, DnevnikContext context)
{
_hub = hub;
_timer = timer;
_context = context;
}
[HttpGet]
public IActionResult Get()
{
var currentUserId = 1;
if (!_timer.IsTimerStarted)
_timer.PrepareTimer(() =>
{
var unreadMessages = _context.Messages
.Where(p => p.RecieverID == currentUserId && p.isRead == false)
.Count();
_hub.Clients.All.SendAsync("checkForMessages", unreadMessages);
});
return Ok(new { Message = "Request Completed" });
}
Unfortunately, I get the following error when trying to access _context:
System.ObjectDisposedException: 'Cannot access a disposed context
instance. A common cause of this error is disposing a context instance
that was resolved from dependency injection and then later trying to
use the same context instance elsewhere in your application. This may
occur if you are calling 'Dispose' on the context instance, or
wrapping it in a using statement. If you are using dependency
injection, you should let the dependency injection container take care
of disposing context instances. ObjectDisposed_ObjectName_Name'
I'm very confused on what steps should I take to solve this, I'm not yet familiar that much with DI too
Any help would be appreciated
You should turn your method into an async method and await the database call.
public async Task<IActionResult> Get()
{
...
var unreadMessages = await _context.Messages.Where(p => p.RecieverID == currentUserId && p.isRead == false).Count();
...
}
A DbContext only lives for a short period of time and its lifetime ends at the end of the web request for which it is created.
The delegate, however, that you use to initialize the TimerManager stores a reference to that DbContext likely for the duration of the application (assuming that TimerManager is a Singleton). But since that DbContext is disposed of soon after you initialized the TimerManager it becomes unusable.
In general, you should prevent moving injected dependencies from thread to thread (except in case those threads are part of a sequentially executed asynchronous operation), because the consuming code (i.e. your controller) doesn't know whether or not it is safe to do so.
This means that, instead of reusing the same DbContext, the timer should get its own instance and, preferable, get a fresh DbContext instance every time the timer is triggered. You should see each timer tick as a new request, just like every call to a controller is a new (web) request. And the general rule of thumb is that each new request gets its own container scope.
What this means is that, instead of reusing the same DbContext instance, you should wrap each tick in a container scope / IServiceScope and resolve the DbContext from that scope.
For instance:
private readonly TimerManager _timer;
private readonly IServiceProvider _provider;
public PorukeHubController(TimerManager timer, IServiceProvider provider)
{
_timer = timer;
_provider = provider;
}
[HttpGet]
public IActionResult Get()
{
var currentUserId = 1;
if (!_timer.IsTimerStarted)
_timer.PrepareTimer(() =>
{
using (var scope = this.provider.CreateScope())
{
var sp = scope.ServiceProvider;
var context = sp.GetRequiredService<DnevnikContext>();
var hub = sp.GetRequiredService<IHubContext<PorukeHub>>();
var unreadMessages = context.Messages
.Where(p => p.RecieverID == currentUserId && p.isRead == false)
.Count();
hub.Clients.All.SendAsync("checkForMessages", unreadMessages);
}
});
return Ok(new { Message = "Request Completed" });
}
Although the code above fixes the initial problem of letting the DbContext go out of scope, it poses a new problem, which is more fundamental in nature, which is that application components shouldn't depend on the (IServiceProvider) DI Container. This is called the Service Locator anti-pattern.
This means that you shouldn't do this type of initialization inside your controllers, but instead move this to the application startup path; a.k.a. the Composition Root.
This can be done, for instance, by introducing a new abstraction:
private readonly IMessageInitializer _initializer;
public PorukeHubController(IMessageInitializer initializer)
{
__initializer = _initializer;
}
[HttpGet]
public IActionResult Get()
{
_messageInitializer.Initialize();
return Ok(new { Message = "Request Completed" });
}
In this code example the new IMessageInitializer hides the complexity of initialization of the timer, the querying of the database and the calling of the hub from the controller.
Inside your Composition Root you can now define an implementation with the original code:
public class ScheduledMessageInitializer : IMessageInitializer
{
private readonly TimerManager _timer;
private readonly IServiceProvider _provider;
public ScheduledMessageInitializer(
TimerManager timer, IServiceProvider provider)
{
_timer = timer;
_provider = provider;
}
public void Initialize()
{
var currentUserId = 1;
if (!_timer.IsTimerStarted)
_timer.PrepareTimer(() => {
using (var scope = this.provider.CreateScope())
{
var sp = scope.ServiceProvider;
var context = sp.GetRequiredService<DnevnikContext>();
var hub = sp.GetRequiredService<IHubContext<PorukeHub>>();
var unreadMessages = context.Messages
.Where(p => p.RecieverID == currentUserId && p.isRead == false)
.Count();
hub.Clients.All.SendAsync("checkForMessages", unreadMessages);
}
});
}
}
This class can be registered as follows:
// This assumes that TimerManager is a singleton as well.
services.AddSingleton<IMessageInitializer, ScheduledMessageInitializer>();
This still poses a (smaller) design issue, which is that with DI you should strive to keep the application's business logic out of the Composition Root. It should only contain the required infrastructural code for the application to execute. The querying of the database and sending it to the hub can be considered business logic; not infrastructure.
That would mean that one last refactoring is in place: you should extract that logic out of the ScheduledMessageInitializer and place it in a new application component:
public class UnreadMessageChecker
{
private readonly DnevnikContext _context;
private readonly IHubContext<PorukeHub> _hub;
public UnreadMessageChecker(DnevnikContext context, IHubContext<PorukeHub> hub)
{
_context = context;
_hub = hub;
}
public async Task CheckAsync()
{
var unreadMessages = context.Messages
.Where(p => p.RecieverID == currentUserId && p.isRead == false)
.Count();
// I noticed how you called SendAsync. You should await it, otherwise
// you'll get into the same trouble as where you started out with:
// with an ObjectDisposedExcetion.
await hub.Clients.All.SendAsync("checkForMessages", unreadMessages);
}
}
services.AddTransient<UnreadMessageChecker>();
This new component can be resolved from the ScheduledMessageInitializer, which reduces it to the following:
public class ScheduledMessageInitializer : IMessageInitializer
{
private readonly TimerManager _timer;
private readonly IServiceProvider _provider;
public ScheduledMessageInitializer(
TimerManager timer, IServiceProvider provider)
{
_timer = timer;
_provider = provider;
}
public void Initialize()
{
var currentUserId = 1;
if (!_timer.IsTimerStarted)
_timer.PrepareTimer(async () =>
{
using (var scope = this.provider.CreateScope())
{
var checker = scope.ServiceProvider
.GetRequiredService<UnreadMessageChecker>();
await checker.CheckAsync();
}
});
}
}
There might still be other issues with your code. For instance, it seems weird to me that you have a currentUserId (which is runtime data, changing on each request), while using it to initialize the timer with; unless that timer isn't a Singleton. But if the timer isn't a singleton, that would mean that would be initializing an endless number of timers, which likely isn't a good idea as well.
Another issue is that, if the TimerManager is indeed singleton, there might be a race condition while initializing. Is the TimerManager thread-safe? What would happen when it gets initialized twice simultaneously? Would that cause problems. I, unfortunately, can't answer this.

Connect with SingalR client with IHubContext provided in diffrent thread

I am using ASP.NET Core, and I am adding some users to a collection via SingalR hub endpoint:
public class MatchMakingHub : Hub
{
//....
// called by client
public async Task EnlistMatchMaking(int timeControlMs)
{
Guid currentId = Guid.Parse(this.Context.User.GetSubjectId());
GetPlayerByIdQuery getPlayerByIdQuery = new GetPlayerByIdQuery(currentId);
Player currentPlayer = await requestSender.Send<Player>(getPlayerByIdQuery);
var waitingPlayer = new WaitingPlayer(currentPlayer, timeControlMs);
this.matchMakePool.Add(waitingPlayer);
}
}
matchMakePool being a singleton collection.
Later, I have an ASP.NET Core background service fetch the users from the collection, and notify them about being fetched:
public class MatchMakingBackgroundService : BackgroundService
{
private readonly MatchMakePoolSingleton matchMakePoolSingleton;
private readonly IServiceProvider serviceProvider;
private const int RefreshTimeMs = 1000;
public MatchMakingBackgroundService(MatchMakePoolSingleton matchMakePoolSingleton, IServiceProvider serviceProvider)
{
this.matchMakePoolSingleton = matchMakePoolSingleton;
this.serviceProvider = serviceProvider;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while(!stoppingToken.IsCancellationRequested)
{
var result = matchMakePoolSingleton.RefreshMatches();
var tasks = new List<Task>();
foreach(var match in result)
{
tasks.Add(StartGameAsync(match));
}
await Task.WhenAll(tasks);
await Task.Delay(RefreshTimeMs, stoppingToken);
}
}
private async Task StartGameAsync(MatchMakeResult match)
{
using var scope = serviceProvider.CreateScope();
var sender = scope.ServiceProvider.GetRequiredService<ISender>();
var hubContext = serviceProvider.GetRequiredService<IHubContext<MatchMakingHub>>();
CreateNewGameCommand newGameCommand = new CreateNewGameCommand(match.WhitePlayer.Id, match.BlackPlayer.Id, TimeSpan.FromMilliseconds(match.TimeControlMs));
Guid gameGuid = await sender.Send(newGameCommand);
await hubContext.Clients.User(match.WhitePlayer.Id.ToString()).SendAsync("NotifyGameFound", gameGuid);
await hubContext.Clients.User(match.BlackPlayer.Id.ToString()).SendAsync("NotifyGameFound", gameGuid);
}
}
My problem is that NotifyGameFound is not being called in the client side. When I notified them straight from the hub itself it was received, but for some reason it doesn't when I call it through the provided IHubContext<MatchMakingHub>. I suspect that this is because it runs on another thread.
Here is the client code:
// in blazor
protected override async Task OnInitializedAsync()
{
var tokenResult = await TokenProvider.RequestAccessToken();
if(tokenResult.TryGetToken(out var token))
{
hubConnection
= new HubConnectionBuilder().WithUrl(NavigationManager.ToAbsoluteUri("/hubs/MatchMaker"), options =>
{
options.AccessTokenProvider = () => Task.FromResult(token.Value);
}).Build();
await hubConnection.StartAsync();
hubConnection.On<Guid>("NotifyGameFound", id =>
{
//do stuff
});
await MatchMakeRequast();
}
}
async Task MatchMakeRequast() =>
await hubConnection.SendAsync("EnlistMatchMaking", Secs * 1000);
I use injection to achieve this.
In my servers Startup.cs ConfigureServices mothod I have:
services.AddScoped<INotificationsBroker, NotificationsBroker>();
In your case I am assuming you are injecting MatchMakingBackgroundService
Something like:
services.AddScoped<MatchMakingBackgroundService>();
In my NotificationsBroker constructor I inject the context:
private readonly IHubContext<NotificationsHub> hub;
public NotificationsBroker(IHubContext<NotificationsHub> hub)
=> this.hub = hub;
I then inject the broker into any service I require it and the service can call the hubs methods I expose via the interface.
You don't have to go the extra step, I do this for testing, you could inject the context directly into your MatchMakingBackgroundService.

Trigger Async Function on Other Pages by Calling it in Non-Async Constructor in C#

In my App, there is an Async Function ProcessOffer(). When I called it in Constructor as ProcessOffer, it works but synchronously. I want to call this Function in constructor asynchronously.
The ProcessOffer() is a function implemented in CredentialViewModel, but I want that, It should be triggered asynchronously everywhere on the App (IndexViewModel e.t.c).
If I'm on the IndexPage, and Web Application sends a request to Mobile Application, ProcessOffer(), should be triggered.. actually what ProcessOffer does is, that it asks the user to enter a PIN, if it's correct, it sends back a response to the Web Application.
I've tried answers from other Posts, but they returned the Error Autofac.Core.DependencyResolutionException: 'An exception was thrown while activating App.Name, when I sends a request to Mobile App from Web Application.
The Solutions I tried.
1- https://stackoverflow.com/a/64012442/14139029
2- Task.Run(() => ProcessOffer()).Wait();
3- ProcessOffer().GetAwatier().GetResult();
CredentialViewModel.cs
namespace Osma.Mobile.App.ViewModels.Credentials
{
public class CredentialViewModel : ABaseViewModel
{
private readonly CredentialRecord _credential;
private readonly ICredentialService _credentialService;
private readonly IAgentProvider _agentContextProvider;
private readonly IConnectionService _connectionService;
private readonly IMessageService _messageService;
private readonly IPoolConfigurator _poolConfigurator;
[Obsolete]
public CredentialViewModel(
IUserDialogs userDialogs,
INavigationService navigationService,
ICredentialService credentialService,
IAgentProvider agentContextProvider,
IConnectionService connectionService,
IMessageService messageService,
IPoolConfigurator poolConfigurator,
CredentialRecord credential
) : base(
nameof(CredentialViewModel),
userDialogs,
navigationService
)
{
_credential = credential;
_credentialService = credentialService;
_agentContextProvider = agentContextProvider;
_connectionService = connectionService;
_messageService = messageService;
_poolConfigurator = poolConfigurator;
_credentialState = _credential.State.ToString();
if (_credentialState == "Offered")
{
ProcessOffer();
}
}
[Obsolete]
public async Task ProcessOffer()
{
foreach (var item in _credential.CredentialAttributesValues)
{
await SecureStorage.SetAsync(item.Name.ToString(), item.Value.ToString());
}
var RegisteredPIN = await SecureStorage.GetAsync("RegisteredPIN");
string PIN = await App.Current.MainPage.DisplayPromptAsync("Enter PIN", null, "Ok", "Cancel", null, 6, Keyboard.Numeric);
if (PIN == RegisteredPIN)
{
try
{
//await _poolConfigurator.ConfigurePoolsAsync();
var agentContext = await _agentContextProvider.GetContextAsync();
var credentialRecord = await _credentialService.GetAsync(agentContext, _credential.Id);
var connectionId = credentialRecord.ConnectionId;
var connectionRecord = await _connectionService.GetAsync(agentContext, connectionId);
(var request, _) = await _credentialService.CreateRequestAsync(agentContext, _credential.Id);
await _messageService.SendAsync(agentContext.Wallet, request, connectionRecord);
await DialogService.AlertAsync("Request has been sent to the issuer.", "Success", "Ok");
}
catch (Exception e)
{
await DialogService.AlertAsync(e.Message, "Error", "Ok");
}
}
else if (PIN != RegisteredPIN && PIN != null)
{
DialogService.Alert("Provided PIN is not correct");
}
}
#region Bindable Command
[Obsolete]
public ICommand ProcessOfferCommand => new Command(async () => await ProcessOffer());
public ICommand NavigateBackCommand => new Command(async () =>
{
await NavigationService.PopModalAsync();
});
#endregion
#region Bindable Properties
private string _credentialState;
public string CredentialState
{
get => _credentialState;
set => this.RaiseAndSetIfChanged(ref _credentialState, value);
}
#endregion
}
}
IndexViewModel.cs
namespace Osma.Mobile.App.ViewModels.Index
{
public class IndexViewModel : ABaseViewModel
{
private readonly IConnectionService _connectionService;
private readonly IMessageService _messageService;
private readonly IAgentProvider _agentContextProvider;
private readonly IEventAggregator _eventAggregator;
private readonly ILifetimeScope _scope;
public IndexViewModel(
IUserDialogs userDialogs,
INavigationService navigationService,
IConnectionService connectionService,
IMessageService messageService,
IAgentProvider agentContextProvider,
IEventAggregator eventAggregator,
ILifetimeScope scope
) : base(
"Index",
userDialogs,
navigationService
)
{
_connectionService = connectionService;
_messageService = messageService;
_agentContextProvider = agentContextProvider;
_eventAggregator = eventAggregator;
_scope = scope;
}
public override async Task InitializeAsync(object navigationData)
{
await base.InitializeAsync(navigationData);
}
public class Post
{
public string Success { get; set; }
public string firstname { get; set; }
}
[Obsolete]
public async Task ScanVerification(object sender, EventArgs e)
{
// Code
}
public async Task SettingsPage(SettingsViewModel settings) => await NavigationService.NavigateToAsync(settings, null, NavigationType.Modal);
#region Bindable Command
public ICommand SettingsPageCommand => new Command<SettingsViewModel>(async (settings) =>
{
await SettingsPage(settings);
});
[Obsolete]
public ICommand ScanVerificationCommand => new Command(async () => await ScanVerification(default, default));
#endregion
}
}
App.xml.cs
[assembly: XamlCompilation(XamlCompilationOptions.Compile)]
namespace Osma.Mobile.App
{
public partial class App : Application
{
public new static App Current => Application.Current as App;
public static IContainer Container { get; set; }
// Timer to check new messages in the configured mediator agent every 10sec
private readonly Timer timer;
private static IHost Host { get; set; }
public App()
{
InitializeComponent();
timer = new Timer
{
Enabled = false,
AutoReset = true,
Interval = TimeSpan.FromSeconds(10).TotalMilliseconds
};
timer.Elapsed += Timer_Elapsed;
}
public App(IHost host) : this() => Host = host;
public static IHostBuilder BuildHost(Assembly platformSpecific = null) =>
XamarinHost.CreateDefaultBuilder<App>()
.ConfigureServices((_, services) =>
{
services.AddAriesFramework(builder => builder.RegisterEdgeAgent(
options: options =>
{
options.AgentName = "Mobile Holder";
options.EndpointUri = "http://11.222.333.44:5000";
options.WalletConfiguration.StorageConfiguration =
new WalletConfiguration.WalletStorageConfiguration
{
Path = Path.Combine(
path1: FileSystem.AppDataDirectory,
path2: ".indy_client",
path3: "wallets")
};
options.WalletConfiguration.Id = "MobileWallet";
options.WalletCredentials.Key = "SecretWalletKey";
options.RevocationRegistryDirectory = Path.Combine(
path1: FileSystem.AppDataDirectory,
path2: ".indy_client",
path3: "tails");
// Available network configurations (see PoolConfigurator.cs):
options.PoolName = "sovrin-test";
},
delayProvisioning: true));
services.AddSingleton<IPoolConfigurator, PoolConfigurator>();
var containerBuilder = new ContainerBuilder();
containerBuilder.RegisterAssemblyModules(typeof(CoreModule).Assembly);
if (platformSpecific != null)
{
containerBuilder.RegisterAssemblyModules(platformSpecific);
}
containerBuilder.Populate(services);
Container = containerBuilder.Build();
});
protected override async void OnStart()
{
await Host.StartAsync();
// View models and pages mappings
var _navigationService = Container.Resolve<INavigationService>();
_navigationService.AddPageViewModelBinding<MainViewModel, MainPage>();
_navigationService.AddPageViewModelBinding<RegisterViewModel, RegisterPage>();
_navigationService.AddPageViewModelBinding<IndexViewModel, IndexPage>();
_navigationService.AddPageViewModelBinding<SettingsViewModel, SettingsPage>();
_navigationService.AddPageViewModelBinding<CredentialsViewModel, CredentialsPage>();
_navigationService.AddPageViewModelBinding<CredentialViewModel, CredentialPage>();
if (Preferences.Get(AppConstant.LocalWalletProvisioned, false))
{
await _navigationService.NavigateToAsync<MainViewModel>();
}
else
{
await _navigationService.NavigateToAsync<ProviderViewModel>();
}
timer.Enabled = true;
}
private void Timer_Elapsed(object sender, ElapsedEventArgs e)
{
// Check for new messages with the mediator agent if successfully provisioned
if (Preferences.Get(AppConstant.LocalWalletProvisioned, false))
{
Device.BeginInvokeOnMainThread(async () =>
{
try
{
var context = await Container.Resolve<IAgentProvider>().GetContextAsync();
await Container.Resolve<IEdgeClientService>().FetchInboxAsync(context);
}
catch (Exception ex)
{
Debug.WriteLine(ex);
}
});
}
}
protected override void OnSleep() =>
// Stop timer when application goes to background
timer.Enabled = false;
protected override void OnResume() =>
// Resume timer when application comes in foreground
timer.Enabled = true;
}
}
Look, you have public override async Task InitializeAsync(object navigationData) method in the IndexViewModel class. I suppose the framework invoke it to initialize the view model. So, thus CredentialViewModel inherits same ABaseViewModel anyway, why wouldn't you just override InitializeAsync in your CredentialViewModel and call ProcessOffer from it?
public CredentialViewModel(
IUserDialogs userDialogs,
INavigationService navigationService,
ICredentialService credentialService,
IAgentProvider agentContextProvider,
IConnectionService connectionService,
IMessageService messageService,
IPoolConfigurator poolConfigurator,
CredentialRecord credential
) : base(
nameof(CredentialViewModel),
userDialogs,
navigationService
)
{
_credential = credential;
_credentialService = credentialService;
_agentContextProvider = agentContextProvider;
_connectionService = connectionService;
_messageService = messageService;
_poolConfigurator = poolConfigurator;
_credentialState = _credential.State.ToString();
}
public override async Task InitializeAsync(object navigationData)
{
if (_credentialState != "Offered") return;
await ProcessOffer();
}
Anyway, you have to avoid calling asynchronous operations in constructors.
So in my View Models, if i need to initialize asynchronously I usually pass OnAppearing() to the view model instead of the constructor. Someone can tell me if this is unwise but it solved my needs.
Code Behind View:
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class CredentialView : ContentPage
{
public CredentialView()
{
InitializeComponent();
BindingContext = new CredentialViewModel();
}
protected override void OnAppearing()
{
base.OnAppearing();
((CredentialViewModel)BindingContext).OnAppearing();
}
}
View Model:
public class CredentialViewModel : INotifyPropertyChanged
{
public CredentialViewModel ()
{
}
public async void OnAppearing()
{
await ProcessOffer();
}
}
Also just FYI, the biggest gotcha i've experienced with asynchronous code on xamarin is to use BeginInvokeOnMainThread. It's my understanding that touched UI should be executed on main thread! (probably why they call it the UI thread. haha)
Example:
Device.BeginInvokeOnMainThread(() =>
{
observableCollection.Clear();
});
As you've already asked before, kicking off async code in the constructor of a class can be a can of worms. You should instead consider doing either:
Starting ProcessOffer in a lifecycle method instead. For instance when the View is showing call ProcessOfferCommand.
Using fire and forget to not block the constructor: Task.Run(ProcessOffer) you should probably avoid this though.
Use something like NotifyTask Stephen Cleary describes here: https://learn.microsoft.com/en-us/archive/msdn-magazine/2014/march/async-programming-patterns-for-asynchronous-mvvm-applications-data-binding you can find the complete code for it here: https://github.com/StephenCleary/Mvvm.Async/blob/master/src/Nito.Mvvm.Async/NotifyTask.cs
Use a CreateAsync pattern with a private constructor. However, this doesn't work well with Dependency Injection usually. Doing IO intensive work during resolution isn't really the correct place to do it.
In my opinion using either 1. or 3. would be the best solutions, perhaps leaning towards a combination of 1. using NotifyTask that can notify you when it is done loading.

Log configuration changes in ASP.NET Core

I want to log when configuration is changed.
I do this in Program.cs or Startup.cs:
ChangeToken.OnChange(
() => configuration.GetReloadToken(),
state => logger.Information("Configuration reloaded"),
(object)null
);
But I get double change reports, so it needs to be debounced. The advice is to do this:
ChangeToken.OnChange(
() => configuration.GetReloadToken(),
state => { Thread.Sleep(2000); logger.Information("Configuration reloaded"); },
(object)null
);
I'm using 2000 here as I'm not sure what's a reasonable value.
I've found that sometimes I still get multiple change detections, separated by 2000 milliseconds. So the debounce doesn't work for me, just causes a delay between reported changes. If I set a high value then I only get one report, but that isn't ideal (and conceals the problem).
So I'd like to know:
Is this really debouncing, or just queueing reported changes?
I've used values from 1000 to 5000 to varying success. What are others using?
Is the sleep issued to the server's main thread? I hope not!
The multiple change detection issue discussed here (and at least a dozen other issues in multiple repos) is something they refuse to address using a built-in mechanism.
The MS docs use a file hashing approach, but I think that debouncing is better.
My solution uses async (avoids async-in-sync which could blow up something accidentally) and a hosted service that debounces change detections.
Debouncer.cs:
public sealed class Debouncer : IDisposable {
public Debouncer(TimeSpan? delay) => _delay = delay ?? TimeSpan.FromSeconds(2);
private readonly TimeSpan _delay;
private CancellationTokenSource? previousCancellationToken = null;
public async Task Debounce(Action action) {
_ = action ?? throw new ArgumentNullException(nameof(action));
Cancel();
previousCancellationToken = new CancellationTokenSource();
try {
await Task.Delay(_delay, previousCancellationToken.Token);
await Task.Run(action, previousCancellationToken.Token);
}
catch (TaskCanceledException) { } // can swallow exception as nothing more to do if task cancelled
}
public void Cancel() {
if (previousCancellationToken != null) {
previousCancellationToken.Cancel();
previousCancellationToken.Dispose();
}
}
public void Dispose() => Cancel();
}
ConfigWatcher.cs:
public sealed class ConfigWatcher : IHostedService, IDisposable {
public ConfigWatcher(IServiceScopeFactory scopeFactory, ILogger<ConfigWatcher> logger) {
_scopeFactory = scopeFactory;
_logger = logger;
}
private readonly IServiceScopeFactory _scopeFactory;
private readonly ILogger<ConfigWatcher> _logger;
private readonly Debouncer _debouncer = new(TimeSpan.FromSeconds(2));
private void OnConfigurationReloaded() {
_logger.LogInformation("Configuration reloaded");
// ... can do more stuff here, e.g. validate config
}
public Task StartAsync(CancellationToken cancellationToken) {
ChangeToken.OnChange(
() => { // resolve config from scope rather than ctor injection, in case it changes (this hosted service is a singleton)
using var scope = _scopeFactory.CreateScope();
var configuration = scope.ServiceProvider.GetRequiredService<IConfiguration>();
return configuration.GetReloadToken();
},
async () => await _debouncer.Debounce(OnConfigurationReloaded)
);
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
public void Dispose() => _debouncer.Dispose();
}
Startup.cs:
services.AddHostedService<ConfigWatcher>(); // registered as singleton
Hopefully, someone else can answer your questions, but I did run into this issue and found this Gist by cocowalla.
The code provided by cocowalla debounces instead of just waiting. It successfully deduplicated the change callback for me.
Cocowalla also includes an extension method so you can simply call OnChange on the IConfiguration.
Here's a sample:
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Primitives;
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
class Program
{
public static async Task Main(string[] args)
{
var configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile(path: "appsettings.json", optional: false, reloadOnChange: true)
.Build();
configuration.OnChange(() => Console.WriteLine("configuration changed"));
while (true)
{
await Task.Delay(1000);
}
}
}
public class Debouncer : IDisposable
{
private readonly CancellationTokenSource cts = new CancellationTokenSource();
private readonly TimeSpan waitTime;
private int counter;
public Debouncer(TimeSpan? waitTime = null)
{
this.waitTime = waitTime ?? TimeSpan.FromSeconds(3);
}
public void Debouce(Action action)
{
var current = Interlocked.Increment(ref this.counter);
Task.Delay(this.waitTime).ContinueWith(task =>
{
// Is this the last task that was queued?
if (current == this.counter && !this.cts.IsCancellationRequested)
action();
task.Dispose();
}, this.cts.Token);
}
public void Dispose()
{
this.cts.Cancel();
}
}
public static class IConfigurationExtensions
{
/// <summary>
/// Perform an action when configuration changes. Note this requires config sources to be added with
/// `reloadOnChange` enabled
/// </summary>
/// <param name="config">Configuration to watch for changes</param>
/// <param name="action">Action to perform when <paramref name="config"/> is changed</param>
public static void OnChange(this IConfiguration config, Action action)
{
// IConfiguration's change detection is based on FileSystemWatcher, which will fire multiple change
// events for each change - Microsoft's code is buggy in that it doesn't bother to debounce/dedupe
// https://github.com/aspnet/AspNetCore/issues/2542
var debouncer = new Debouncer(TimeSpan.FromSeconds(3));
ChangeToken.OnChange<object>(config.GetReloadToken, _ => debouncer.Debouce(action), null);
}
}
In the sample, the debounce delay is 3 seconds, for my small json file, the debounce delay stops deduplicating around 230 milliseconds.

Cannot create commands from unopened database

I've searched around quite a lot and I cannot find any answers to this.
I am writing a Xamarin Forms Mobile application, it seems when I minimise the application and then reopen it or one of my activities get launched the following exception gets thrown:
SQLiteConnection.CreateCommand (System.String cmdText, System.Object[] ps)
SQLite.SQLiteException: Cannot create commands from unopened database
SQLiteConnection.CreateCommand (System.String cmdText, System.Object[] ps)
TableQuery`1[T].GenerateCommand (System.String selectionList)
TableQuery`1[T].GetEnumerator ()
System.Collections.Generic.List`1[T]..ctor (System.Collections.Generic.IEnumerable`1[T] collection) [0x00062] in :0
Enumerable.ToList[TSource] (System.Collections.Generic.IEnumerable`1[T] source)
AsyncTableQuery`1[T].<ToListAsync>b__9_0 ()
Task`1[TResult].InnerInvoke ()
Task.Execute ()
Here is my code:
Generic Repository (Where the Sqlite instance gets created)
public class Repository<T> : IRepository<T> where T : Entity, new()
{
private readonly SQLiteAsyncConnection _db;
public Repository(string dbPath)
{
_db = new SQLiteAsyncConnection(dbPath);
_db.CreateTableAsync<T>().Wait();
}
}
The IOC registration
FreshIOC.Container.Register<IRepository<Settings>>(new Repository<Settings>(dbPath)); // FreshIOC is a wrapper around TinyIOC
In my App.xaml.cs OnResume
protected override void OnResume()
{
SQLiteAsyncConnection.ResetPool();
}
The above with ResetPool I put that in to see if it would make a difference but it did not.
URL Activity
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
var url = Intent.Data.ToString();
var split = url.Split(new[] { "ombi://", "_" }, StringSplitOptions.RemoveEmptyEntries);
if (split.Length > 1)
{
var dbLocation = new FileHelper().GetLocalFilePath("ombi.db3");
var repo = new Repository<OmbiMobile.Models.Entities.Settings>(dbLocation);
var settings = repo.Get().Result;
foreach (var s in settings)
{
var i = repo.Delete(s).Result;
}
repo.Save(new Settings
{
AccessToken = split[1],
OmbiUrl = split[0]
});
}
Intent startup = new Intent(this, typeof(MainActivity));
StartActivity(startup);
Finish();
}
I am not sure what else to do or look for, I can't seem to find any information about this sort of error.
Update:
After more debugging it seems to only happen after the Url activity has finished.
I have removed the DB code from the Activity and it still seems to happen. Once the Activity has launched the main App() then runs this code:
var repo = FreshIOC.Container.Resolve<IRepository<Settings>>();
try
{
Task.Run(async () =>
{
settings = (await repo.Get()).FirstOrDefault();
}).Wait();
}
catch (Exception e)
{
Debug.WriteLine(e.Message);
throw;
}
This where the error is happening. It happens when the Get() is called which calls return _db.Table<T>().ToListAsync();
I have tried making everything async (didn't help), making the repository, connection and where we do CreateTableAsync async and still no luck.
You are making synchronous blocking calls like .Wait() and .Result that could potentially cause deadlocks when mixed with an asynchronous API.
SQLiteAsyncConnection was meant to be used asynchronously.
One common work around is to create event handlers that would allow for async non blocking calls to be made.
For example when calling CreateTableAsync in the repository
public class Repository<T> : IRepository<T> where T : Entity, new() {
private readonly SQLiteAsyncConnection _db;
public Repository(string dbPath) {
_db = new SQLiteAsyncConnection(dbPath);
createTable += onCreateTable; //Subscribe to event
createTable(this, EventArgs.Empty); //Raise event
}
private event EventHandler createTable = delegate { };
private async void onCreateTable(object sender, EventArgs args) {
createTable -= onCreateTable; //Unsubscribe from event
await _db.CreateTableAsync<T>(); //async non blocking call
}
//...
}
The repository abstraction appears to have an asynchronous API yet there are synchronous calls.
Again this can cause deadlock and is not advised.
The code needs to be refactored to be async all the way through if the intent is to have a responsive UI or use SQLite.Net, the non-async version, to make synchronous calls.
Refactoring of the URL Activity to be asynchronous would look like this following the same format as above.
protected override void OnCreate(Bundle bundle) {
base.OnCreate(bundle);
creating += onCreateCore; //subscribe to event
creating(this, EventArgs.Empty); //raise event
}
private event EventHandler creating = delegate { };
private async void onCreateCore(object sender, EventArgs args) {
creating -= onCreateCore; //unsubscribe to event
var url = Intent.Data.ToString();
var split = url.Split(new[] { "ombi://", "_" }, StringSplitOptions.RemoveEmptyEntries);
if (split.Length > 1) {
var dbLocation = new FileHelper().GetLocalFilePath("ombi.db3");
var repo = new Repository<OmbiMobile.Models.Entities.Settings>(dbLocation);
var settings = await repo.Get();
foreach (var s in settings) {
var i = await repo.Delete(s);
}
repo.Save(new Settings {
AccessToken = split[1],
OmbiUrl = split[0]
});
}
Intent startup = new Intent(this, typeof(MainActivity));
StartActivity(startup);
Finish();
}
UPDATE
Also from a design perspective, the initialization of the connection should be inverted out of the repository and managed externally (SRP).
public interface ISQLiteAsyncProvider {
SQLiteAsyncConnection GetConnection();
}
public class DefaultSQLiteAsyncProvider : ISQLiteAsyncProvider {
private readonly Lazy<SQLiteAsyncConnection> connection;
public DefaultSQLiteAsyncProvider(string path) {
connection = new Lazy<SQLiteAsyncConnection>(() => new SQLiteAsyncConnection(path));
}
public SQLiteAsyncConnection GetConnection() {
return connection.Value;
}
}
Playing with the idea of an asynchronous lazy initialization for the connection using
/// <summary>
/// Provides support for asynchronous lazy initialization.
/// </summary>
/// <typeparam name="T"></typeparam>
public class LazyAsync<T> : Lazy<Task<T>> {
/// <summary>
/// Initializes a new instance of the LazyAsync`1 class. When lazy initialization
/// occurs, the specified initialization function is used.
/// </summary>
/// <param name="valueFactory">The delegate that is invoked to produce the lazily initialized Task when it is needed.</param>
public LazyAsync(Func<Task<T>> valueFactory) :
base(() => Task.Run(valueFactory)) { }
}
This makes it possible now to refactor the repository to use lazy initialization, which allowed for the removal of the event handler in the repository
public class Repository<T> : IRepository<T> where T : Entity, new() {
public Repository(ISQLiteAsyncProvider provider) {
this.connection = new LazyAsync<SQLiteAsyncConnection>(await () => {
var db = provider.GetConnection();
await db.CreateTableAsync<T>();
return db;
});
}
private readonly LazyAsync<SQLiteAsyncConnection> connection;
public async Task<List<T>> Get() {
var _db = await connection.Value;
return await _db.Table<T>().ToListAsync();
}
public async Task<T> Get(int id) {
var _db = await connection.Value;
return await _db.Table<T>().Where(x => x.Id == id).FirstOrDefaultAsync();
}
public async Task<int> Save(T entity) {
var _db = await connection.Value;
return entity.Id == 0
? await _db.InsertAsync(entity)
: await_db.UpdateAsync(entity);
}
public async Task<int> Delete(T entity) {
var _db = await connection.Value;
return await _db.DeleteAsync(entity);
}
public void Dispose() {
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing) {
if (disposing) {
// get rid of managed resources
}
// get rid of unmanaged resources
}
}
And registered like
// same instance should be used for other repositories
var provider = new DefaultSQLiteAsyncProvider(dbPath);
var settingsRepository = new Repository<Settings>(provider);
FreshIOC.Container.Register<IRepository<Settings>>(settingsRepository);
Thanks for #Nkosi to his insight and advice, that was much appreciated but none of the solutions worked.
After pulling down the sqlite.net-pcl library (saved by OSS again!) and debugging through it, it seemed that every time my Activity launched there is a check to see if the connection was open and it wasn't, the only place it was being set to open is when the SqliteConnection was constructed. Now the way I wrote it, it was a singleton but stupidly my Repository<T> implemented IDisposable. So my IOC container was disposing of the SqliteConnection but it was never recreating it due to it being a singleton.
TL;DR removed IDisposable implementation on the repository because the SqliteConnection was a singleton.
I had the same error, but was not due to Disposable implementation. For some unknown reason it broke if I had the following:
lock (locker)
{
foreach (var item in database.Table<DBItems>()) //It broke on this line
{
//...
}
}
So I changed the line to
foreach (var item in database.Table<DBItems>().ToList()) //Notice the "ToList()"
Problem solved...

Categories

Resources