Azure Feature Flag is not updating after cache expiration - azure

We have FeatureFlag: IsServiceNeeded with no label set in Feature Manager of Azure App Configuration. We have used in built method AddAzureAppConfiguration by setting up the cache interval.
We are using .net core 3.1 web api and Feature Manager of Azure App Configuration.
We had IsServiceNeeded enabled during initialization of the app and after few hours, we disabled IsServiceNeed. We wait for entire day, but don't see the difference since the below returns true instead of false. We were expecting it to update every 3 minutes due to how we have it configured in program.cs file.
await _featureManager.IsEnabledAsync("IsServiceNeeded")
Let me know if you see anything odd with below. Thanks in advance,
Here is the code snippet we are using it.
Program.cs file
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((context, config) =>
{
var configurationRoot = config.Build();
var appConfigConString = configurationRoot["AppConfigConnectionString"];
config.AddAzureAppConfiguration(options => options.Connect(appConfigConString).UseFeatureFlags(featureFlagOptions => {
**featureFlagOptions.CacheExpirationInterval = TimeSpan.FromMinutes(3);**
}));
})
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
Startup.cs file
public class Startup
{
public IConfiguration Configuration { get; }
public string ContentRootPath { get; set; }
public Startup(IWebHostEnvironment env, IConfiguration configuration)
{
Configuration = configuration;
}
public void ConfigureServices(IServiceCollection services)
{
services.AddHsts(options =>
{
options.Preload = true;
options.IncludeSubDomains = true;
options.MaxAge = TimeSpan.FromDays(365);
});
var conf = Configuration.GetSection("AppSettings").Get<Config>();
services.Configure<Config>(Configuration.GetSection("AppSettings"));
services.AddSingleton<IAppSettings>(c => conf);**
services.AddScoped<IProcessHandler, ProcessHandler>();
**services.AddFeatureManagement();**
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseHsts();
app.UseHttpsRedirection();
app.UseHttpStatusCodeExceptionMiddleware();
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
private static void LoadMediatorHandlers(IServiceCollection services)
{
foreach (var assembly in Assembly
.GetEntryAssembly()
.GetReferencedAssemblies()
.Select(Assembly.Load)
.Where(name => (name.FullName.Contains("Queries") || name.FullName.Contains("Commands"))))
{
services.AddMediatR(assembly);
}
services.AddMediatR(typeof(Startup));
services.AddScoped<IMediator, Mediator>();
}
}
Application of Feature Flag:
public class ProcessHandler : IProcessHandler
{
private readonly IFeatureManager _featureManager;
public ProcessHandler(IFeatureManager featureManager)
{
_featureManager = featureManager;
}
public async Task<ClassA> ProcessXyz()
{
if (`await _featureManager.IsEnabledAsync("IsServiceNeeded")`)
{
return new ClassA();
}
return null;
}
}
Please Note: I have just added the required code and replaced actual names for security issues.
Thanks in advance

What is missing is the middleware that refreshes feature flags (and configuration) from Azure App Configuration.
Open your startup.cs, add below
public void ConfigureServices(IServiceCollection services)
{
// ...
services.AddAzureAppConfiguration();
services.AddFeatureManagement();
}
And then add below
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// ...
app.UseAzureAppConfiguration();
}

Related

Hosting ASP.NET Core 5 Web API on IIS: an error occurred while starting the application

I have an ASP.NET Core 5 Web API project that I'm trying to host on IIS. Everything worked fine:
I installed the .NET Core hosting bundle
I added IIS_IUSRS to allow the access to the published application folder
When I open the published application in cmd and run dotnet myAppName.dll, everything is working fine and I can test all my apis and all of them are working well.
But when I browse my project from IIS, I get this error:
An error occurred while starting the application
This is my startup.cs:
namespace storedProcedure
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddCors();
services.AddControllers().AddNewtonsoftJson(x => x.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore);
//adding dbContext service
services.AddDbContext<trainingContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString
("trainingDB"))) ;
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "storedProcedure", Version = "v1" });
});
services.AddDirectoryBrowser();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseSwagger();
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "storedProcedure v1"));
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseStaticFiles(new StaticFileOptions()
{
FileProvider = new PhysicalFileProvider(#"D:\Users\lenovo\Desktop\paul\web2-Net-CORE\storedProcedure\storedProcedure\Resources"),
RequestPath = "/Resources"
});
app.UseRouting();
app.UseCors(
options => options.WithOrigins("http://localhost:4200").AllowAnyMethod().AllowCredentials()
);
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
}
And this is my Program.cs:
namespace storedProcedure
{
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
}
NOTE: I'm integrating SQL Server database with my project and I added the database publish settings:
Click here to see an image of my db publish settings
I solved it, The Resources folder (where my static files are), was not included in the published folder, that's why it was giving me an error

