How to inject a service into Blazor? - c#

I'm having trouble trying to inject some services into the client project. In the Program.cs file I have the following:
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) });
await builder.Build().RunAsync();
ConfigureServices(builder.Services);
}
public static void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IMetamaskInterop, MetamaskBlazorInterop>();
services.AddSingleton<MetamaskService>();
services.AddSingleton<MetamaskInterceptor>();
}
}
In a new component I try to inject these services as follows:
#inject IJSRuntime JSRuntime;
#inject MetamaskService metamaskService;
#inject MetamaskInterceptor metamaskInterceptor;
But doing this is giving me an error and it is the following:
I appreciate any help or guidance.

You are configuring the services after you start you app.
Add ConfigureServices(builder.Services); before await builder.Build().RunAsync();.
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) });
// configure services before running the app
ConfigureServices(builder.Services);
await builder.Build().RunAsync();
}
public static void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IMetamaskInterop, MetamaskBlazorInterop>();
services.AddSingleton<MetamaskService>();
services.AddSingleton<MetamaskInterceptor>();
}
}

You shouldn't introduce a new static method named ConfigureServices into the Program class. This is not the reason for the exception thrown, but it is greatly misleading as ConfigureServices is the name of a method defined in the Startup class (used in the past in WebAssembly Blazor, and then removed...).
Sometimes, you may need a second method for conditional configuration, but don't name it ConfigureServices. In the current instance, you don't need a second method. Just do it as it is done below.
When you use a second method, you mustn't run
await builder.Build().RunAsync(); as this code build and execute your app, rendering it too late to add more services to the service collection. Just pass the IServiceCollection object to the second method, and when it returns build and run your app.
Here's the updated version of your code:
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.AddSingleton<IMetamaskInterop, MetamaskBlazorInterop>();
builder.Services.AddSingleton<MetamaskService>();
builder.Services.AddSingleton<MetamaskInterceptor>();
await builder.Build().RunAsync();
}
}

Related

Activity is null when using Microsoft Hosting Extensions and net472

I am trying to use OpenTelemetry with my net472 app that uses Microsoft.Extensions.Hosting.
I create my host like this:
Host.CreateDefaultBuilder()
.ConfigureServices((hostContext, services) =>
{
services.AddOpenTelemetry().WithTracing(tracerProviderBuilder =>
{
tracerProviderBuilder
.SetResourceBuilder(ResourceBuilder.CreateDefault().AddService("MySample"))
.AddConsoleExporter()
.AddSource(serviceName);
}).StartWithHost();
})
.Build();
If I then try to create a new activity like this, it is null:
var activitySource = new ActivitySource(serviceName);
using var activity = activitySource.StartActivity("Hello");
If instead I register OpenTelemetry like this, it works just fine:
using var tracerProvider = Sdk.CreateTracerProviderBuilder()
.SetResourceBuilder(ResourceBuilder.CreateDefault().AddService("MySample"))
.AddSource(serviceName)
.AddConsoleExporter()
.Build();
How can I get an ActivitySource that has the configured listener using the first approach of creating a Host?
The solution that worked for me is to resolve the TracerProvider using the ServiceCollection. That way the listener gets subscribed and ActivitySource is able to start activities.
This is how I register the services.
Host.CreateDefaultBuilder()
.ConfigureServices((hostContext, services) =>
{
services.AddSingleton(new ActivitySource(serviceName));
services.AddOpenTelemetry().WithTracing(tracerProviderBuilder =>
{
tracerProviderBuilder
.SetResourceBuilder(ResourceBuilder.CreateDefault().AddService("MySample"))
.AddConsoleExporter()
.AddSource(serviceName);
});
})
.Build();
And then when TracerProvider is resolved, it's build using the configured TracerProvider:
using var tracerProvider = ServiceLocator.GetService<TracerProvider>();
var activitySource = ServiceLocator.GetService<ActivitySource>();
// Now this doesn't provide a null object
using var activity = activitySource.StartActivity("Hello");
Just for reference, this is ServiceLocator:
public static class ServiceLocator
{
internal static IHost Host { get; set; }
public static T GetService<T>() where T : class
{
return (T)Host.Services.GetService(typeof(T));
}
}
Thanks for the hint, i tried to adopt this on my .net core 7 application.
The following code seems to fix the bug for me too.
var app = builder.Build();
...
app.UseTelemetry();
...
app.Run();
public static void UseTelemetry(this IApplicationBuilder app)
{
_ = app.ApplicationServices.GetRequiredService<TracerProvider>();
}

.NET 6 IWebHost vs IHost vs WebApplication: Startup class with ILogger Dependency Injection

