I have a blazor component in my application:
public class IndexComponent : ComponentBase
{
public string ContentRoot { get; set; }
public string WebRoot { get; set; }
private IHostingEnvironment HostingEnvironment;
public IndexComponent(IHostingEnvironment hostingEnvironment)
{
HostingEnvironment = hostingEnvironment;
}
protected override async Task OnInitAsync()
{
//Some Code Here
}
}
I am trying to use DI in my app , for example IHostingEnvironment.
Code give no compile time error here but when i run it than in code behind file of this razor (Index.razor.g.cs file):
public class Index : IndexComponent
at this line it says:
There is no argument given that corresponds to the required formal
parameter hostingEnvironment of IndexComponent.IndexComponent
This can be solved by using #inject IHostingEnvironment in Razor file but I am moving my function block from Razor to IndexComponent.cs file so need it there.
Neither of it works in below way:
[Inject]
IHostingEnvironment HostingEnvironment
What will work here?
Note: No use of ViewModel
Update 1
In StartUp.cs by adding namespace
using Microsoft.AspNetCore.Hosting.Internal;
And than
services.AddSingleton<IHostingEnvironment>(new HostingEnvironment());
It is now able to register IHostingEnvironment on client side project but it does not have values for its properties (contentrootpath and webrootpath).
Only one thing is available here which is EnvironmentName and its value is always Production ,
Update:
The error is from WebAssembly, so it is a client-side app. There is no HostingEnvironment on the client and therefore the service is not registered. It would be useless if it was.
So, step back: Why do (you think) you need it?
You should make it a protected or public read/write property:
// in IndexComponent
[Inject]
protected IHostingEnvironment HostingEnvironment { get; set; }
and remove the constructor parameters.
Side note: IHostingEnvironment is marked as obsolete.
It turns out that for Blazor you need a slightly different interface, namely IWebAssemblyHostEnvironment.
From this documentation, what you should inject is:
#inject IWebAssemblyHostEnvironment HostEnvironment
From this comment:
WASM: System.InvalidOperationException: Cannot provide a value for property 'HostingEnvironment' on type 'JewelShut.Client.Pages.Index'. There is no registered service of type 'Microsoft.AspNetCore.Hosting.IHostingEnvironment'
I'm guessing this is a client side Blazor application. (My apologies if I'm wrong with my guess.). On client side Blazor the IHostingEnvironment is not registered by default in DI container. Still the error says that the service you are trying to inject is not registered. To register a service:
In the Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
//few sample for you
services.AddScoped<AuthenticationStateProvider, ApiAuthenticationStateProvider>();
services.AddAuthorizationCore();
//register the required services
//services.Add...
}
If the injected service is registered the way #Henk Holterman has suggested is the right answer.
Di in blazor
Related
I'd like to create razor app, which can be plugged into another .net web application.
I successfully made it, but razor project doesn't load static files like *.css and *.js, so page looks ugly with pure html. The wwwroot and another folders are unreachable in browser.
How can i isolate css and js files within razor app?
Also github link to default projects with minimal changes.
link non actual, please look at answer
https://github.com/ShockThunder/RoseQuartz
//Code from pluggable razor app
//Extension methods to add razor app
public static IServiceCollection AddRoseQuartz(this IServiceCollection services)
{
services.AddRazorPages();
return services;
}
//Another to include routing and static files
public static IApplicationBuilder UseRoseQuartz(this IApplicationBuilder builder)
{
builder.UseStaticFiles();
builder.UseRouting();
builder.UseEndpoints(endpoints => { endpoints.MapRazorPages(); });
return builder;
}
//Code from main project
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddRoseQuartz();
services.AddControllers();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseRoseQuartz();
//another Startup code
}
So, after some days of investigation, I found this question.
Can Razor Class Library pack static files (js, css etc) too?
Ehsan Mirsaeedi provided a working way to embed static files into RCL and it works for my project. Here is a link to answer https://stackoverflow.com/a/53024912.
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 have a scenario where we have a "standardised startup" for many small AspNet Core websites.
A seemingly obvious solution to achieve this is to refactor the Startup.cs class into a separate common assembly (as Infrastructure.Web.Core.Startup). We then have each small AspNet Core website reference it the common assembly and use that startup class instead:
public static Microsoft.AspNetCore.Hosting.IWebHostBuilder CreateWebHostBuilder( string[] args )
{
return new WebHostBuilder()
.UseKestrel()
.ConfigureServices( collection => { } )
.UseContentRoot( System.IO.Directory.GetCurrentDirectory() )
.UseStartup<Infrastructure.Web.Core.Startup>(); //.UseStartup<Startup>();
}
Somehow, this breaks attribute routing in the sense that the routes are not hit. No errors, but not routing. The moment I copy the class back into the website project (with the exact same code) it works again.
As a test, if I wrap the Startup.cs class in the common library in a local startup class (like below), it also works:
public class Startup
{
private readonly Infrastructure.Web.Core.Startup _startup;
public Startup( IConfiguration configuration )
{
_startup = new Infrastructure.Web.Core.Startup( configuration );
}
public IConfiguration Configuration => _startup.Configuration;
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices( IServiceCollection services )
{
_startup.ConfigureServices( services );
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure( IApplicationBuilder app, IHostingEnvironment env )
{
_startup.Configure( app, env );
}
}
If I had to take a guess, it's probably something to do with the Dependency Injection.
Does anyone have any suggestions?
FYI: It's using typical AspNet Core 2.1 projects
UPDATE
Incidentally, if I use inheritance it also works but the derived class must be in the same project as the website. I guess it seems obvious, but thought I include that information for completeness sake:
public class Startup : Infrastructure.Web.Core.Startup
{
public Startup( IConfiguration configuration ) : base(configuration)
{
}
}
You can fix this by adding the following statement to your services in your Startup.cs method.
services.AddApplicationPart(typeof(AnTypeInTheOtherAssembly).Assembly);
This will tell the View/Controller Discovery to also check for the new location. Your Project which contains the Startup.cs file would be the Startup Project, and all the others would be just references and libraries or similar.
As of .Net Core 3 you can use something called Razor Class Libraries, see the MSDN. This will automatically add this your Controllers and Views to the discovery, it also has debugging support and will work just as a normal Class Library would.
In my .NET Core project, I have below settings in Configure method:
public void ConfigureServices(IServiceCollection services)
{
services
.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
//services.AddOptions<UploadConfig>(Configuration.GetSection("UploadConfig"));
}
I have not registerd any IOptions and I am injecting it in a controller
[Route("api/[controller]")]
[ApiController]
public class HelloWorldController : ControllerBase
{
public HelloWorldController(IOptions<UploadConfig> config)
{
var config1 = config.Value.Config1;
}
}
The IOptions is getting resolved with the default instance, and I get to know error only when I am trying to use it (and when I expect the value to be not null).
Can I somehow get it to fail, stating the instance type is not registered or something similar? I just want to catch errors as early as possible.
The options framework is set up by the default host builder as part of its setup, so you do not need to AddOptions() yourself. This however also ensures that you can use IOptions<T> wherever you want since the framework will provide that exact options object for you.
The way options work is that the framework will always give you a T (as long as it can construct one). When you do set up configuration using e.g. AddOptions<T> or Configure<T>, what actually happens is that a configuration action gets registered for that type T. And when an IOptions<T> is later resolved, all those registered actions will run in the sequence they are registered.
This means that it’s valid to not have configured an options type. In that case, the default values from the object will be used. Of course, this also means that you are not able to detect whether you have actually configured the options type and whether the configuration is actually valid. This usually has to be done when you use the values.
For example, if you require Config1 to be configured, you should explicitly look for it:
public HelloWorldController(IOptions<UploadConfig> config)
{
if (string.IsNullOrEmpty(config.Value.Config1))
throw ArgumentException("Config1 is not configured properly");
}
An alternative would be to register a validation action for a type using OptionsBuilder.Validate. This will then be called automatically when you resovle the options object to validate the containing value. That way, you can have the validation set up in a central location:
services.AddOptions<UploadConfig>()
.Bind(Configuration.GetSection("UploadConfig"))
.Validate(c => !string.IsNullOrEmpty(c.Config1));
Unfortunately, this also means that you can only detect these problems when you actually use the values, which can be missed if you are not testing your application thoroughly. A way around this would be to resolve the options once when the application starts and validate them there.
For example, you could just inject your IOptions<T> within your startup’s Configure method:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, IOptions<UploadConfig> uploadOptions)
{
// since the options are injected here, they will be constructed and automatically
// validated if you have configured a validate action
// …
app.UseMvc();
}
Alternatively, if you have multiple options you want to validate and if you want to run logic that does not fit into the validation action, you could also create a service that validates them:
public class OptionsValidator
{
private readonly IOptions<UploadConfig> uploadOptions;
public OptionsValidator(IOptions<UploadConfig> uploadOptions)
{
_uploadOptions = uploadOptions;
}
public void Validate()
{
if (string.IsNullOrEmpty(_uploadOptions.Value.Config1))
throw Exception("Upload options are not configured properly");
}
}
And then inject that in your Configure:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, OptionsValidator optionsValidator)
{
// validate options explicitly
optionsValidator.Validate();
// …
app.UseMvc();
}
Whatever you do, keep also in mind that by default the configuration sources are configured to support updating the configuration at run-time. So you will always have a situation in which a configuration can be invalid temporarily at run-time.
Great answer from poke, I just wanna complete it with this that you can fail-fast in your startup file when no configuration is provided:
public class MyOptions
{
public string MyValue { get; set; }
}
public void ConfigureServices(IServiceCollection services)
{
var options = Configuration.GetSection("MyOptions").Get<MyOptions>();
if (string.IsNullOrWhiteSpace(options?.MyValue))
{
throw new ApplicationException("MyValue is not configured!");
}
}
IOptions configuration values are read lazily. Although the
configuration file might be read upon application startup, the
required configuration object is only created when IOptions.Value is
called for the first time.
When deserialization fails, because of application misconfiguration,
such error will only appear after the call to IOptions.Value. This can
cause misconfigurations to keep undetected for much longer than
required. By reading -and verifying- configuration values at
application startup, this problem can be prevented.
This articles also helps you to get the idea:
Is IOptions Bad?
ASP.NET Core 2.2 – Options Validation
In my aspnetcore app (v2.1) I need to configure a read-only database (entityframework core + SQLite) which is in ~/wwwroot/App_Data/quranx.db
I need to call this code in Startup.ConfigureServices
services.AddDbContext<QuranXDataContext>(options => options
.UseSqlite($"Data Source={databasePath}")
.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking)
);
But at that point I cannot find a way to get the path to wwwroot. To get that path I need IHostingEnvironment, but I am unable to get a reference to that until Startup.Configure is called, and that is after Startup.ConfigureServices has finished.
How is this done?
It's easy enough to access IHostingEnvironment in ConfigureServices (I've explained how below) but before you read the specifics, take a look at Chris Pratt's warning in the comments about how storing a database in wwwroot is a very bad idea.
You can take a constructor parameter of type IHostingEnviroment in your Startup class and capture that as a field, which you can then use in ConfigureServices:
public class Startup
{
private readonly IHostingEnvironment _env;
public Startup(IHostingEnvironment env)
{
_env = env;
}
public void ConfigureServices(IServiceCollection services)
{
// Use _env.WebRootPath here.
}
// ...
}
For ASP.NET Core 3.0+, use IWebHostEnvironment instead of IHostingEnvironment.
Path.GetFullPath("wwwroot");