Blazor Hosting Startup Assembly not being recognized - .net-core

I am building a Blazor app (both wasm and server) which both share an API and a set of Services. I have the services broken out into its own class library. There are probably 50 or so services and I dont want to duplicate the service declarations in the Server and WASM configuration sections.
Current Situation
WASM
public static async Task Main(string[] args)
{
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("app");
var zooAPI = new Uri("http://localhost:51552/api/v1/");
builder.Services.AddHttpClient<IZooService, ZooService>(client => client.BaseAddress = zooAPI);
await builder.Build().RunAsync();
}
Server
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages();
services.AddServerSideBlazor();
var zooAPI = new Uri("http://localhost:51552/api/v1/");
services.AddHttpClient<IZooService, ZooService>(client => client.BaseAddress = zooAPI);
}
Since both of these register the services which will end up being over 50 services id like to add a Startup within my Services Class Library.
[assembly: HostingStartup(typeof(Zoo.Services.ServicesStartup))]
namespace Zoo.Services
{
public class ServicesStartup : IHostingStartup
{
public void Configure(IWebHostBuilder builder)
{
var zooAPI = new Uri("http://localhost:51552/api/v1/");
builder.ConfigureServices((context, services) =>
{
services.AddHttpClient<IZooService, ZooService>(client => client.BaseAddress = zooAPI);
});
}
}
}
The issue I have is that this Startup is not being recognized and the Services are not being registered. The exception is "An Unhandled exception occured while processing the request. InvalidOperationException: Cannot provide a value for property 'ZooService'. There is no registered service of type IZooService.
What am I missing to have this ServiceStartup recognized and the registered upon app start?

In order for the Blazor app to know there is a HostingStartup it needs to read you need to tell it the assemblies to look into.
For Server Hosting in the Program.cs you can add the StartupAssemblies Keys or you can add it to the enviornmentvariables ASPNETCORE_HOSTINGSTARTUPASSEMBLIES
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseSetting(WebHostDefaults.HostingStartupAssembliesKey, "Zoo.Services").UseStartup<Startup>();
});
I have not figured out how to add it to the WASM yet

Related

What is the best way to access configuration data in a .NET Core worker service/console app?

I have a series of .NET Core worker services (targeting .NET 6). I am accessing configuration data from appsettings.json through hostcontext and that works fine:
public static async Task Main(string[] args)
{
// Start the application
await CreateHostBuilder(args).Build().RunAsync();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.UseWindowsService()
.ConfigureServices((hostContext, services) =>
{
// I can access config data like this:
var connectionString = hostcontext.Configuration["ConnectionStrings:MyConnectionString"];
});
However, I now need to get configuration data from within Main() before CreateHostBuilder() is called.
I can do something like this, but is there a better way? It feels wrong to build a ConfigurationBuilder just to grab this one value, is there a more efficient way to do this?
public static async Task Main(string[] args)
{
// Get the configuration
var environmentName = Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT") ?? "Production";
var builder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json")
.AddJsonFile($"appsettings.{environmentName}.json", true, true);
var configuration = builder.Build();
// Start the application
await CreateHostBuilder(args).Build().RunAsync();
}

How to get logger/configuration into a static class for OnTicketReceived event of AAD B2C using Razor Pages and dotnetcore 3.1

I am using Azure B2C in a Razor Pages web app on dotnet core 3.1 and I want to log information to my database whenever a user is added or changed. I also want to confirm a user is in the database when they sign in, and add them if they are not. Right now I'm just trying to get the "new user" running. I don't know how to get logging and configuration into my static class given the event has a specific signature.
public UserRepository(ILogger<UserRepository> logger, IConfiguration configuration)
{
_logger = logger;
_configuration = configuration;
}
I have been following this article and I have read this post and have implemented a static class with:
public async static Task<Task> OnTicketReceivedCallback(TicketReceivedContext context)
{
//OnTicketReceived
//Check if user just completed signup flow
List<Claim> claims = context.Principal.Claims.ToList();
bool isNewUser = claims.FirstOrDefault(x => x.Type == "newUser") == null ? false : true;
//If so, do what needs to be done
if (isNewUser)
{
//This is where I am getting the error and don't know how to push the logger and configuration through the stack
UserRepository repo = new UserRepository();
}
return Task.CompletedTask;
}
Being called from the startup:
public void ConfigureServices(IServiceCollection services)
{
services.AddMicrosoftIdentityWebAppAuthentication(Configuration, "AzureAdB2C");
services.Configure<OpenIdConnectOptions>(OpenIdConnectDefaults.AuthenticationScheme, options =>
options.Events.OnTicketReceived = B2CExtensions.OnTicketReceivedCallback
);
services.AddRazorPages()
.AddMicrosoftIdentityUI();
}
Make OnTicketReceivedCallback class non static and register it as a singleton. That way you can pass an ILogger trough the constructor.

Configuring Rebus in a .net core Worker Service (or a Console App)

I have seen that Adding rebus in the ASP.NET Core execution pipeline is very neat using Startup.cs.
I wonder if there is a same neat way to do the same for Worker service or generally a console app.
Most .net core console apps I have seen are very simple demo applications.
Kindly if there is any concrete sample configuration using .net core console application.
Regards
Amour Rashid
One way would be to add Microsoft's Microsoft.Extensions.Hosting package and build your background service as a BackgroundService:
public class MyBackgroundService : BackgroundService
{
readonly IServiceCollection _services = new ServiceCollection();
public BackgroundService()
{
// configure the bus
services.AddResbus(
configure => configure
.Transport(t => t.Use(...))
);
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
using var provider = _services.BuildServiceProvider();
// start the bus
provider.UseRebus();
while (!stoppingToken.IsCancellationRequested)
{
await Task.Delay(TimeSpan.FromSeconds(1), stoppingToken);
}
}
}
which you then add by going
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureServices((hostContext, services) =>
{
services.AddHostedService<MyBackgroundService>();
});
in your startup.
Thanks Mogens,
Another way is to
var host =CreateHostBuilder(args).Build();
host.UseRebus();
host.Run();
Or in ConfigureServices method
....
var provider=services.CreateServiceProvider();
provider.UseRebus();
It helped me I could create Worker Services using rebus.

Can't call client method from server

I'm trying to use SignalR to broadcast a message from the server to the client without the client triggering the message. From tutorials that I've seen, defining a method in the client, like so:
signalRConnection.client.addNewMessage = function(message) {
console.log(message);
};
should allow the following hub code to be used on the server:
public async Task SendMessage(string message)
{
await Clients.All.addNewMessage("Hey from the server!");
}
However, the Clients.All.addNewMessage call causes an error in the C# compiler:
'IClientProxy' does not contain a definition for 'addNewMessage' and no accessible extension method 'addNewMessage' accepting a first argument of type 'IClientProxy' could be found (are you missing a using directive or an assembly reference?)
How do I fix this? The server code is contained within the hub.
This is because you are using ASP.NET Core SignalR but you are calling client method following ASP.NET MVC SignalR. In ASP.NET Core SignalR you have to call the client method as follows:
public async Task SendMessage(string message)
{
await Clients.All.SendAsync("AddNewMessage", message); // here `AddNewMessage` is the method name in the client side.
}
It showing your client side code is also for ASP.NET MVC SignalR. For ASP.NET Core SignalR it should be as follows:
"use strict";
var connection = new signalR.HubConnectionBuilder().withUrl("/chatHub").build();
connection.on("AddNewMessage", function (message) {
// do whatever you want to do with `message`
});
connection.start().catch(function (err) {
return console.error(err.toString());
});
And In the Startup class SignalR setup should be as follows:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddSignalR(); // Must add this
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseSignalR(routes =>
{
routes.MapHub<ChatHub>("/chatHub"); // Here is configuring for `ChatHub`
});
app.UseMvc();
}
}
Please follow Get started with ASP.NET Core SignalR this tutorial if you face further problem.