I migrated a .NET WebApp from originally 2.1 to 3.1 and 6.0.
I would like to switch to the new 'minimal hosting model' from .NET 6 but still want to keep my long and complex Startup class separated.
I am using Serilog with custom configuration as ILogger implementation: I create a LoggerBuilder in a separate class.
My main looks like this:
public static async Task Main(string[] args)
{
// global shared logger, created BEFORE the host build to be able to log starting and ending the service.
Log.Logger = LoggerBuilder.BuildLogger();
try
{
LoggerAudit.LogAuditAsWarning("Starting " + Constants.ServiceName);
using var source = new CancellationTokenSource();
await CreateWebHost(args).RunAsync(source.Token).ConfigureAwait(false);
//await CreateHost(args).RunAsync(source.Token).ConfigureAwait(false);
//await CreateWebApp(args).RunAsync(source.Token).ConfigureAwait(false);
}
catch (Exception ex)
{
LoggerAudit.GlobalLogAudit.Fatal(ex, Constants.ServiceName + " terminated unexpectedly");
}
finally
{
LoggerAudit.LogAuditAsWarning("Closing " + Constants.ServiceName);
Log.CloseAndFlush();
}
}
with 3 implementations of the Host or WebApp, which should be equivalent
private static IWebHost CreateWebHost(string[] args) // .NET Core 2.1 way
{
IWebHostBuilder builder = WebHost.CreateDefaultBuilder(args)
.UseSerilog()
.SuppressStatusMessages(true)
.ConfigureAppConfiguration((hostContext, config) =>
{
config.BuildConfiguration();
})
.UseStartup<Startup>()
.UseUrls("http://*:5000");
return builder.Build();
}
private static IHost CreateHost(string[] args) // .NET Core 3.1 way
{
IHostBuilder builder = Host.CreateDefaultBuilder(args)
.UseSerilog()
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder
.SuppressStatusMessages(true)
.ConfigureAppConfiguration((hostContext, config) =>
{
config.BuildConfiguration();
})
.UseStartup<Startup>()
.UseUrls("http://*:5000");
});
return builder.Build();
}
private static WebApplication CreateWebApp(string[] args) // .NET 6 way
{
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
builder.Host.UseSerilog();
var startup = new Startup(builder.Configuration, null);
startup.ConfigureServices(builder.Services);
WebApplication app = builder.Build();
IApiVersionDescriptionProvider provider = app.Services.GetRequiredService<IApiVersionDescriptionProvider>();
startup.Configure(app, app.Environment, provider, app.Lifetime);
return app;
}
All 3 ways are still compatible with .NET 6 and suppose to work fine.
Now, UseSerilog() method tells me the WebHost way is obsolete, so I should migrate. So it makes sense to migrate to the newest model with WebApplication.
The catch is the constructor signature of my Startup class
public Startup(IConfiguration config, ILogger<Startup> logger)
I used to still have the CreateWebHost version active.
Switching to CreateHost throws an exception in the Startup constructor:
"Unable to resolve service for type 'Microsoft.Extensions.Logging.ILogger`1[Siemens.BT.Edge.CloudConnector.Startup]' while attempting to activate 'Siemens.BT.Edge.CloudConnector.Startup'."
WHY ?
To avoid the exception, I can use the Startup factory:
.UseStartup(builderContext => new Startup(builderContext.Configuration, null))
Then the question is: how can I use my ILogger there with Dependency Injection?
And the same apply when switching to CreateWebApp.
In any other situation, I should use the
var logger = app.Services.GetService<ILogger<Startup>>();
but obviously, I can't call that before calling the Build method and Startup ctor.
So the better question would be: why did my Startup constructor work with WebHost?
so the answer for the WebApplication is to use the BuildServiceProvider:
private static WebApplication CreateWebApp(string[] args) // .NET 6
{
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
builder.Host.UseSerilog();
ILogger<Startup> logger = builder.Services.BuildServiceProvider().GetService<ILogger<Startup>>();
var startup = new Startup(builder.Configuration, logger);
startup.ConfigureServices(builder.Services);
WebApplication app = builder.Build();
IApiVersionDescriptionProvider provider = app.Services.GetRequiredService<IApiVersionDescriptionProvider>();
startup.Configure(app, app.Environment, provider, app.Lifetime);
return app;
}
It is working but looks a bit ugly...

Do not validate JWT expiration in tests