Operation ID when using Application Insight

I am providing my endpoint with a correlation ID:
I then read that ID from the HttpContext.Request.Headers and use that as my telemetry.Context.Operation.Id.
This works, but when I look in my log I have an extra entry which is auto generated by the framework. This entry has its own ID. How can I ensure the framework useses the same ID?
This is how I configure the service
using System;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using AutoMapper;
using Microsoft.ApplicationInsights;
using Microsoft.ApplicationInsights.Extensibility;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.ApiExplorer;
using Microsoft.AspNetCore.Mvc.Versioning;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Logging;
using Users.Api.Services;
using Users.Api.Utility;
using Users.Services.Implementations;
using Users.Services.Interfaces;
using Users.Sql;
using Users.Utility;
using Packages.Api.Filters;
using Packages.Audit;
using Swashbuckle.AspNetCore.Swagger;
namespace Users.Api
{
public class Startup
{
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables();
Configuration = builder.Build();
}
public IConfigurationRoot Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// Dependency injection
services.AddAutoMapper();
services.AddDbContext<UsersContext>(
builder => builder.UseSqlServer(Environment.GetEnvironmentVariable("Connectionstring")));
services.AddScoped<IUserService, UserService>();
services.AddScoped<IIdentityService, IdentityService>();
services.AddScoped<IServiceBusCommunicator, ServiceBusCommunicator>();
services.AddScoped<IGraphClient, GraphClient>();
services.AddScoped<IClaimsHarvester, ClaimsHarvester>();
services.AddScoped<IUserRepository, UserRepository>();
services.AddSingleton<HttpClient>();
services.AddSingleton<EndpointConfiguration>();
services.AddSingleton<GraphConfiguration>();
services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddScoped<IAuditLogClient, AuditLogClient>();
var clientId = Environment.GetEnvironmentVariable("Authentication:AzureAd:ClientId");
var tenant = Environment.GetEnvironmentVariable("Authentication:AzureAd:Tenant");
var signInPolicyId = Environment.GetEnvironmentVariable("Authentication:AzureAd:SignInPolicyId");
var authority = $"https://login.microsoftonline.com/tfp/{tenant}/{signInPolicyId}/v2.0/";
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultSignInScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(bearerOptions =>
{
bearerOptions.Authority = authority;
bearerOptions.Audience = clientId;
bearerOptions.Events = new JwtBearerEvents
{
OnAuthenticationFailed = AuthenticationFailed
};
});
services.AddMvc();
services.AddSwaggerGen(
options =>
{
options.SwaggerDoc("v1", new Info { Title = "Users API", Version = "v1" });
});
services.ConfigureSwaggerGen(options =>
{
options.OperationFilter<AuthorizationHeaderParameterOperationFilter>();
options.OperationFilter<CorrelationHeaderParameterOperationFilter>();
options.OperationFilter<XTotalCountHeaderParameterOperationFilter>();
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app,
IHostingEnvironment env,
ILoggerFactory loggerFactory,
ContextInitializer contextInitializer)
{
if (env.IsDevelopment())
{
// loggerFactory.AddConsole(Configuration.GetSection("Logging"));
// loggerFactory.AddDebug();
app.UseDeveloperExceptionPage();
}
app.UseAuthentication();
app.UseMvc();
app.UseSwagger();
app.UseSwaggerUI(
c =>
{
c.SwaggerEndpoint($"{Environment.GetEnvironmentVariable("ServiceFabric:UniqueUrlPath")}/swagger/v1/swagger.json", "Contacts API V1");
});
// Seed default values
contextInitializer.Seed();
}
private Task AuthenticationFailed(AuthenticationFailedContext arg)
{
// For debugging purposes only!
var s = $"AuthenticationFailed: {arg.Exception.Message}";
arg.Response.ContentLength = s.Length;
arg.Response.Body.Write(Encoding.UTF8.GetBytes(s), 0, s.Length);
return Task.FromResult(0);
}
}
}
This is not generally supported by ApplicationInsights.
You may still achieve it, but have to write a custom request collection.
You'd need to remove RequestTelemetryTrackingModule from the DI container and add your custom middleware that tracks requests.
What will not work with this approach (code is below):
scenarios when you use different instrumentation keys on this service and upstream services. You can check out how it is handled in AppInsights SDK ( set requestTelemetry.Source and response header)
correlation with informational traces emitted by AspNetCore.
normally, request telemetry name contains route rather than path, you might need to figure it out
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddApplicationInsightsTelemetry("ikey");
var requestModule =
services.FirstOrDefault(sd => sd.ImplementationType == typeof(RequestTrackingTelemetryModule));
if (requestModule != null)
{
services.Remove(requestModule);
}
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, TelemetryClient client)
{
app.UseMiddleware<RequestMiddleware>(client);
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseMvc();
}
}
public class RequestMiddleware
{
private readonly RequestDelegate next;
private readonly TelemetryClient telemetryClient;
public RequestMiddleware(
RequestDelegate next,
TelemetryClient telemetryClient)
{
this.telemetryClient = telemetryClient;
this.next = next;
}
public async Task InvokeAsync(HttpContext context)
{
var activity = new Activity("request");
if (context.Request.Headers.TryGetValue("x-my-correlation-id", out var val))
{
activity.SetParentId(val);
}
using (var request = telemetryClient.StartOperation<RequestTelemetry>(activity))
{
request.Telemetry.Url = context.Request.GetUri();
request.Telemetry.Context.Operation.Name = $"{context.Request.Method} {context.Request.Path.Value}";
request.Telemetry.Name = $"{context.Request.Method} {context.Request.Path.Value}";
try
{
await next.Invoke(context).ConfigureAwait(false);
}
catch (Exception e)
{
telemetryClient.TrackException(e);
request.Telemetry.Success = false;
throw;
}
finally
{
if (context.Response != null)
{
request.Telemetry.ResponseCode = context.Response.StatusCode.ToString();
request.Telemetry.Success = context.Response.StatusCode < 400;
}
else
{
request.Telemetry.Success = false;
}
}
}
}
}

