I have to build a simple netcore 2.2 service (built as library) which has to use basic auth.
The project file is:
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net462</TargetFramework>
<OutputType>Library</OutputType>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
<OutDir>$(SolutionDir)bin\$(Configuration)\Modules\WebDataExport</OutDir>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Authorization" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Hosting.Abstractions" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Abstractions" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Core" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Routing" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="2.2.0" />
</ItemGroup>
<ItemGroup>
<Reference Include="System.ComponentModel.Composition" />
</ItemGroup>
</Project>
The Builder is:
public IWebHost BuildWebHost(string[] args)
{
var path = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
var config = new ConfigurationBuilder()
.SetBasePath(path)
.AddJsonFile("appsettings.json", optional: false)
.Build();
var webHost = WebHost.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((hostingContext) =>
{
})
.ConfigureServices(services =>
{
services.AddMvcCore().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
// configure basic authentication
services.AddAuthentication("BasicAuthentication")
.AddScheme<AuthenticationSchemeOptions, BasicAuthenticationHandler>("BasicAuthentication", null);
// Add service for data access
services.AddSingleton(_dataProvider);
})
.Configure(app =>
{
app.UseAuthentication();
app.UseMvc();
})
.UseUrls($"http://*:{config.GetValue<int>("ListenPort")}")
.UseKestrel();
return webHost.Build();
}
And my controller:
[Authorize]
[ApiController]
[Route("[controller]")]
[Produces("application/json")]
public class GetRecordingsController : ControllerBase
{
public GetRecordingsController(ILogger<GetRecordingsController> logger, IDataProvider dataProvider)
{
}
[HttpGet]
public async Task<IActionResult> Get([FromQuery] string From,
[FromQuery] string To)
{
}
}
And, in the end, this is the BasicAuthenticationHandler :
public class BasicAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions>
{
private readonly IDataProvider _dataProvider;
public BasicAuthenticationHandler(IOptionsMonitor<AuthenticationSchemeOptions> options,
ILoggerFactory logger,
UrlEncoder encoder,
ISystemClock clock,
IDataProvider dataProvider)
: base(options, logger, encoder, clock)
{
_dataProvider = dataProvider;
}
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
if (!Request.Headers.ContainsKey("Authorization"))
return AuthenticateResult.Fail("Missing Authorization Header");
User user;
try
{
var authHeader = AuthenticationHeaderValue.Parse(Request.Headers["Authorization"]);
var credentialBytes = Convert.FromBase64String(authHeader.Parameter);
var credentials = Encoding.UTF8.GetString(credentialBytes).Split(new[] { ':' }, 2);
var username = credentials[0];
var password = credentials[1];
user = await _dataProvider.AuthenticateUser(username, password);
}
catch
{
return AuthenticateResult.Fail("Invalid Authorization Header");
}
if (user == null)
return AuthenticateResult.Fail("Invalid Username or Password");
var claims = new[] {
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
new Claim(ClaimTypes.Name, user.Username),
};
var identity = new ClaimsIdentity(claims, Scheme.Name);
var principal = new ClaimsPrincipal(identity);
var ticket = new AuthenticationTicket(principal, Scheme.Name);
return AuthenticateResult.Success(ticket);
}
}
Now, what's happen is that, if i send a GET with a wrong password, the HandleAuthenticateAsync correctly returns fail but my get method is called anyway: the Microsoft.AspNetCore.Authorization.DefaultAuthorizationService is not called.
This is the log:
info: Sinora.Modules.WebDataExport.BasicAuthenticationHandler[7]
BasicAuthentication was not authenticated. Failure message: Invalid Username or Password
info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0]
Executing endpoint 'Sinora.Modules.WebDataExport.Controllers.GetRecordingsController.Get (Sinora.Modules.WebDataExport)'
info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[1]
Route matched with {action = "Get", controller = "GetRecordings"}. Executing action Sinora.Modules.WebDataExport.Controllers.GetRecordingsController.Get (Sinora.Modules.WebDataExport)
.....
[METHOD EXECUTION]
.....
info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1]
Executed endpoint 'Sinora.Modules.WebDataExport.Controllers.GetRecordingsController.Get (Sinora.Modules.WebDataExport)'
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[2]
Request finished in 11384.1282ms 200
While I'm expecting something like:
info: Sinora.Modules.WebDataExport.BasicAuthenticationHandler[7]
BasicAuthentication was not authenticated. Failure message: Invalid Username or Password
info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0]
Executing endpoint 'Sinora.Modules.WebDataExport.Controllers.GetRecordingsController.Get (Sinora.Modules.WebDataExport)'
info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[1]
Route matched with {action = "Get", controller = "GetRecordings"}. Executing action Sinora.Modules.WebDataExport.Controllers.GetRecordingsController.Get (Sinora.Modules.WebDataExport)
info: Microsoft.AspNetCore.Authorization.DefaultAuthorizationService[2]
Authorization failed.
info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[3]
Authorization failed for the request at filter 'Microsoft.AspNetCore.Mvc.Authorization.AuthorizeFilter'.
info: Microsoft.AspNetCore.Mvc.ChallengeResult[1]
Executing ChallengeResult with authentication schemes ().
info: WebApi.Helpers.BasicAuthenticationHandler[12]
AuthenticationScheme: BasicAuthentication was challenged.
info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[2]
Executed action Sinora.Modules.WebDataExport.Controllers.GetRecordingsController.Get (Sinora.Modules.WebDataExport) in 11062.176ms
info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1]
Executed endpoint 'Sinora.Modules.WebDataExport.Controllers.GetRecordingsController.Get (Sinora.Modules.WebDataExport)'
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[2]
Request finished in 11384.1282ms 200
I cannot figure out where I'm wrong
I think you are missing the following call in your startup file
app.UseAuthorization();
I hope this solves your issue.
Related
I'm trying to create my first webjobapp that requires the use of Entity Framework. I am trying to inject my context into the builder of the app, but I'm getting an error:
Cannot bind parameter 'db' to type ApplicationDbContext. Make sure the parameter Type is supported by the binding
There are several examples on the web but the versions of the Azure WebJob appear to have changed quickly and I can't find one that works for the version I'm using. These are the package versions I'm using:
Webjobs 3.0
EF.NET Core 7.0
Targeting .NET 7.0
Here is my code:
class Program
{
static async Task Main()
{
var builder = new HostBuilder();
builder.ConfigureWebJobs(b =>
{
b.AddTimers();
b.AddAzureStorageCoreServices();
});
builder.ConfigureLogging((context, b) =>
{
b.AddConsole();
});
builder.ConfigureAppConfiguration((hostContext, configApp) =>
{
configApp.SetBasePath(Directory.GetCurrentDirectory());
configApp.AddEnvironmentVariables(prefix: "ASPNETCORE_");
configApp.AddJsonFile($"appsettings.json", true);
configApp.AddJsonFile($"appsettings.{hostContext.HostingEnvironment.EnvironmentName}.json", true);
});
builder.ConfigureServices(services => { services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer("My full connection string"));
services.AddTransient<IEmailSender, EmailSender>();
});
var host = builder.Build();
using (host)
//{
await host.RunAsync();
}
}
}
public class Functions
public static void Sendemails([TimerTrigger("0 0 20 * * SUN-THU")] TimerInfo timer,
ILogger logger, ApplicationDbContext db)
{
var mylist = db.Users.ToList();
}
}
I tried to configure the services using the following code instead of the above code. When inspecting the builder I can see that the services were added to the builder. But inspecting the host after the call to builder.Build(), The services do not show up in the host container.
var serviceCollection = new ServiceCollection();
ConfigureServices(serviceCollection);
.....
builder.ConfigureServices((context, sc) => sc= serviceCollection);
var host = builder.Build();
using (host)
{
await host.RunAsync();
}
}
.....
private void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(_configuration.GetConnectionString("DefaultConnection")))
}
Project File
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<None Remove="appsettings.Development.json" />
<None Remove="appsettings.json" />
</ItemGroup>
<ItemGroup>
<Content Include="appsettings.Development.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
<Content Include="appsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<PackageReference
Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="7.0.1" />
<PackageReference Include="Microsoft.Azure.WebJobs" Version="3.0.33" />
<PackageReference Include="Microsoft.Azure.WebJobs.Extensions" Version="4.0.1" />
<PackageReference Include="Microsoft.Azure.WebJobs.Extensions.OpenApi.Configuration.AppSettings" Version="1.5.0" />
<PackageReference Include="Microsoft.Azure.WebJobs.Extensions.SendGrid" Version="3.0.2" />
<PackageReference Include="Microsoft.Azure.WebJobs.Extensions.Storage" Version="5.0.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="7.0.1" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="7.0.0" />
<PackageReference Include="SendGrid" Version="9.28.1" />
</ItemGroup>
</Project>
Initially Even I got the same error.
Done few changes in Program.cs .
My Program.cs:
using Microsoft.AspNetCore.Identity.UI.Services;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
class Program
{
static async Task Main()
{
var builder = new HostBuilder()
.ConfigureWebJobs(b =>
{
b.AddTimers();
b.AddAzureStorageCoreServices();
})
.ConfigureLogging((context, b) =>
{
b.AddConsole();
})
.ConfigureAppConfiguration((hostContext, configApp) =>
{
configApp.SetBasePath(Directory.GetCurrentDirectory());
configApp.AddEnvironmentVariables(prefix: "ASPNETCORE_");
configApp.AddJsonFile($"appsettings.json", true);
configApp.AddJsonFile($"appsettings.{hostContext.HostingEnvironment.EnvironmentName}.json", true);
})
.ConfigureServices(services =>
{
services.AddDbContext<ApplicationDbContext>(
options => options.UseSqlServer("MyDefaultConnection"));
services.AddTransient<ApplicationDbContext, ApplicationDbContext>();
});
var host = builder.Build();
using (host)
{
await host.RunAsync();
}
}
}
If you are running locally, cross check the local.settings.json file once.
My local.settings.json :
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
"FUNCTIONS_WORKER_RUNTIME": "dotnet"
}
}
My appsettings.json:
{
"ConnectionStrings": {
"AzureWebJobsStorage": "Storage Account Connection String"
}
}
Also have a look at SO Thread once.
References taken from MSDoc.
I do the steps from the beginning.
1-
public class ApplicationDbContext: IdentityDbContext<User, Role, Guid>
{
public ApplicationDbContext(DbContextOptions options)
: base(options)
{
}
}
2-add in appsettings.json
"ConnectionStrings": {
"SqlServer": "Data Source=;Initial Catalog=SpaceManagment;User ID=sa;Password=654321;MultipleActiveResultSets=true;TrustServerCertificate=True;Connection Timeout = 60"
},
3-
add in program.cs
builder.Services.AddDbContext<ApplicationDbContext>(options =>
{
options
.UseSqlServer(settings.SqlServer);
//Tips
// .ConfigureWarnings(warning => warning.Throw(RelationalEventId.QueryClientEvaluationWarning));
});
I am trying to create windows service using dotnet worker project that starts up rest-api, I am using kestrel server for this purpose:
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Builder;
namespace TurkisisService
{
public class Worker : BackgroundService
{
private readonly ILogger<Worker> _logger;
public Worker(ILogger<Worker> logger)
{
_logger = logger;
kestrelStarted=false;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
_logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
if(!kestrelStarted) {
kestrelStarted=true;
var host = new WebHostBuilder()
.UseKestrel()
.UseStartup<Startup>()
.Build();
var task = Task.Run(() => {
host.Run();
});
}
await Task.Delay(1000, stoppingToken);
}
}
private bool kestrelStarted;
}
public class Startup {
public void Configure(IApplicationBuilder app){
app.Run(context => {
return context.Response.WriteAsync("Hello world!");
});
}
}
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureServices((hostContext, services) =>
{
services.AddHostedService<Worker>();
});
}
}
I used these packages:
<Project Sdk="Microsoft.NET.Sdk.Worker">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<UserSecretsId>dotnet-turkisis_service-9216353E-18A1-49B4-8CAB-E939439FF992</UserSecretsId>
<RootNamespace>turkisis_service</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="5.0.0" />
</ItemGroup>
The console displays these lines of logs:
Now listening on: http://localhost:5000
Now listening on: https://localhost:5001
but when I open the url on web browser: http://localhost:5000, also https://localhost:5001 returns empty, they are display: ERR_EMPTY_RESPONSE
I want to set all localhost port can allow CORS and then i tried to use * keyword to do it , like below code :
readonly string MyAllowSpecificOrigins = "_myAllowSpecificOrigins";
public void ConfigureServices(IServiceCollection services)
{
//..
services.AddCors(options =>
{
options.AddPolicy(name: MyAllowSpecificOrigins,
builder =>
{
builder.WithOrigins($"http://localhost:*");
}
);
});
//..
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
//..
app.UseCors(MyAllowSpecificOrigins);
//..
}
but it not work :
csproj :
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp3.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Dapper" Version="2.0.35" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="System.Data.SqlClient" Version="4.8.1" />
</ItemGroup>
</Project>
I want to set all localhost port can allow CORS
To achieve above requirement, you can try:
services.AddCors(options =>
{
options.AddPolicy(name: MyAllowSpecificOrigins,
builder =>
{
builder.SetIsOriginAllowed(origin => new Uri(origin).Host == "localhost");
}
);
});
I am getting the below error :
InvalidOperationException: No database provider has been configured for this DbContext. A provider can be configured by overriding the DbContext.OnConfiguring method or by using AddDbContext on the application service provider. If AddDbContext is used, then also ensure that your DbContext type accepts a DbContextOptions object in its constructor and passes it to the base constructor for DbContext.
Startup.cs :
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddDistributedMemoryCache();
services.AddSession(options=>{options.IdleTimeout = TimeSpan.FromMinutes(1);});services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie(options =>
{
options.Cookie.HttpOnly = true;
options.ExpireTimeSpan = TimeSpan.FromMinutes(20);
options.Cookie.Name = "SampleCore";
options.LoginPath = "/log-in";
//options.AccessDeniedPath = "/access-denied";
options.LogoutPath = "/log-off";
options.ReturnUrlParameter = "postBack";
});
services.AddMvc(options =>
{
options.Filters.Add(new AutoValidateAntiforgeryTokenAttribute());
}).SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
string connection = Configuration.GetConnectionString("MyConnection");
services.AddDbContext<EmployeeContext>(options => options.UseSqlServer(connection), ServiceLifetime.Scoped);
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseStaticFiles();
app.UseSession();
app.UseCookiePolicy();
app.UseAuthentication();
app.UseCustomErrorPages();
app.UseMvc();
}
UseCustomErrorPages Extension :
public async Task Invoke(HttpContext context)
{
await _next(context); // Getting error at this line
if (context.Response.StatusCode == 404 && !context.Response.HasStarted)
{
string originalPath = context.Request.Path.Value;
context.Items["originalPath"] = originalPath;
context.Request.Path = "/page-not-found";
await _next(context);
}
else if (context.Response.StatusCode == 500)
{
string originalPath = context.Request.Path.Value;
context.Items["originalPath"] = originalPath;
context.Request.Path = "/error";
await _next(context);
}
else if(context.Response.StatusCode == 401)
{
string originalPath = context.Request.Path.Value;
context.Items["originalPath"] = originalPath;
context.Request.Path = "/access-denied";
await _next(context);
}
}
DbContext :
public partial class EmployeeContext : DbContext
{
public EmployeeContext(DbContextOptions<EmployeeContext> options)
: base(options)
{
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
{
var connectionString = "connectionString";
optionsBuilder.UseSqlServer(connectionString);
base.OnConfiguring(optionsBuilder);
}
}
public virtual DbSet<TblEmployee> TblEmployee { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
//Code
}
}
csproj file :
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="Microsoft.AspNetCore.Razor.Design" Version="2.1.2" PrivateAssets="All" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="2.1.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="2.1.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer.Design" Version="1.1.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="2.1.4">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="2.1.6" />
Controller :
private EmployeeContext _context;
public HomeController(EmployeeContext context)
{
_context = context;
}
public List<TblEmployee> GetEmployees
{
List<TblEmployee> getEmployees = _context.TblEmployee.ToList();
return getEmployees ;
}
appsettings.json :
{
"Logging": {
"LogLevel": {
"Default": "Warning"
}
},
"ConnectionStrings": {
"MyConnection": "connection string"
},
"AllowedHosts": "*"
}
I don't understand where I did wrong. Kindly help
I followed the hello world example form IOptionsSnapshot, but the content is not refreshed after file config.json is changed and saved.
My dev environment:
1.VS 2017
2 .csproj file below
<PropertyGroup>
<TargetFramework>netcoreapp1.1</TargetFramework>
<PreserveCompilationContext>true</PreserveCompilationContext>
<AssemblyName>UsingOptions</AssemblyName>
<OutputType>Exe</OutputType>
<PackageId>UsingOptions</PackageId>
<RuntimeFrameworkVersion>1.0.4</RuntimeFrameworkVersion>
<PackageTargetFallback>$(PackageTargetFallback);dotnet5.6;portable-net45+win8</PackageTargetFallback>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore" Version="1.1.1" />
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="1.1.1" />
<PackageReference Include="Microsoft.AspNetCore.Server.IISIntegration" Version="1.1.1" />
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="1.1.1" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="1.1.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="1.1.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="1.1.1" />
<PackageReference Include="Microsoft.Extensions.Options" Version="1.1.1" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="1.1.1" />
<PackageReference Include="Microsoft.AspNetCore.Diagnostics" Version="1.1.1" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="1.1.1" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="1.1.1" />
</ItemGroup>
Below is the code from IOptionsSnapshot
config.json:
{
"Time": {
"Message": "Hello "
}
}
public class TimeOptions
{
// Records the time when the options are created.
public DateTime CreationTime { get; set; } = DateTime.Now;
// Bound to config. Changes to the value of "Message"
// in config.json will be reflected in this property.
public string Message { get; set; }
}
public class Controller
{
public readonly TimeOptions _options;
public Controller(IOptionsSnapshot<TimeOptions> options)
{
_options = options.Value;
}
public Task DisplayTimeAsync(HttpContext context)
{
return context.Response.WriteAsync(_options.Message + _options.CreationTime);
}
}
public class Startup
{
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
// reloadOnChange: true is required for config changes to be detected.
.AddJsonFile("config.json", optional: false, reloadOnChange: true)
.AddEnvironmentVariables();
Configuration = builder.Build();
}
public IConfigurationRoot Configuration { get; set; }
public void Configure(IApplicationBuilder app)
{
// Simple mockup of a simple per request controller that writes
// the creation time and message of TimeOptions.
app.Run(DisplayTimeAsync);
}
public void ConfigureServices(IServiceCollection services)
{
// Simple mockup of a simple per request controller.
services.AddScoped<Controller>();
// Binds config.json to the options and setups the change tracking.
services.Configure<TimeOptions>(Configuration.GetSection("Time"));
}
public Task DisplayTimeAsync(HttpContext context)
{
context.Response.ContentType = "text/plain";
return context.RequestServices.GetRequiredService<Controller>().DisplayTimeAsync(context);
}
public static void Main(string[] args)
{
var host = new WebHostBuilder()
.UseKestrel()
.UseIISIntegration()
.UseStartup<Startup>()
.Build();
host.Run();
}
}
The source code of IOptions and IOptionsSnapshot look the same. Those interfaces have common implementation in OptionsManager. So OptionsSnapshot does not reload options. Instead of IOptionsSnapshot, use IOptionsMonitor. You do not need to subscribe to OnChange event, just access to CurrentValue property.
UPD
Since Microsoft.Extensions.Options 2.x release IOptionsSnapshot has been removed.