I'm writing an integration test for my web service with JWT authentication. I'd like to test it with a token received from the real service. The problem is real tokens expire in 1 hour.
A possible way is to set options.TokenValidationParameters.ValidateLifetime inside AddJwtBearer of class Startup below.
However, Startup class is a also a code to be tested, so I don't want to change or replace it for testing.
Is there a neat way to test all JWT validation logic except expiration?
My project code:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddAuthentication("JWT")
.AddJwtBearer("JWT", options =>
{
options.Authority = "https://my-real-identity-server.com/";
options.Audience = "...";
// I don't want to disable lifetime validation in real life
// options.TokenValidationParameters.ValidateLifetime = false;
});
// Other stuff
}
public void Configure(IApplicationBuilder app) => app
.UseRouting()
.UseAuthentication()
.UseAuthorization()
.UseEndpoints(endpoints => endpoints.MapControllers());
}
public class TestController : ControllerBase
{
[Authorize]
[HttpGet("/validate")]
public string Get() => "success";
}
My test code:
public class HostBuilderTests
{
private IHost testHost;
private CancellationTokenSource cancel;
private HttpClient client;
[SetUp]
public async Task ShouldReturnStatus()
{
testHost = Host.CreateDefaultBuilder()
.ConfigureWebHostDefaults(webBuilder =>
webBuilder
.UseStartup<Startup>()
.UseTestServer())
.ConfigureServices(services => services
.AddLogging(b => b.ClearProviders().AddNUnit()))
.Build();
cancel = new CancellationTokenSource(10000);
var server = testHost.Services.GetRequiredService<IServer>()
.ShouldBeOfType<TestServer>();
await testHost.StartAsync(cancel.Token);
client = server.CreateClient();
}
[TearDown]
public async Task TearDown()
{
await testHost.StopAsync(cancel.Token);
client.Dispose();
testHost.Dispose();
cancel.Dispose();
}
[Test]
[TestCase("<<JWT token copied from the real service>>")]
public async Task StatusShouldBeOk(string realToken)
{
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", realToken);
using var response = await client.GetAsync("/validate", cancel.Token);
response.StatusCode.ShouldBe(HttpStatusCode.OK);
}
}
Finally I found a simple way to manage options for authentication handlers:
An authentication scheme is a name which corresponds to:
An authentication handler.
Options for configuring that specific instance of the handler.
See MSDN.
So it suffices to post-configure JwtBearerOptions specifying the authentication scheme name "JWT" as options instance name in the test setup:
testHost = Host.CreateDefaultBuilder()
// other setup
.ConfigureServices(services => services
.PostConfigure<JwtBearerOptions>("JWT",
op => op.TokenValidationParameters.ValidateLifetime = false)
// other setup
).Build();
Also it is possible to pass null instead of "JWT" as it written in comments:
// Null name is used to configure all named options.

How do you unit test RazorViewEngineOptions in ASP.NET Core MVC?

In an extension method called from ConfigureServices I'm adding an instance of EmbeddedFileProvider to RazorViewEngineOptions. I'd like to test that it gets added but I can't find how to get the RazorViewEngineOptions instance.
This works when the application is run:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.AddMyServices(Configuration);
}
public static IServiceCollection AddMyServices(this IServiceCollection services, IConfiguration configuration)
{
services.Configure<RazorViewEngineOptions>(options =>
{
options.FileProviders.Add(new EmbeddedFileProvider(typeof(MyClass).Assembly, "My.Namespace"));
});
return services;
}
But how do I test it? A NullReferenceException is thrown here:
[Fact]
public void MyTest()
{
var services = new ServiceCollection();
var serviceProvider = services.BuildServiceProvider();
MyServicesBuilder.AddMyServices(services, new Mock<IConfiguration>().Object);
var razorOptions = serviceProvider.GetService<IOptions<RazorViewEngineOptions>>();
Assert.Equal(1, razorOptions.Value.FileProviders.Where(x => x.GetType() == typeof(EmbeddedFileProvider)).Count());
}
I have tried adding services.AddMvc() or services.AddSingleton<RazorViewEngineOptions>().
I've also tried calling services.GetRequiredService<RazorViewEngineOptions>() but that throws System.InvalidOperationException : No service for type 'Microsoft.Extensions.Options.IOptions'1[Microsoft.AspNetCore.Mvc.Razor.RazorViewEngineOptions
I've also tried asking for RazorViewEngineOptions rather than IOptions<RazorViewEngineOptions>.
Anything added to the service collection after the provider has already been built, wont be known to the provider.
Added everything needed to the service collection and only then build the provider to perform your assertions
For example
[Fact]
public void MyTest() {
//Arrange
var services = new ServiceCollection();
services.AddOptions();
IConfiguration config = new ConfigurationBuilder()
// Call additional providers here as needed.
//...
.Build();
//Act
MyServicesBuilder.AddMyServices(services, config);
//OR
//services.AddMyServices(config);
//Assert
var serviceProvider = services.BuildServiceProvider();
var razorOptions = serviceProvider.GetService<IOptions<RazorViewEngineOptions>>();
Assert.NotNull(razorOptions);
Assert.Equal(1, razorOptions.Value.FileProviders.Where(x => x.GetType() == typeof(EmbeddedFileProvider)).Count());
}