IPFiltering Asp Net Core

I'm using the following nuget package : https://github.com/msmolka/ZNetCS.AspNetCore.IPFiltering
I'm using it to block IP that are trying to bruteforce the authentication of my app.
The Blacklist is defined in the appsetting.json, and I don't know how to dynamicaly modify it during runtime, for example, add an IP that has a bad password.
The way I'm actually doing isn't working as I still can connect even if the IP is correctly persisted in the conf ...
Startup.cs
namespace Sondage
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddIPFiltering(this.Configuration.GetSection("IPFiltering"));
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
// 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.UseHsts();
}
app.UseIPFiltering();
app.UseHttpsRedirection();
app.UseMvc();
}
}
}
appsettings.json
{
"Logging": {
"LogLevel": {
"Default": "Warning"
}
},
"AllowedHosts": "*",
"IPFiltering": {
"DefaultBlockLevel": "None",
"HttpStatusCode": 404,
"Blacklist": [],
"IgnoredPaths": [ "GET:/ignoreget", "*:/ignore" ]
}
}
Part of my controller :
[Route("api/authenticate")]
[ApiController]
public class authenticationController : ControllerBase
{
private IConfiguration _configuration;
public authenticationController(IConfiguration Configuration)
{
_configuration = Configuration;
}
[HttpPost]
public string authent(string value)
{
Dictionary<string, string> result = new Dictionary<string, string>();
IPAddress ip_addr = HttpContext.Connection.RemoteIpAddress;
if(!Globals.tryByIP.TryGetValue(ip_addr, out int numberOfTry)) {
Globals.tryByIP.Add(ip_addr, 0);
} else
{
if(numberOfTry>=0)
{
Console.WriteLine("-----");
_configuration.GetSection("IPFiltering")["Blacklist"] = ip_addr.ToString();
Console.WriteLine(_configuration.GetSection("IPFiltering")["Blacklist"]);
}
}
Make sure to use the IPAddressRange package as well.
I had problems recently with this nuget package as well, and at first I forgot to add the ipaddress parser.

How can I get webrootpath in asp.net core 2 in Program.cs

I am told the following is the correct way to seed the database in asp.net core 2.0, i.e, it must be done in program.cs.
I would like to seed the database from a CSV file in the file system. However I cannot figure to access the content root or web root etc in the Main function.
//Program. cs
public class Program
{
public static void Main(string[] args)
{
var host = BuildWebHost(args);
using (var scope = host.Services.CreateScope())
{
var services = scope.ServiceProvider;
try
{
var context = services.GetRequiredService<EventContext>();
// => How can I get the webrootpath here to pass to the seed function?
// => I cannot figure how to access the HostingEnvironment
DbInitialize.Seed(context);
}
catch (Exception ex)
{
....
}
}
host.Run();
}
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.Build();
}
Not sure if you have gotten an answer or found a solution, but what we have done in our projects.
public class SeedData
{
public static void Seed(IServiceProvider serviceProvider)
{
using (var serviceScope = serviceProvider.GetRequiredService<IServiceScopeFactory>().CreateScope())
{
var env = serviceScope.ServiceProvider.GetRequiredService<IHostingEnvironment>();
var webRoot = env.WebRootPath;
}//end using scope
}// end class
and the usage
public class Program
{
public static void Main(string[] args)
{
var isSeed = args.Contains("/seed");
if (isSeed)
{
args = args.Except(new[] { "/seed" }).ToArray();
}
var host = BuildWebHost(args);
if (isSeed)
{
SeedData.EnsureSeeded(host.Services);
}
host.Run();
}
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.Build();
}

ConfigurationBuilder haven't definition for AddJson

I'm trying to use connection string from my json file by doing next steps
Json file
{
"ConnectionStrings": {
"PlatformDatabase": "Server=xxxx\\SQLEXPRESS;Database=xxxx;Trusted_Connection=True;"
}
}
Access to json
var builder = new ConfigurationBuilder()
.SetBasePath(System.AppContext.BaseDirectory)
.AddJsonFile("appsettings.json",
optional: true,
reloadOnChange: true);
Configuration = builder.Build();
optionsBuilder.UseSqlServer(Configuration.GetConnectionString("PlatformDatabase"));
Error: ConfigurationBuilder does not contain a definition for
AddJsonFile.
Does anyone have this problem before? I tried searching it but all the solution I found doesn't work now (i suppose with version 2).
EDIT
note. I created .Net Core 2.0 console application
The problem was solved by install the nuget package
Microsoft.Extensions.Configuration.Json
You do not need to explicit add appsettings.json in ASP.NET Core 2.
You just need the followings inside Startup.cs -
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(
Configuration.GetConnectionString("PlatformDatabase")));
...
services.AddMvc();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
...
}
Program.cs
public class Program
{
public static void Main(string[] args)
{
BuildWebHost(args).Run();
}
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.Build();
}
Another Approach
You register IConfiguration as Singleton in DI container, and then inject it to your DBContext constructor. Then get the connection string inside OnConfiguring method.
public class Startup
{
public Startup(IConfiguration config)
{
_config = config;
}
private IConfiguration _config;
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton(_config);
services.AddDbContext<YOUR_DB_Context>(ServiceLifetime.Scoped);
...
services.AddMvc();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
...
}
}
YOUR_DB_Context.cs
public class YOUR_DB_Context : IdentityDbContext OR DbContext
{
private IConfiguration _config;
public YOUR_DB_Context(DbContextOptions options, IConfiguration config)
: base(options)
{
_config = config;
}
protected override void OnModelCreating(ModelBuilder builder)
{
...
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
base.OnConfiguring(optionsBuilder);
optionsBuilder.UseSqlServer(_config["Data:PlatformDatabase"]);
}
}

Resources