I'm trying to host a Blazor component in an Avalonia application using a WebView.
I have a Blazor project with the following class:
public class ServerManager
{
private static WebApplication? _host;
public static void RunServer(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor();
builder.Services.AddSingleton<WeatherForecastService>();
_host = builder.Build();
if (_host.Environment.IsDevelopment())
{
_host.UseDeveloperExceptionPage();
}
else
{
_host.UseExceptionHandler("/Error");
_host.UseHsts();
}
_host.UseHttpsRedirection();
_host.UseStaticFiles();
_host.UseRouting();
_host.MapBlazorHub();
_host.MapFallbackToPage("/_Host");
_host.Start();
}
public static void StopServer()
{
_host?.DisposeAsync();
}
The main application has a reference to the Blazor project and calls RunServer on startup
and StopServer on exit. The WebView address is set to https://localhost:7012.
This approach worked fine in WPF but it seems that the server is not correctly started in Avalonia. What am I missing?
Related
Problem
When using RazorViewEngine.GetView((executingFilePath: viewPath, viewPath, isMainPage: false) in a WPF application, it can't find the view no matter what path I use.
Setup
WPF
.NET Core 3.1
What I have tried
When I do this in a console application using .NET Core 3.1, it works fine - it finds the view using the path ~/Views/Templates/MyTemplate.cshtml
This doesn't work when using same approach in a WPF application that targets .NET Core 3.1.
The MyTemplate.cshtml file lives inside Views/Templates in the root of the project.
I tried using paths like "~/bin/Debug/netcoreapp3.1/Views /MyTemplate.cshtml" and a bunch of other combinations but still it doesn't work.
I tried using the console application as a library and reference it from the WPF application but when I do that I get the same error, I moved all the code from the console application to the WPF project to avoid any cross project reference issues but still it doesn't work.
I looked here Razor engine cant find view but no luck.
The main difference is that in the WPF application I register the MvcCore services in App.xaml.cs and in the console app I do it on demand e.g: return provider.GetRequiredService<RazorViewToStringRenderer>(); but this should not be an issue.
Code
public App()
{
var serviceCollection = new ServiceCollection();
ConfigureServices(serviceCollection);
ServiceProvider = serviceCollection.BuildServiceProvider();
}
private void OnStartup(object sender, StartupEventArgs e)
{
var mainWindow = ServiceProvider.GetRequiredService<MainWindow>();
mainWindow.Show();
}
private void ConfigureServices(IServiceCollection services)
{
var applicationEnvironment = PlatformServices.Default.Application;
services.AddSingleton(applicationEnvironment);
var environment = new HostingEnvironment();
services.AddSingleton<IWebHostEnvironment>(environment);
var diagnosticSource = new DiagnosticListener("Microsoft.AspNetCore");
services.AddSingleton<DiagnosticListener>(diagnosticSource);
services.AddSingleton<DiagnosticSource>(diagnosticSource);
services.AddLogging();
services.AddMvcCore();
services.AddMvcCore().AddRazorPages();
services.AddMvcCore().AddRazorViewEngine();
services.AddMvcCore().AddViews();
services.AddSingleton<RazorViewToStringRenderer>();
services.AddSingleton<MainWindow>();
}
public class RazorViewToStringRenderer
{
private IRazorViewEngine _viewEngine;
private ITempDataProvider _tempDataProvider;
private IServiceProvider _serviceProvider;
public RazorViewToStringRenderer(
IRazorViewEngine viewEngine,
ITempDataProvider tempDataProvider,
IServiceProvider serviceProvider)
{
_viewEngine = viewEngine;
_tempDataProvider = tempDataProvider;
_serviceProvider = serviceProvider;
}
public IView FindView(ActionContext actionContext, string viewName)
{
var templatePath= "~/Views/Templates/MyTemplate.cshtml"; //I use this variable for testing but is passed in viewName param.
var getViewResult = _viewEngine.GetView(executingFilePath:templatePath, viewPath:templatePath, isMainPage: false);
if (getViewResult.Success) //This is always false
{
return getViewResult.View;
}
.....
}
}
I'm thinking that I'm missing some configuration in WPF that is preventing the razor pages to be found.
I am trying to figure out how to use hostbuilder pattern to run a console app (not a windows service). Intent is to keep the flow as similar to a WebApi to keep development practices similar. I have seen samples for using HostedService or BackGroundService, where they want to run it as a windows service. But If I am looking to run a simple console app, where do I specify my entrypoint class and method? From hostbuilder.Build(), I can see Run() and RunAsync() methods. But I am unable to figure out what will it execute?
I have seen other examples of where you can create servicecollection and then use serviceprovider.GetService().SomeMethod() to start the process. But that kind of deviates from what we want to do. So please suggest how to specify startup process. We are using 3.1 .Net Core.
class Program
{
static async void Main(string[] args)
{
var host = CreateHostBuilder(args).Build();
await host.RunAsync();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureServices((hostBuilderContext, serviceCollection) => new Startup(hostBuilderContext.Configuration).ConfigureServices(serviceCollection))
.UseSerilog()
;
}
EDIT: An update for .NET 6 is below ↓
Not much has changed with .NET 7.
I'd start off with the default worker template. It comes with necessary packages pre-installed. If you already have a project, install Microsoft.Extensions.Hosting package.
dotnet new worker -n MyCli
Then open up the Program.cs and build the host. Remove the Worker hosted service if you don't want to go with the hosted service route.
public class Program
{
public static void Main(string[] args)
{
var host = CreateHostBuilder(args).Build();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureServices((hostContext, services) =>
{
// remove the hosted service
// services.AddHostedService<Worker>();
// register your services here.
});
}
Build your logic:
internal class MyService
{
// you can also inject other services
private ILogger<MyService> _logger;
public MyService(ILogger<MyService> logger)
{
_logger = logger;
}
public void DoSomething()
{
_logger.LogInformation("Doing something");
}
}
Then register the class inside .ConfigureServices method
Host.CreateDefaultBuilder(args)
.ConfigureServices((hostContext, services) =>
{
services.AddTransient<MyService>();
});
Now you can resolve and call it inside the Main method:
public static void Main(string[] args)
{
var host = CreateHostBuilder(args).Build();
var myService = host.Services.GetRequiredService<MyService>();
myService.DoSomething();
}
.NET 6 update
With .NET 6, boilerplate is reduced significantly. We can rewrite our Program.cs as:
var host = Host.CreateDefaultBuilder(args)
.ConfigureServices(services => { services.AddTransient<MyService>(); })
.Build();
var my = host.Services.GetRequiredService<MyService>();
await my.ExecuteAsync();
class MyService
{
private readonly ILogger<MyService> _logger;
public MyService(ILogger<MyService> logger)
{
_logger = logger;
}
public async Task ExecuteAsync(CancellationToken stoppingToken = default)
{
_logger.LogInformation("Doing something");
}
}
Basically:
Instantiate your host builder and configure your services and whatnot.
Make a class with a method for your programme and register that class as a service.
Build the host, create a scope, get an instance of your class, call your method.
My programme is the mehod MainAsync in my class ProgramAsync.
class Program
{
static void Main(string[] args)
{
using (IHost host = Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration(cfg =>
{
cfg.AddJsonFile("appsettings.json");
})
.ConfigureServices((context, services) =>
{
services.AddDbContext<EquitiesDbContext>(options => { options.UseSqlServer(context.Configuration.GetConnectionString("Equities")); });
services.AddScoped<ProgramAsync>();
})
.ConfigureLogging((context, cfg) =>
{
cfg.ClearProviders();
cfg.AddConfiguration(context.Configuration.GetSection("Logging"));
cfg.AddConsole();
})
.Build()
)
{
using( IServiceScope scope = host.Services.CreateScope() )
{
ProgramAsync p = scope.ServiceProvider.GetRequiredService<ProgramAsync>();
p.MainAsync().Wait();
}
}
Console.WriteLine("Done.");
}
}
I premise that I'm new to .NET Core and usually in Full Framework I used TopShelf to use console app as service.
Now I've successfully created my .NET Core application that serves WebApi, but I need to attach another service I wrote (it's an IBMMQ service that receives messages and dispatches them back).
My current program.cs is
class Program
{
static void Main(string[] args)
{
DbProviderFactories.RegisterFactory("System.Data.SqlClient", SqlClientFactory.Instance);
Directory.SetCurrentDirectory(System.AppDomain.CurrentDomain.BaseDirectory);
var host = WebHost.CreateDefaultBuilder()
// .UseContentRoot(pathToContentRoot)
.UseStartup<Startup>()
.Build();
host.Run();
}
}
and here's my startup
class Startup
{
private readonly Container container = new Container();
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
IntegrateSimpleInjector(services);
services.AddControllers();
services.AddSimpleInjector(container);
services.BuildServiceProvider(validateScopes: true)
.UseSimpleInjector(container);
}
private void IntegrateSimpleInjector(IServiceCollection services)
{
container.Options.DependencyInjectionBehavior =
new SerilogContextualLoggerInjectionBehavior(container.Options);
services.AddHttpContextAccessor();
services.AddSingleton<IControllerActivator>(
new SimpleInjectorControllerActivator(container));
services.EnableSimpleInjectorCrossWiring(container);
services.UseSimpleInjectorAspNetRequestScoping(container);
}
public void Configure(IApplicationBuilder app, Microsoft.AspNetCore.Hosting.IHostingEnvironment env)
{
InitializeContainer(app);
DataConnection.DefaultSettings = new Linq2DbSettings();
LinqToDB.Common.Configuration.Linq.AllowMultipleQuery = true;
app.UseRouting();
app.UseEndpoints(endpointRouteBuilder => endpointRouteBuilder.MapControllers());
container.RegisterMvcControllers(app);
container.Verify();
}
private void InitializeContainer(IApplicationBuilder app)
{
//register container
}
}
Is there a way I can add my service's startup here?
Thanks
I'm trying to build a Windows Service using the latest Dotnet Core 2.1 runtime. I'm NOT hosting any aspnet, I do not want or need it to respond to http requests.
I've followed the code found here in the samples: https://github.com/aspnet/Docs/tree/master/aspnetcore/fundamentals/host/generic-host/samples/2.x/GenericHostSample
I've also read this article: https://learn.microsoft.com/en-us/aspnet/core/fundamentals/host/generic-host?view=aspnetcore-2.1
The code works great when run inside of a console window using dotnet run. I need it to run as a windows service. I know there's the Microsoft.AspNetCore.Hosting.WindowsServices, but that's for the WebHost, not the generic host. We'd use host.RunAsService() to run as a service, but I don't see that existing anywhere.
How do I configure this to run as a service?
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
namespace MyNamespace
{
public class Program
{
public static async Task Main(string[] args)
{
try
{
var host = new HostBuilder()
.ConfigureHostConfiguration(configHost =>
{
configHost.SetBasePath(Directory.GetCurrentDirectory());
configHost.AddJsonFile("hostsettings.json", optional: true);
configHost.AddEnvironmentVariables(prefix: "ASPNETCORE_");
configHost.AddCommandLine(args);
})
.ConfigureAppConfiguration((hostContext, configApp) =>
{
configApp.AddJsonFile("appsettings.json", optional: true);
configApp.AddJsonFile(
$"appsettings.{hostContext.HostingEnvironment.EnvironmentName}.json",
optional: true);
configApp.AddEnvironmentVariables(prefix: "ASPNETCORE_");
configApp.AddCommandLine(args);
})
.ConfigureServices((hostContext, services) =>
{
services.AddLogging();
services.AddHostedService<TimedHostedService>();
})
.ConfigureLogging((hostContext, configLogging) =>
{
configLogging.AddConsole();
configLogging.AddDebug();
})
.Build();
await host.RunAsync();
}
catch (Exception ex)
{
}
}
}
#region snippet1
internal class TimedHostedService : IHostedService, IDisposable
{
private readonly ILogger _logger;
private Timer _timer;
public TimedHostedService(ILogger<TimedHostedService> logger)
{
_logger = logger;
}
public Task StartAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("Timed Background Service is starting.");
_timer = new Timer(DoWork, null, TimeSpan.Zero,
TimeSpan.FromSeconds(5));
return Task.CompletedTask;
}
private void DoWork(object state)
{
_logger.LogInformation("Timed Background Service is working.");
}
public Task StopAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("Timed Background Service is stopping.");
_timer?.Change(Timeout.Infinite, 0);
return Task.CompletedTask;
}
public void Dispose()
{
_timer?.Dispose();
}
}
#endregion
}
EDIT: I repeat, this is not to host an ASP.NET Core app. This is a generic hostbuilder, not a WebHostBuilder.
As others have said you simply need to reuse the code that is there for the IWebHost interface here is an example.
public class GenericServiceHost : ServiceBase
{
private IHost _host;
private bool _stopRequestedByWindows;
public GenericServiceHost(IHost host)
{
_host = host ?? throw new ArgumentNullException(nameof(host));
}
protected sealed override void OnStart(string[] args)
{
OnStarting(args);
_host
.Services
.GetRequiredService<IApplicationLifetime>()
.ApplicationStopped
.Register(() =>
{
if (!_stopRequestedByWindows)
{
Stop();
}
});
_host.Start();
OnStarted();
}
protected sealed override void OnStop()
{
_stopRequestedByWindows = true;
OnStopping();
try
{
_host.StopAsync().GetAwaiter().GetResult();
}
finally
{
_host.Dispose();
OnStopped();
}
}
protected virtual void OnStarting(string[] args) { }
protected virtual void OnStarted() { }
protected virtual void OnStopping() { }
protected virtual void OnStopped() { }
}
public static class GenericHostWindowsServiceExtensions
{
public static void RunAsService(this IHost host)
{
var hostService = new GenericServiceHost(host);
ServiceBase.Run(hostService);
}
}
I hope you found the solution for this problem.
In my case I used generic host (introduced in 2.1) for such purpose and then just wrap it up with systemd to run it as a service on Linux host.
I wrote a small article about it https://dejanstojanovic.net/aspnet/2018/june/clean-service-stop-on-linux-with-net-core-21/
I hope this helps
IHostedService if for [asp.net core] backendjob,
if u want to build a windows service on .net core, u should reference this package System.ServiceProcess.ServiceController, and use ServiceBase as base class.
(you can also start from a .net framework windows service and then change the .csproj file)
edit: please see this doc and this code https://github.com/aspnet/Hosting/blob/dev/src/Microsoft.AspNetCore.Hosting.WindowsServices/WebHostWindowsServiceExtensions.cs.
To create a windows service ServiceBase for manage your IHost
With SignalR 2.0 in a self-hosted application, from these instructions you have something like this:
class Startup
{
public void Configuration(IAppBuilder app)
{
app.MapSignalR(new HubConfiguration { Resolver = ... });
}
}
class Program
{
static void Main(string[] args)
{
using (WebApp.Start("http://localhost:8080")) // constructs Startup instance internally
{
Console.WriteLine("Server running on {0}", url);
Console.ReadLine();
}
}
}
You will notice that the Startup class instance is created with some behind-the-scenes magic. I can't figure out how to fill in dependencies on it. Is there some way to override the construction of the Startup class so that I can inject dependencies into it?
Instead of replacing the IAppActivator, you can simply register Startup's constructor arguments with Katana's ServiceProvider.
The default IAppActivator will resolve any services matching the Startup constructor's argument types for you. The only downside is you can't use WebApp.Start, since that doesn't expose the ServiceProvider:
public class MyService : IMyService
{
private readonly IMyOtherService _myOtherService;
// Services will be recursively resolved by Katana's ServiceProvider
public MyService(IMyOtherService myOtherService)
{
_myOtherService = myOtherService;
}
// Implementation
}
public class Startup
{
private readonly IMyService _myService;
// Startup must have exactly one constructor.
public Startup(IMyService myService)
{
_myService = myService
}
public void Configuration(IAppBuilder app)
{
app.MapSignalR(new HubConfiguration { Resolver = ... });
}
}
using System;
using Microsoft.Owin.Hosting;
using Microsoft.Owin.Hosting.Services;
using Microsoft.Owin.Hosting.Starter;
public class Program
{
static void Main(string[] args)
{
var url = "http://localhost:8080";
var services = (ServiceProvider)ServicesFactory.Create();
var options = new StartOptions(url);
services.Add<IMyOtherService, MyOtherService>();
services.Add<IMyService, MyService>();
var starter = services.GetService<IHostingStarter>();
using (starter.Start(options)) // constructs Startup instance internally
{
Console.WriteLine("Server running on {0}", url);
Console.ReadLine();
}
}
}
I copied the default implementation of WebApp.Start into Program.Main, but instead of calling IHostingStarter.Start immediately, I add custom services first: http://katanaproject.codeplex.com/SourceControl/changeset/view/c726b87e90c05677a256ca1821bac481f402d6bd#src/Microsoft.Owin.Hosting/WebApp.cs
There are a bunch of other overloads for ServiceProvider.Add if you need them: http://msdn.microsoft.com/en-us/library/microsoft.owin.hosting.services.serviceprovider(v=vs.111).aspx
This should be much simpler than replacing Katana's IAppActivator using StartOptions.Settings like I suggest in my previous answer.
I am leaving my previous answer up, however, as it does explain in more detail how the Startup class is constructed and how to replace default service implementations using the Settings dictionary.
Checkout the dependency injection information here: http://www.asp.net/signalr/overview/signalr-20/extensibility/dependency-injection
Should have everything you need to know :)
Hope this helps!
class Startup
{
private readonly IDependencyResolver _resolver;
public Startup(IDependencyResolver resolver)
{
_resolver = resolver;
}
public void Configuration(IAppBuilder app)
{
app.MapSignalR(new HubConfiguration { Resolver = _resolver; });
}
}
class Program
{
static void Main(string[] args)
{
Startup startup = new Statrtup(new MyResolver());
using (WebApp.Start("http://localhost:8080", startup.Configuration))
{
Console.WriteLine("Server running on {0}", url);
Console.ReadLine();
}
}
}