How do I inject IHttpClientFactory in my Azure Service Fabric application?

I am trying to use IHttpClientFactory in my solution instead of just instances of HttpClient.
startup.cs:
services.AddHttpClient("Test", client =>
{
client.BaseAddress = new Uri("http://localhost:57863");
client.Timeout = new TimeSpan(0, 0, 30);
client.DefaultRequestHeaders.Clear();
});
and in my services in need of a HttpClient:
private readonly Uri _clusterLinuxUri;
private readonly IHttpClientFactory _clientFactory;
public LiasseService(ConfigSettings settings, IHttpClientFactory clientFactory)
{
_clusterLinuxUri = new Uri($"{settings.LinuxClusterEndpoint}");
_clientFactory = clientFactory;
}
public async Task<LiasseDetails> CreateLiasseAsync(LiasseCreate liasseData)
{
using (var response = await _clientFactory.CreateClient("Test")
.PostAsJsonAsync($"{_clusterLinuxUri}{_createPath}", liasseData))
{
await response.CheckHttpError($"{nameof(CreateLiasseAsync)} - error in CL");
var detailsList = await response.Content.ReadAsAsync<LiasseDetailsList>();
return detailsList.Details.FirstOrDefault();
}
}
The part I haven't figured out is how to inject it in Autofac.
program.cs
private static void Main()
{
try
{
var builder = new ContainerBuilder();
builder.RegisterModule(new GlobalAutofacModule());
builder.RegisterServiceFabricSupport();
builder.RegisterStatelessService<FacadeCore>("xxx.FacadeCoreType");
using (builder.Build())
{
ServiceEventSource.Current.ServiceTypeRegistered(Process.GetCurrentProcess().Id, typeof(FacadeCore).Name);
Thread.Sleep(Timeout.Infinite);
}
}
catch (Exception e)
{
ServiceEventSource.Current.ServiceHostInitializationFailed(e.ToString());
throw;
}
}
public class GlobalAutofacModule : Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterType<ConfigSettings>();
builder.RegisterType<PaymentService>().As<IPaymentService>();
builder.RegisterType<MailerService>().As<IMailerService>();
builder.RegisterType<LiasseService>().As<ILiasseService>();
builder.RegisterType<AnalyseFinanciereService>().As<IAnalyseFinanciereService>();
builder.RegisterType<ApimService>().As<IApimService>();
builder.RegisterType<UserRepository>().As<IUserRepository>();
builder.RegisterType<ApplicationProcessRepository>().As<IApplicationProcessRepository>();
builder.RegisterType<LiasseRepository>().As<ILiasseRepository>();
builder.RegisterType<CustomUserIdProvider>().As<IUserIdProvider>();
}
}
Am I supposed to create some custom client that implements IHttpClientFactory to be able to inject it? How should I do this? Any examples? Thanks.
Please see Interface documentation here
So to answer your question:
1) Using IServiceCollection from 'ConfigureServices' method call .AddHttpClient()
2) Create new Autofac container builder and populate it with IServiceCollection mentioned above
3) From ConfigureServices method return new AutofacServiceProvider
public IServiceProvider ConfigureServices(IServiceCollection services)
{
...
services.AddHttpClient();
var containerBuilder = new ContainerBuilder();
containerBuilder.Populate(services);
var container = containerBuilder.Build();
return new AutofacServiceProvider(container);
}
P.S.
Make sure to add - Autofac.Extensions.DependencyInjection nuget package, in order to be able to use AutofacServiceProvider class.
Apart from doing using a WebHostBuilder based solution similar to what you get when creating a vanilla Asp.Net Core Service Fabric project, you can alternatively just do this
public class HttpClientModule : Module
{
protected override void Load(ContainerBuilder builder)
{
builder.Register(_ =>
{
var services = new ServiceCollection();
services.AddHttpClient();
var containerBuilder = new ContainerBuilder();
containerBuilder.Populate(services);
var container = containerBuilder.Build();
return new AutofacServiceProvider(container);
})
.As<IServiceProvider>()
.SingleInstance();
builder.Register(ctx =>
{
var scope = ctx.Resolve<IComponentContext>();
var provider = scope.Resolve<IServiceProvider>();
var factory = provider.GetService<IHttpClientFactory>();
return factory.CreateClient();
}).As<HttpClient>();
}
}

Categories

Resources