I am testing out Blazor and now trying to get a grip regarding services and custom services.
The problem is I get this error message when starting the Blazor application:
WASM: System.InvalidOperationException: Unable to resolve service for
type 'Blazor.Extensions.Storage.Interfaces.IStorage' while attempting
to activate 'BlazorTest.Services.MyLocalStorage'.
My own custom service MyLocalStorage requires another service to access the browsers local storage; in this case I am using Blazor.Extensions.Storage
According to this page on learn.microsoft.com, a custom service that depends on another service needs to have a constructor to support it: "Constructor injection must be used instead. Required services are added by adding parameters to the service's constructor. When dependency injection creates the service, it recognizes the services it requires in the constructor and provides them accordingly."
So, my ConfigureServices in Startup.cs looks like this:
public void ConfigureServices(IServiceCollection services)
{
services.AddStorage(); // <-- extension method for Blazor.Extensions.Storage
services.AddSingleton<IMyLocalStorage, MyLocalStorage>();
}
and my custom service:
public interface IMyLocalStorage
{
Task<string> GetAuthToken();
Task<string> GetDeviceUUID();
Task SetAuthToken(string authToken);
Task SetDeviceUuid(string deviceUuid);
}
public class MyLocalStorage : IMyLocalStorage
{
private readonly IStorage storage;
public MyLocalStorage(IStorage storage)
{
this.storage = storage;
}
// bla bla implementation
}
I thought this was the only thing required, but it still fails as can be seen above.
Note that if I skip my own custom serivce and use the Blazor.Extensions.Storage package directly, it works without any issues.
Im running VS2019 Preview 16.2.0 and .NET Core 3.0.0.
If you take a look at the Blazor Storage code here you can see it does not register the IStorage interface.
Further inspection of the SessionStorage and LocalStorage classes shows that both implement IStorage so even if you did register IStorage you would need to clarify which class you want to inject.
It looks like you need to inject SessionStorage or LocalStorage in your constructor instead.
public class MyLocalStorage : IMyLocalStorage
{
private readonly LocalStorage storage;
public MyLocalStorage(LocalStorage storage)
{
this.storage = storage;
}
// bla bla implementation
}
Related
in .razor file I use
#inject HttpClient Http
to get access to the HTTPClient.
Is there a way to do the same in a .cs file or do I have to pass it along as a parameter?
update
I thought I had it, but I don't.
Using statements
using System.Net.Http;
using Microsoft.AspNetCore.Components;
using System.Net.Http.Json;
defined as class parameter
[Inject]
protected HttpClient Http {get;set;}
in my call Task
await Http.GetFromJsonAsync<SharedGLAccount[]>($"api/{ST_comp}/GLAccounts")
getting the following error
Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer[100]
Unhandled exception rendering component: Value cannot be null. (Parameter 'client')
I suggest you use IHttpClientFactory for this. Checkout this documentation which explains the benefits of using this and also copied below:
Provides a central location for naming and configuring logical HttpClient instances. For example, a client named github could be registered and configured to access GitHub. A default client can be registered for general access.
Codifies the concept of outgoing middleware via delegating handlers in HttpClient. Provides extensions for Polly-based middleware to take advantage of delegating handlers in HttpClient.
Manages the pooling and lifetime of underlying HttpClientMessageHandler instances. Automatic management avoids common DNS (Domain Name System) problems that occur when manually managing HttpClient lifetimes.
Adds a configurable logging experience (via ILogger) for all requests sent through clients created by the factory.
An example of usage is:
In startup.cs file:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient(); // <- add this
You can inject using this in a service or repository class:
public class BasicService : IBasicService
{
private readonly IHttpClientFactory _httpClientFactory;
public BasicUsageModel(IHttpClientFactory httpClientFactory) // <- inject here
{
_httpClientFactory = httpClientFactory;
}
or this if its razor page code behind:
[Inject] public IHttpClientFactory HttpClientFactory { get; set; }
or this if its razor page:
#inject IHttpClientFactory HttpClientFactory
And use it like this:
var httpClient = _clientFactory.CreateClient(); // <- create HTTP client
i'm building a small webapi to work in conjunction with additional functionalities running in the background.
In the specific case I have a class called TelegramBot:
public class TelegramBot
{
static ITelegramBotClient botClient;
private readonly BotManagerContext _botManagerContext;
public TelegramBot(BotManagerContext botManagerContext)
{
_botManagerContext = botManagerContext;
botClient = new TelegramBotClient("xxxx:yyyyy");
botClient.OnMessage += Bot_OnMessage;
botClient.StartReceiving();
}
That I'm trying to run together with the web api. BotManagerContext is a DbContext initialized in the web api, i'm trying to retrieve it using dependency injection - so i'm trying to add the TelegramBot class into the Startup.cs file so that it starts as a Singleton and can retrieve the dbcontext
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<BotManagerContext>(opt =>
opt.UseSqlite("Data Source=BotManager.db"));
services.AddControllers();
services.AddSingleton<TelegramBot>();
}
Question is - how do I implement this? using an interface? I'm fairly new to this and I don't know how to implement it :)
Thanks
Implementing your own IHostedService would be the best way to go about this. For getting the dbcontext in the service you can use IserviceProvider as your dependency. Serviceprovider will give you the dbcontext.
You can configure your custom hosted service to be added as a singleton then. Check this documentation for details:
https://learn.microsoft.com/en-us/dotnet/architecture/microservices/multi-container-microservice-net-applications/background-tasks-with-ihostedservice#implementing-ihostedservice-with-a-custom-hosted-service-class-deriving-from-the-backgroundservice-base-class
I am writing a piece of middleware (maybe I want a scoped service??), I guess my plan is to have some kind of multi-tenant scenario.
If for example, I have 2 domains that respond on this service:
www.domain1.com
www.domain2.com
I want to capture the request when it starts, look at the host name that is being used and then set some other object to be available through Dependency Injection for everything further up the pipeline.
It seems that middleware should be the right way to achieve this, but not sure how to do the final step.
My options seem to be:
Middleware
Register Singleton service to access database
Register early to be the first item of middleware to capture the request.
Analyse Request Object and build custom configuration object
Add custom configuration as a scoped object to the DI container for use by other services
Service
Register Singleton service to access database
Register Singleton service for IHttpContextAccessor
Register Scoped? Service - to do equivalent of middleware
Analyse the request object and build custom configuration object
Register custom object as new scoped object in the DI container
My assumption is that the Service is able to register the custom scoped object as it is still within the ConfigureServices method of the startup.cs
However, with middleware it is initialised through the Configure method by which point the DI container has already been built?
You can use the factory-overload of AddScoped for the service you want to be different per tenant/request. Here's an example:
services.AddScoped<IServiceForTenant>(sp =>
{
var httpContextAccessor = sp.GetRequiredService<IHttpContextAccessor>();
var serviceForTenant = new ServiceForTenant();
// TODO: Use httpContextAcccessor.HttpContext to configure serviceForTenant.
return serviceForTenant;
});
For each request that comes in to your ASP.NET Core application, the code above will run when you first request IServiceForTenant in e.g. a controller. At this point, your code can read from IHttpContextAccessor.HttpContext and make whatever decisions it needs in order to create the implementation instance for IServiceForTenant. This same instance will then be used for the rest of the request (i.e. further up the pipeline).
The argument passed into AddScoped is Func<IServiceProvider, T>. All you need to provide here is a delegate of some kind, which could be done in one of many ways. Here's some examples:
You could just wrap the call into its own extension method, like this:
public static void AddServiceForTenant(this IServiceCollection services)
{
services.AddScoped<IServiceForTenant>(sp =>
{
// ...
});
}
In ConfigureServices:
services.AddServiceForTenant();
Use a class with a static method:
public static class ServiceForTenantFactory
{
public static ITenantForService Create(IServiceProvider sp)
{
// ...
}
}
In ConfigureServices:
services.AddScoped(ServiceForTenantFactory.Create);
Use a class with an instance method:
public class ServiceForTenantFactory
{
public ITenantForService Create(HttpContext httpContext)
{
// ...
}
}
In ConfigureServices:
services.AddScoped(sp =>
{
var httpContextAccessor = sp.GetRequiredService<IHttpContextAccessor>();
var serviceForTenantFactory = new ServiceForTenantFactory(); // Or use DI.
return serviceForTenantFactory.Create(httpContextAccessor.HttpContext);
});
This last option is the most flexible, as you could even resolve ServiceForTenantFactory itself from DI and it can have its own dependencies, etc. Note also that Create here takes the HttpContext directly (as an example).
As I've already said, there are yet more options than the three of shown, but this should be a good base to work with.
I have an Azure Function App with a function that runs on a blob trigger. I've proven that this function can run through the Azure Portal and responds to this blob trigger without issues... or at least it did.
Now that I've added functionality which makes use of EF Core (2.2.4), it gives me the following error, both when debugging locally and when publishing to Azure:
Microsoft.Azure.WebJobs.Host: Error indexing method 'ParseThings'. Microsoft.Azure.WebJobs.Host: Cannot bind parameter 'context' to type AvastusContext. Make sure the parameter Type is supported by the binding. If you're using binding extensions (e.g. Azure Storage, ServiceBus, Timers, etc.) make sure you've called the registration method for the extension(s) in your startup code (e.g. builder.AddAzureStorage(), builder.AddServiceBus(), builder.AddTimers(), etc.).
I have a Startup class as instructed by Azure Function App documentation here, and have followed their example to the letter, aside from the following line in place of their configured example services:
[assembly: FunctionsStartup(typeof(AvstFunctionApp.Startup))]
namespace AvstFunctionApp
{
public class Startup : FunctionsStartup
{
public override void Configure(IFunctionsHostBuilder builder)
{
builder.Services.AddDbContext<AvastusContext>(options => options.UseSqlServer(Environment.GetEnvironmentVariable("AvastusDb")));
}
}
}
And the beginning of my function:
public static class ParseThings
{
[FunctionName("ParseThings")]
public static void Run([BlobTrigger("summaries/{name}", Connection = "StorageConnectionString")]Stream myBlob, string name, ILogger log, AvastusContext context)
I can confirm that the AddDbContext line is getting hit in a debugger, so presumably there's some bug happening behind the scenes here, or I'm doing something incredibly silly.
Things I've tried that haven't worked include:
Adding .BuildServiceProvider(true) to the AddDbContext line
Using WebJobsStartup instead of the more recently advertised FunctionsStartup
Downgrading to .NET Core 2.2.0
Changing the Function class and Run method from static to instance
Fixing incorrect namespace of the injected AvastusContext
It's also worth noting that there are two other functions in this Function App project which don't seem to have any serious issues, and I've been able to get dependency injection working using a similar method with EF Core for another (ASP.NET Core MVC) project in this solution.
Thank you in advance for any help anyone can provide!
P.S. I find it incredibly weird that there hasn't been anything describing this situation with the later versions of .NET Core, Azure Function Apps, and EF Core on the interwebs, which leads me to believe that this might be a simple mistake. Hopefully not.
perhaps one solution can be you can try injecting IServiceProvider in your function instead of AvastusContext like I have injected in the repository class below:
private readonly IServiceProvider serviceProvider;
public SomeRepository(IServiceProvider serviceProvider)
{
this.serviceProvider = serviceProvider;
}
using var context = this.serviceProvider.GetService<XYZDBContext>();
This will provide a context object to you. Also, Not sure why you are trying to access context in the function directly for good practice have a context class defined, and maintain repository to do any CRUD operation in the code.
Startup.cs you can add extra configurations like :
builder.Services.AddDbContext<XYZDBContext>(
options =>
{
options.UseSqlServer(
conn,
sqlServerOptionsAction:
sqlOptions =>
{
sqlOptions.EnableRetryOnFailure(maxRetryCount: 3, maxRetryDelay: TimeSpan.FromSeconds(30), errorNumbersToAdd: null);
});
}, ServiceLifetime.Transient);
this configuration works perfectly fine in my current solution. Try this out.
Function app can't resolve dbcontext in functions as it can only resolve BindingContext. You need to create custom bindings to use dbcontext directly in function app.
Other way to get dbcontext injected via DI is to pass it to constructor and using a class level variable in the function.
public class ParseThings
{
private AvastusContext _context;
public ParseThings(AvastusContext context){
_context = context;
}
[FunctionName("ParseThings")]
public void Run([BlobTrigger("summaries/{name}", Connection = "StorageConnectionString")]Stream myBlob, string name, ILogger log){
// use _context here
}
}
If it still doesn't resolve you might want to look it into whether the functionsStartup is configured properly
I am having a web api application written in c#, and i have used app insights to log the exceptions, so i have registered a service as follows,
private IExceptionLogService ExceptionLogService { get; set; }
and this is register inside the unity config as well,
<register type="IExceptionLogService" mapTo="ExceptionLogService" />
but when i run the application, the configuration shows in debug as well, it shows the file and the assembly.
private static IUnityContainer BuildUnityContainer()
{
var section = (UnityConfigurationSection)ConfigurationManager.GetSection("unity");
var container = new UnityContainer().LoadConfiguration(section);
return container;
}
but when i try to resolve the dependencies it returns null
private void ResolveDependencies(HttpConfiguration configuration)
{
ExceptionLogService = ExceptionLogService ?? (IExceptionLogService)configuration.DependencyResolver.GetService(typeof(IExceptionLogService));
}
what is the issue here?
You register dependency in Unity DI container that implements IUnityContainer interface but trying to resolve dependency through HttpConfiguration.DependencyResolver of IDependencyResolver type. By default DependencyResolver is set to instance of System.Web.Http.Dependencies.EmptyResolver. It's clear from EmptyResolver class name that it's just a stub that performs no actual resolving.
You should either provide your own implementation of IDependencyResolver that wraps UnityContainer or use some existing implementation. Sample implementation is described in article Dependency Injection in ASP.NET Web API 2.
I suggest using of Unity.WebAPI NuGet package.
After you add this NuGet to your project, class UnityConfig will be added. Put your registrations to its RegisterComponents() method and add following call to Application_Start():
UnityConfig.RegisterComponents();