How to wait for asp.net core configuration to be done for backgroundworkers? - asp.net-core

I created a blazor server application which also makes use of BackgroundService / IHostedService - Unfortunately those services Start+Execute before the Configure method within Startup is called. Therefore migrations/database creation are not done before they are used in the services.
I already found a workaround for this, but i am wondering if there is a proper way to solve this.
My workaround for those who have the same issue is this:
public class ApplicationStateTransmitter
{
private readonly TaskCompletionSource _configurationDone;
public ApplicationStateTransmitter()
{
_configurationDone = new TaskCompletionSource(null);
}
public Task ConfigurationDone => _configurationDone.Task;
public void NotifyConfigurationDone() => _configurationDone.SetResult();
}
Startup.ConfigureServices:
services.AddSingleton<ApplicationStateTransmitter>();
Startup.Configure:
using (var serviceScope = serviceScopeFactory.CreateScope())
{
using (var context = serviceScope.ServiceProvider.GetRequiredService<ApplicationDbContext>())
{
if (!context.Database.CanConnect())
{
logger.LogInformation("Database does not exist yet - Creating database through migrations");
context.Database.Migrate();
}
else
{
if (context.Database.GetPendingMigrations().Any())
{
logger.LogInformation("Applying database migration");
context.Database.Migrate();
logger.LogInformation("Applying database migration done");
}
else
{
logger.LogDebug("No pending migrations found");
}
}
}
}
applicationStateTransmitter.NotifyConfigurationDone();
logger.LogInformation("Configuration done");
Inject instance and await like this
await _applicationStateTransmitter.ConfigurationDone;

Related

How to inject a service in my DbContext class and have host.MigrateDatabase() still working

I've got a working EFCore, .NET5, Blazor WASM application.
I call await host.MigrateDatabase(); in my Program.Main() to have my database always up-to-date.
public static async Task<IHost> MigrateDatabase(this IHost host)
{
using var scope = host.Services.CreateScope();
try
{
// Get the needed context factory using DI:
var contextFactory = scope.ServiceProvider.GetRequiredService<IDbContextFactory<AppDbContext>>();
// Create the context from the factory:
await using var context = contextFactory.CreateDbContext();
// Migrate the database:
await context.Database.MigrateAsync();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
throw;
}
return host;
}
In my AppDbContext I've overridden SaveChangesAsync() to add and update CreatedOn en UpdatedOn.
I mentioned this in DbContext.SaveChanges overrides behaves unexpected before.
I also want to fill CreatedBy and UpdatedBy with the userId.
I have an IdentityOptions class to hold the user data:
public class IdentityOptions
{
public string UserId => User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
public ClaimsPrincipal User { get; set; }
}
I've registered this class in StartUp like this:
services.AddScoped(sp =>
{
var context = sp.GetService<IHttpContextAccessor>()?.HttpContext;
var identityOptions = new IdentityOptions();
if (context?.User.Identity != null && context.User.Identity.IsAuthenticated)
{
identityOptions.User = context.User;
}
return identityOptions;
});
I inject this IdentityOptions class into several other services, without any problem.
But when I inject it in my AppDbContext:
public AppDbContext(DbContextOptions<AppDbContext> options, IdentityOptions identityOptions)
: base(options)
{
...
}
I get an error in MigrateDatabase():
"Cannot resolve scoped service 'IdentityOptions' from root provider."
I've been trying numerous options I found googling but can't find a solution that works for me.
Please advice.
Update:
services.AddDbContextFactory<AppDbContext>(
options => options.UseSqlServer(Configuration.GetConnectionString("DbConnection"),
b => b.MigrationsAssembly("DataAccess"))
#if DEBUG
.LogTo(Console.WriteLine, new [] {RelationalEventId.CommandExecuted})
.EnableSensitiveDataLogging()
#endif
);
Thanks to the great help of #IvanStoev (again), I found the answer.
Adding lifetime: ServiceLifetime.Scoped to AddDbContextFactory in Startup solved my problem.
Now I can use my IdentityOptions class in SaveChanges and automatically update my Created* and Updated* properties.

Blazor WASM ViewModel

