Open davidj028 opened 2 years ago
Ninject itself supports .NET Core/.NET 6 and Revo provides adapters for seamless integration with ASP.NET Core apps (see Revo.AspNetCore package). For Blazor WebAssembly, you would just need to replicate what is done in RevoStartup class (i.e. create the Ninject kernel, load packages and maybe register some extra stuff) - this should actually be quite easy.
More important question is, how do you plan on using the framework in your application? Do you plan on having a separate backend application and just share some parts with the client app, or do you want to do everything on the client (e.g. database persistence)? Having database persistence in the client app might prove more tricky - while EF Core should work in WebAssembly fine, you can't really open regular network connections to databases, which probably leaves you with the option to use some in-memory DB providers or maybe SQLite (which apparently should be possible now - but I haven't tested either). It's an interesting topic though and I might look into trying this out later.
On a side note, regarding your question about DI - yes, we plan get rid of the Ninject dependency, possibly replacing it with the Microsoft's standard DI infrastructure.
We are building an app that will be occasionally offline, so we need to persist the data on the device. We were wanting to explore using it to setup all of our commands/handlers/events/listeners/etc, and then take advantage of the generic IRepository to store JSON in SQLite. We were also planning on implementing an IAsyncEventListener to push the data to our server when the client is online.
I have the basic Blazor hello world app, and have added Revo.Core, Revo.DataAccess, and Revo.Infrastructure packages.
I've then tweaked RevoStartup to look like this:
using Ninject;
using Ninject.Activation;
using Ninject.Infrastructure.Disposal;
using Revo.Core.Configuration;
using Revo.Core.Types;
using Revo.Infrastructure;
namespace RevoFramework.Startup
{
public class RevoStartup
{
private static readonly AsyncLocal<Scope> scopeProvider = new AsyncLocal<Scope>();
public static object RequestScope(IContext context) => scopeProvider.Value;
private KernelBootstrapper kernelBootstrapper;
public IKernel Kernel { get; private set; }
public virtual void ConfigureServices(IServiceCollection services)
{
CreateKernel();
services.AddSingleton(sp => Kernel);
kernelBootstrapper = new KernelBootstrapper(Kernel, new RevoConfiguration().ConfigureInfrastructure());
var typeExplorer = new TypeExplorer();
kernelBootstrapper.Configure();
var assemblies = typeExplorer
.GetAllReferencedAssemblies()
.Where(a => !a.GetName().Name.StartsWith("System."))
.Where(a => !a.IsDynamic).ToList();
kernelBootstrapper.LoadAssemblies(assemblies);
}
private void CreateKernel()
{
Kernel = new StandardKernel();
Kernel.Bind<Func<IKernel>>().ToMethod(ctx => () => Kernel);
Kernel.Bind<StandardKernel>().ToMethod(ctx => Kernel as StandardKernel);
}
private sealed class Scope : DisposableObject { }
}
}
And then my Blazor Program.cs looks like this:
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using Blazor;
using RevoFramework.Startup;
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.RootComponents.Add<HeadOutlet>("head::after");
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
RevoStartup revoStartup = new RevoStartup();
revoStartup.ConfigureServices(builder.Services);
await builder.Build().RunAsync();
I've added the events/commands/etc from the todo sample app, and modified the Counter.razor page to look like this:
@page "/counter"
@using Revo.Core.Commands
@using RevoFramework.Commands
@inject ICommandBus CommandBus
<PageTitle>Counter</PageTitle>
<h1>Counter</h1>
<p role="status">Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
@code {
private int currentCount = 0;
private Guid toDoList = new Guid();
private void IncrementCount()
{
currentCount++;
if (currentCount == 1) {
CommandBus.SendAsync(new CreateTodoListCommand(toDoList, $"ToDoList"));
}
else {
CommandBus.SendAsync(new AddTodoCommand(toDoList, $"TodDo {currentCount}"));
}
}
}
However, whenever I click on the counter page, it throws this error, so I've missed some critical piece and the dependency injection is not working.
Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer[100]
Unhandled exception rendering component: Cannot provide a value for property 'CommandBus' on type 'Blazor.Pages.Counter'. There is no registered service of type 'Revo.Core.Commands.ICommandBus'.
System.InvalidOperationException: Cannot provide a value for property 'CommandBus' on type 'Blazor.Pages.Counter'. There is no registered service of type 'Revo.Core.Commands.ICommandBus'.
at Microsoft.AspNetCore.Components.ComponentFactory.<>c__DisplayClass7_0.<CreateInitializer>g__Initialize|1(IServiceProvider serviceProvider, IComponent component)
at Microsoft.AspNetCore.Components.ComponentFactory.PerformPropertyInjection(IServiceProvider serviceProvider, IComponent instance)
at Microsoft.AspNetCore.Components.ComponentFactory.InstantiateComponent(IServiceProvider serviceProvider, Type componentType)
at Microsoft.AspNetCore.Components.RenderTree.Renderer.InstantiateComponent(Type componentType)
at Microsoft.AspNetCore.Components.RenderTree.Renderer.InstantiateChildComponentOnFrame(RenderTreeFrame& frame, Int32 parentComponentId)
at
Would definitely appreciate any help you can offer so we can determine whether Revo would be a good fit for us or not!
With ASP.NET Core controllers, the trick would be to register a custom IControllerActivator which lets us construct the instances on our own, using Ninject injector. Similarly, with Blazor, we need IComponentActivator.
I have set up an experimental branch with a test demo here: 8279f9b74a41d8d168ce4bcde2ecb7acfea2591c@experimental/blazor-wasm
I was initially worried about the database persistence and whether will the WASM SQLite work, but after figuring out the correct setup (you need the right Emscripten SDK version and .NET wasm-tools installed), I was actually blown away how well it worked almost out-of-the-box. The guys behind the .NET WASM runtime have done a really amazing job.
That being said, the example in the branch linked above implements just a super-simple UI for adding and viewing todo lists and the support for Blazor runtime is super-rudimentary and generally not thoroughly tested, so there might be some challenges ahead (e.g. I'm not sure about the threading model in Blazor/WASM and I have limited experience with these technologies). For real apps, you would probably want to implement persisting the SQLite data to IndexedDB using File System API as shown here, implement stuff like IUserContext, etc.
If you wanted to refactor this and maintain it as a new Revo in-repo provider package (e.g. Revo.Blazor), I'd be happy to consider merging it. I might even look into this myself, but no promises or ETA there.
That's great, thanks for this example! We'll go through it as soon as we can over the coming weeks.
We're interested in using this framework for a cross-platform application we're developing using Blazor. The main issue we're running into is trying to understand how to setup the dependency injection. It appears Ninject does not maintain an adapter for .net core. I've found an example on how to set this up with asp.net core, but it's not clear how this would translate to Blazor WebAssembly.
Autofac, for example, implements the IServiceProviderFactory, which provides a pretty simple implementation.
Do you know if this would be possible with Revo/Ninject, and if so, would you be able to provide a sample implementation?
If not, do you have plans to support Microsoft's standard DI in the future?