Application Insights in IHostedService console application

I am trying to enable Application Insights in a console application using IHostedService (for the moment, it's a simple console application which we run as WebJob, in future in containers).
As far as my knowledge goes, in the following code, so far we do not have any extension to register globally Application Insights as an implementation of ILogger:
public static class Program
{
public static Task Main(string[] args)
{
var hostBuilder = new HostBuilder()
.ConfigureHostConfiguration(config =>
{
config.SetBasePath(Directory.GetCurrentDirectory());
config.AddJsonFile("appsettings.json", optional: false);
config.AddEnvironmentVariables();
})
.ConfigureLogging((context, logging) =>
{
logging.AddConfiguration(context.Configuration.GetSection("Logging"));
if (context.HostingEnvironment.IsDevelopment())
{
logging.AddConsole();
}
else
{
//TODO: register ApplicationInsights
}
});
return hostBuilder.RunConsoleAsync();
}
}
So far, I found out that potentially, I should be able to set everything up using custom implementation of the logger, i.e. public class ApplicationInsightsLogger : ILogger, and then... register it in the container so that DI resolves it.
Is this the right direction?
I made an extension that I could use from either an IHost or an IWebHost:
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.ApplicationInsights;
public static class LoggingBuilderExtensions
{
public static ILoggingBuilder AddLogging(this ILoggingBuilder loggingBuilder)
{
loggingBuilder.AddFilter<ApplicationInsightsLoggerProvider>("", LogLevel.Trace);
loggingBuilder.AddAzureWebAppDiagnostics();
loggingBuilder.AddApplicationInsights();
return loggingBuilder;
}
}
Since I'm not sending in the context (HostBuilderContext or WebHostBuilderContext), I can use it in either app type like this:
new HostBuilder().ConfigureLogging(loggingBuilder => loggingBuilder.AddLogging())
or
WebHost.CreateDefaultBuilder().ConfigureLogging(loggingBuilder => loggingBuilder.AddLogging())
If you needed a specific property from the context (like environment type), you could extract that and send it in as a parameter to the extension.
Here's a reference: https://github.com/Microsoft/ApplicationInsights-dotnet-logging/blob/develop/src/ILogger/Readme.md

Resources