I did a lot of Razor pages the past year, and a couple of weeks ago I started to transform all to a ViewModel for my Blazor Server App.
Now I thought it's time to make a new Blazor WebAssembly App.
But I struggle to build a POC with a ViewModel, based on the WeatherForecast example.
But whatever I do, I have errors. And so far I did not find a a good basic example.
Unhandled exception rendering component: Unable to resolve service for type 'fm2.Client.Models.IFetchDataModel' while attempting to activate 'fm2.Client.ViewModels.FetchDataViewModel'.
System.InvalidOperationException: Unable to resolve service for type 'fm2.Client.Models.IFetchDataModel' while attempting to activate 'fm2.Client.ViewModels.FetchDataViewModel'.
Example: https://github.com/rmoergeli/fm2
namespace fm2.Client.ViewModels
{
public interface IFetchDataViewModel
{
WeatherForecast[] WeatherForecasts { get; set; }
Task RetrieveForecastsAsync();
Task OnInitializedAsync();
}
public class FetchDataViewModel : IFetchDataViewModel
{
private WeatherForecast[] _weatherForecasts;
private IFetchDataModel _fetchDataModel;
public WeatherForecast[] WeatherForecasts
{
get => _weatherForecasts;
set => _weatherForecasts = value;
}
public FetchDataViewModel(IFetchDataModel fetchDataModel)
{
Console.WriteLine("FetchDataViewModel Constructor Executing");
_fetchDataModel = fetchDataModel;
}
public async Task RetrieveForecastsAsync()
{
_weatherForecasts = await _fetchDataModel.RetrieveForecastsAsync();
Console.WriteLine("FetchDataViewModel Forecasts Retrieved");
}
public async Task OnInitializedAsync()
{
_weatherForecasts = await _fetchDataModel.RetrieveForecastsAsync();
}
}
}
namespace fm2.Client
{
public class Program
{
public static async Task Main(string[] args)
{
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
builder.Services.AddScoped<IFetchDataViewModel, FetchDataViewModel>();
await builder.Build().RunAsync();
}
}
}
Additional note:
Here how I did it previously for Blazor Server App: https://github.com/rmoergeli/fm2_server
Here I try the same for the Blazor WebAssembly App:
https://github.com/rmoergeli/fm2_wasm (Constructor is not initialized).
This POC is different comapred to the first link at the top. Here I tried to just do the same like I did for the Blazor Server App.
I pulled the latest code from Github. It looks like the wrong api was getting called.
When I changed from this:
WeatherForecast[] _weatherForecast = await _http.GetFromJsonAsync<WeatherForecast[]>("api/SampleData/WeatherForecasts");
to this:
WeatherForecast[] _weatherForecast = await _http.GetFromJsonAsync<WeatherForecast[]>("WeatherForecast");
in WeatherViewModel.cs
I could get the weather data to be displayed.

How to use Realm Collection Notifications in an ASP.NET Core Application?

I've tried using this sample code from the Realm .NET SDK but my handler code never gets called consistently:
var token = realm.All<Person>().SubscribeForNotifications ((sender, changes, error) =>
{
// Access changes.InsertedIndices, changes.DeletedIndices, and changes.ModifiedIndices
});
I've tried running this from a number of different threads, but I suspect none of them have a looper/runloop, a requirement noted in the SDK. Is it even possible to create a looper/runloop thread in ASP.NET Core that will work with Realm? How would I do that?
Finally figured out how to implement the looper/run loop that works for Realm Notifications. The keys parts of the solution are:
Use a hosted service to start/stop the thread.
Use Nito.AsyncEx to establish a synchronization context for that thread.
Loop inside the thread, calling the RefreshAsync method on the realm instance.
Call Task.Delay between loop iterations to keep the thread cpu-friendly.
Here is the code:
public class RealmNotificationService : IHostedService
{
private System.Threading.Thread _thread;
public Task StartAsync(CancellationToken stoppingToken)
{
_thread = new System.Threading.Thread(() =>
Nito.AsyncEx.AsyncContext.Run(() => Looper(stoppingToken))
);
_thread.Start();
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken stoppingToken)
{
return Task.CompletedTask;
}
private async void Looper(CancellationToken stoppingToken)
{
using (var connection = await Realm.GetInstanceAsync({YOUR-CONFIG}))
{
var token = connection.All<{YOUR-OBJECT}>().SubscribeForNotifications((sender, changes, error) =>
{
// Access changes.InsertedIndices, changes.DeletedIndices, and changes.ModifiedIndices
});
}
while (!stoppingToken.IsCancellationRequested)
{
await connection.Realm.RefreshAsync();
await Task.Delay(2000, stoppingToken);
}
}
}
Bill Raudabaugh's answer almost worked for me. However, the final while loop is outside of the using so connection is undefined.
I was able to get it to work by moving that loop inside of the using. Also I removed the RefreshAsync() as it seems it's not needed; Realm automatically updates in real time without having to refresh.
Here is my code:
public class RealmNotificationService : IHostedService
{
private System.Threading.Thread _thread;
public Task StartAsync(CancellationToken stoppingToken)
{
_thread = new System.Threading.Thread(() =>
Nito.AsyncEx.AsyncContext.Run(() => Looper(stoppingToken))
);
_thread.Start();
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken stoppingToken)
{
return Task.CompletedTask;
}
private async void Looper(CancellationToken stoppingToken)
{
using (var connection = await Realm.GetInstanceAsync({YOUR-CONFIG}))
{
var token = connection.All<{YOUR-OBJECT}>().SubscribeForNotifications((sender, changes, error) =>
{
// Access changes.InsertedIndices, changes.DeletedIndices, and changes.ModifiedIndices
});
while (!stoppingToken.IsCancellationRequested)
{
await Task.Delay(2000, stoppingToken);
}
}
}
}

Testing ASP.NET 5 with Entity Framework 7 using in memory database

I am wanting to get ahold of the Context that I am injecting into the controllers during testing and modify the data in the "in memory" version of the database context.
So the controller looks like this
[Route("api/[controller]")]
public class TestController : Controller
{
private readonly TestContext _testContext;
public TestController(TestContext testContext)
{
_testContext = testContext;
}
[HttpGet]
public IActionResult Get()
{
return Ok(new { _testContext.Users });
}
}
The test looks like this
public class SiteTests
{
[Fact]
public async Task GetIt()
{
var server = TestServer.Create(app => { app.UseMvc(); }, services =>
{
services.AddMvc();
services.AddEntityFramework()
.AddSqlServer()
.AddDbContext<TestContext>(options => options.UseInMemoryDatabase());
services.AddScoped<TestContext, TestContext>();
});
var client = server.CreateClient();
var response = await client.GetAsync("http://localhost/api/test");
var content = await response.Content.ReadAsStringAsync();
Assert.True(response.IsSuccessStatusCode);
}
}
I would love to somehow get ahold of the context before the client gets the request and modify what data will be coming back from the database context.
I have the test project in GitHub
If you're targeting .NET Core, you won't be able to make use of any automatic mocking frameworks.
The best you can do is make all your methods in TestContext virtual, then extend it in your unit tests.
public class IntegrationTestContext : TestContext
{
// override methods here
}
You can then use
var context = new IntegrationTestContext();
services.AddInstance<TestContext>(context);
You can also capture any extra information you want in IntegrationTestContext and access it from within your test.

Managing RavenDB IDocumentSession lifecycles with StructureMap for NServiceBus and MVC

I am using NServiceBus v4.3, MVC4, RavenDB 2.5 and StructureMap 2.6.4 in our solution.
I am having a similar issue under StructureMap to that described in this question's responses where I require different lifecycles for the MVC Controller and NServiceBus Handler use of RavenDB's IDocumentSession in my Web project.
Specifically in my case what happens is that if I use the HybridHttpOrThreadLocalScoped (as the above answer suggests for Windsor) lifecycle the sessions are not properly disposed of and I soon hit the 30 transaction limit error. If I use the HttpContext lifecycle my NSB event Handlers in the Web project do not get called.
In my Controllers the session is wrapped in a unit of work applied via an MVC ActionFilter. I also use the UoW within the Handlers as my Registry is wired up to retrieve the session from the UoW. The code is as such:
RavenDbWebRegistry.cs
public sealed class RavenDbWebRegistry : Registry
{
public RavenDbWebRegistry()
{
// register RavenDB document store
ForSingletonOf<IDocumentStore>().Use(() =>
{
var documentStore = new DocumentStore
{
ConnectionStringName = "RavenDB",
Conventions =
{
IdentityPartsSeparator = "-",
JsonContractResolver = new PrivatePropertySetterResolver(),
},
};
documentStore.Initialize();
return documentStore;
});
For<IDocumentSession>().HybridHttpOrThreadLocalScoped().Add(ctx =>
{
var uow = (IRavenDbUnitOfWork)ctx.GetInstance<IUnitOfWork>();
return uow.DocumentSession;
});
For<IUnitOfWork>().HybridHttpOrThreadLocalScoped().Use<WebRavenDbUnitOfWork>();
}
}
Example of Web project Handler:
public class SiteCreatedEventHandler : IHandleMessages<ISiteCreatedEvent>
{
public IBus Bus { get; set; }
public IUnitOfWork Uow { get; set; }
public IDocumentSession DocumentSession { get; set; }
public void Handle(ISiteCreatedEvent message)
{
try
{
Debug.Print(#"{0}{1}", message, Environment.NewLine);
Uow.Begin();
var site = DocumentSession.Load<Site>(message.SiteId);
Uow.Commit();
//invoke Hub and push update to screen
var context = GlobalHost.ConnectionManager.GetHubContext<AlarmAndNotifyHub>();
//TODO make sure this SignalR function is correct
context.Clients.All.displayNewSite(site, message.CommandId);
context.Clients.All.refreshSiteList();
}
catch (Exception ex)
{
Uow.Rollback();
}
}
}
Usage of ActionFilter:
[RavenDbUnitOfWork]
public ViewResult CreateNew(int? id)
{
if (!id.HasValue || id.Value <= 0)
return View(new SiteViewModel { Guid = Guid.NewGuid() });
var targetSiteVm = MapSiteToSiteViewModel(SiteList(false)).FirstOrDefault(s => s.SiteId == id.Value);
return View(targetSiteVm);
}
WebRegistry (that sets up NSB in my MVC project)
public sealed class WebRegistry : Registry
{
public WebRegistry()
{
Scan(x =>
{
x.TheCallingAssembly();
x.Assembly("IS.CommonLibrary.ApplicationServices");
x.LookForRegistries();
});
IncludeRegistry<RavenDbWebRegistry>();
FillAllPropertiesOfType<IUnitOfWork>();
FillAllPropertiesOfType<IDocumentSession>();
FillAllPropertiesOfType<StatusConversionService>();
FillAllPropertiesOfType<IStateRepository<TieState>>();
FillAllPropertiesOfType<IStateRepository<DedState>>();
FillAllPropertiesOfType<ITieService>();
FillAllPropertiesOfType<IDedService>();
FillAllPropertiesOfType<IHbwdService>();
//NServiceBus
ForSingletonOf<IBus>().Use(
NServiceBus.Configure.With()
.StructureMapBuilder()
.DefiningCommandsAs(t => t.Namespace != null && t.Namespace.EndsWith("Command"))
.DefiningEventsAs(t => t.Namespace != null && t.Namespace.EndsWith("Event"))
.DefiningMessagesAs(t => t.Namespace == "Messages")
.RavenPersistence("RavenDB")
.UseTransport<ActiveMQ>()
.DefineEndpointName("IS.Argus.Web")
.PurgeOnStartup(true)
.UnicastBus()
.CreateBus()
.Start(() => NServiceBus.Configure.Instance
.ForInstallationOn<Windows>()
.Install())
);
//Web
For<HttpContextBase>().Use(() => HttpContext.Current == null ? null : new HttpContextWrapper(HttpContext.Current));
For<ModelBinderMappingDictionary>().Use(GetModelBinders());
For<IModelBinderProvider>().Use<StructureMapModelBinderProvider>();
For<IFilterProvider>().Use<StructureMapFilterProvider>();
For<StatusConversionService>().Use<StatusConversionService>();
For<ITieService>().Use<TieService>();
For<IDedService>().Use<DedService>();
For<IHbwdService>().Use<HbwdService>();
For<ISiteService>().Use<SiteService>();
IncludeRegistry<RedisRegistry>();
}
I have tried configuring my Registry using every possible combination I can think of to no avail.
Given that the StructureMap hybrid lifecycle does not work as I would expect, what must I do to achieve the correct behaviour?
Is the UoW necessary/beneficial with RavenDB? I like it (having adapted it from my earlier NHibernate UoW ActionFilter) because of the way it manages the lifecycle of my sessions within Controller Actions, but am open to other approaches.
What I would ideally like is a way to - within the Web project - assign entirely different IDocumentSessions to Controllers and Handlers, but have been unable to work out any way to do so.
Firstly, RavenDB already implements unit of work by the wrapping IDocumentSession, so no need for it. Opening a session, calling SaveChanges() and disposing has completed the unit of work
Secondly, Sessions can be implemented in a few ways for controllers.
The general guidance is to set up the store in the Global.asax.cs. Since there is only 1 framework that implements IDocumentSession - RavenDB, you might as well instantiate it from the Global. If it was NHibernate or Entity Framework behind a repository, I'd understand. But IDocumentSession is RavenDB specific, so go with a direct initialization in the Application_Start.
public class Global : HttpApplication
{
public void Application_Start(object sender, EventArgs e)
{
// Usual MVC stuff
// This is your Registry equivalent, so insert it into your Registry file
ObjectFactory.Initialize(x=>
{
x.For<IDocumentStore>()
.Singleton()
.Use(new DocumentStore { /* params here */ }.Initialize());
}
public void Application_End(object sender, EventArgs e)
{
var store = ObjectFactory.GetInstance<IDocumentStore>();
if(store!=null)
store.Dispose();
}
}
In the Controllers, add a base class and then it can open and close the sessions for you. Again IDocumentSession is specific to RavenDB, so dependency injection doesn't actually help you here.
public abstract class ControllerBase : Controller
{
protected IDocumentSession Session { get; private set; }
protected override void OnActionExecuting(ActionExecutingContext context)
{
Session = ObjectFactory.GetInstance<IDocumentStore>().OpenSession();
}
protected override void OnActionExecuted(ActionExecutedContext context)
{
if(this.IsChildAction)
return;
if(content.Exception != null && Session != null)
using(context)
Session.SaveChanges();
}
}
Then from there, inherit from the base controller and do your work from there:
public class CustomerController : ControllerBase
{
public ActionResult Get(string id)
{
var customer = Session.Load<Customer>(id);
return View(customer);
}
public ActionResult Edit(Customer c)
{
Session.Store(c);
return RedirectToAction("Get", c.Id);
}
}
Finally, I can see you're using StructureMap, so it only takes a few basic calls to get the Session from the DI framework:
public class SiteCreatedEventHandler : IHandleMessages<ISiteCreatedEvent>
{
public IBus Bus { get; set; }
public IUnitOfWork Uow { get; set; }
public IDocumentSession DocumentSession { get; set; }
public SiteCreatedEventHandler()
{
this.DocumentSession = ObjectFactory.GetInstance<IDocumentStore>().OpenSession();
}
public void Handle(ISiteCreatedEvent message)
{
using(DocumentSession)
{
try
{
Debug.Print(#"{0}{1}", message, Environment.NewLine);
///// Uow.Begin(); // Not needed for Load<T>
var site = DocumentSession.Load<Site>(message.SiteId);
//// Uow.Commit(); // Not needed for Load<T>
// invoke Hub and push update to screen
var context = GlobalHost.ConnectionManager.GetHubContext<AlarmAndNotifyHub>();
// TODO make sure this SignalR function is correct
context.Clients.All.displayNewSite(site, message.CommandId);
context.Clients.All.refreshSiteList();
}
catch (Exception ex)
{
//// Uow.Rollback(); // Not needed for Load<T>
}
}
}