How to configure HealthCheck with IHost? - c#

In our current implementation of healthcheck's in worker service we do like this (simplified)
var options = new WebApplicationOptions {
Args = args,
ContentRootPath = WindowsServiceHelpers.IsWindowsService()
? AppContext.BaseDirectory
: default
};
var builder = WebApplication.CreateBuilder(options);
builder.Host.UseWindowsService();
builder.Services.AddHealthChecks().AddCheck<ServiceIsOnlineCheck>(nameof(ServiceIsOnlineCheck));
builder.Services.AddHostedService<Worker>();
var healthcheckoptions = new HealthCheckOptions
{
ResponseWriter = ResponseWriters.WriteDetailedStatus,
ResultStatusCodes =
{
[HealthStatus.Healthy] = StatusCodes.Status200OK,
[HealthStatus.Degraded] = StatusCodes.Status200OK,
[HealthStatus.Unhealthy] = StatusCodes.Status200OK
}
};
var app = builder.Build();
app.UseHealthChecks("/health", healthcheckoptions);
app.Run();
When I create a new worker service in .NET 7, the setup in program.cs is completely different and I can not understand how we can set up health checks in them.
How do you implement it when program.cs looks like this? (we need to set our own response writer and other custom options)
IHost host = Host.CreateDefaultBuilder(args)
.UseWindowsService(options =>
{
options.ServiceName = "Service Name";
})
.ConfigureWebHost(host =>
{
// ???
})
.ConfigureServices(services =>
{
services.AddHostedService<RunOnScheduleWorker>();
})
.Build();
host.Run();

This template uses the generic hosting (which was used in pre .NET 6 templates), so you can setup it with Startup. Here is a small working snippet which you can draw inspiration from:
IHost host = Host.CreateDefaultBuilder(args)
.UseConsoleLifetime()
.ConfigureWebHostDefaults(builder =>
{
builder.UseStartup<Startup>();
})
.ConfigureServices(services => { services.AddHostedService<Worker>(); })
.Build();
host.Run();
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddHealthChecks();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseHealthChecks("/health");
}
}
But you are not limited to using it, you can:
Switch to the one previously used, just copy-paste everything from the one you used.
Since you want to expose ASP.NET Core endpoint(s) - use corresponding project type and add just hosted service to it.
Read more:
.NET Generic Host
.NET Generic Host in ASP.NET Core
The Startup class

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>();
}

Getting Null from value using IOptionSnapshot<T>

I'm trying to implement Azure App Configuration to my Application that uses the ASP.NET Boilerplate Framework. I'm following this tutorial but when I try to access my settings everything comes null. When the Startup.cs get executed I can see the values in the constructor but when I try to get them else where I get the null.
Program.cs:
public class Program
{
public static void Main(string[] args)
{
BuildWebHost(args).Run();
}
public static IWebHost BuildWebHost(string[] args)
{
return WebHost.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((config) =>
{
// Retrieve the connection string
IConfiguration settings = config.Build();
string connectionString = settings.GetConnectionString("AppConfig");
// Load configuration from Azure App Configuration
config.AddAzureAppConfiguration(options =>
{
options.Connect(connectionString)
// Load all keys that start with `TestApp:` and have no label
.Select("TestApp:*", LabelFilter.Null)
// Configure to reload configuration if the registered sentinel key is modified
.ConfigureRefresh(refreshOptions => refreshOptions.Register("TestApp:Settings:Sentinel", refreshAll: true));
}).Build();
})
.UseStartup<Startup>()
.Build();
}
}
Startup.cs:
public class Startup
{
private const string _defaultCorsPolicyName = "localhost";
private const string _apiVersion = "v1";
public IConfigurationRoot _appConfiguration;
public IConfiguration Configuration { get; }
public Startup(IWebHostEnvironment env, IConfiguration configuration)
{
_appConfiguration = env.GetAppConfiguration();
Configuration = configuration; //Azure App Configuration
}
public IServiceProvider ConfigureServices(IServiceCollection services)
{
//MVC
services.AddControllersWithViews(
options =>
{
options.Filters.Add(new AbpAutoValidateAntiforgeryTokenAttribute());
}
).AddNewtonsoftJson(options =>
{
options.SerializerSettings.ContractResolver = new AbpMvcContractResolver(IocManager.Instance)
{
NamingStrategy = new CamelCaseNamingStrategy()
};
});
IdentityRegistrar.Register(services);
AuthConfigurer.Configure(services, _appConfiguration);
services.AddSignalR();
// Configure CORS for angular2 UI
services.AddCors(
options => options.AddPolicy(
_defaultCorsPolicyName,
builder => builder
.WithOrigins(
// App:CorsOrigins in appsettings.json can contain more than one address separated by comma.
_appConfiguration["App:CorsOrigins"]
.Split(",", StringSplitOptions.RemoveEmptyEntries)
.Select(o => o.RemovePostFix("/"))
.ToArray()
)
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials()
)
);
options.DocInclusionPredicate((docName, description) => true);
// Define the BearerAuth scheme that's in use
options.AddSecurityDefinition("bearerAuth", new OpenApiSecurityScheme()
{
Description = "JWT Authorization header using the Bearer scheme. Example: \"Authorization: Bearer {token}\"",
Name = "Authorization",
In = ParameterLocation.Header,
Type = SecuritySchemeType.ApiKey
});
});
services.AddAzureAppConfiguration();
// Bind configuration "TestApp:Settings" section to the Settings object
services.AddOptions();
services.Configure<Settings>(Configuration.GetSection("TestApp:Settings"));
// Configure Abp and Dependency Injection
return services.AddAbp<RptWebHostModule>(
// Configure Log4Net logging
options => options.IocManager.IocContainer.AddFacility<LoggingFacility>(
f => f.UseAbpLog4Net().WithConfig("log4net.config")
)
);
}
public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory)
{
app.UseAbp(options => { options.UseAbpRequestLocalization = false; }); // Initializes ABP framework.
app.UseCors(_defaultCorsPolicyName); // Enable CORS!
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAbpRequestLocalization();
app.UseEndpoints(endpoints =>
{
endpoints.MapHub<AbpCommonHub>("/signalr");
endpoints.MapControllerRoute("default", "{controller=Home}/{action=Index}/{id?}");
endpoints.MapControllerRoute("defaultWithArea", "{area}/{controller=Home}/{action=Index}/{id?}");
});
options.IndexStream = () => Assembly.GetExecutingAssembly()
options.DisplayRequestDuration(); // Controls the display of the request duration (in milliseconds) for "Try it out" requests.
});
// Use Azure App Configuration middleware for dynamic configuration refresh.
app.UseAzureAppConfiguration();
}
}
Custom Controller where I get the null values:
[Route("api/[controller]/[action]")]
public class AzureAppConfigTest : AbpControllerBase
{
public Settings _settings { get; }
public AzureAppConfigTest(IOptionsSnapshot<Settings> options
)
{
_settings = options.Value;
}
[HttpPost]
public string Test()
{
return _settings.Message; // The Problem is here
}
}
I need to get the values else where in the Application, I tried changing IOptionsSnapshot for IOptions but I can't make it work, Iv'e been stuck with this about two week but since I'm new in the Microsoft world I can't see clearly where the problem is, Thanks in Advance
Update:
I am able to use the configuration at the Presentation Layer, but If I try to use it on the Application layer I don't get the values.

.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...

How to get environment variables from inside Main() in ASP.NET Core?

I want to know if the code is Dev/Stage so I need to get this. I tried the code below but it seems to skip running the Configure() of Startup.cs. Help ?
public static void Main(string[] args)
{
IHost host = CreateHostBuilder(args).Build();
SeedDatabase(host);
host.Run();
}
public static IHostBuilder CreateHostBuilder(string[] args)
{
//return Host.CreateDefaultBuilder(args)
// .ConfigureWebHostDefaults(webBuilder =>
// {
// webBuilder.UseStartup<Startup>();
// });
return Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
webBuilder.ConfigureServices(services => { })
.Configure(app =>
{
Env = app.ApplicationServices.GetRequiredService<IWebHostEnvironment>();
});
});
}
You haven't quite explained what it is you are trying to get (the environment variables or the environment object). But based on some of the code in the question it appears like you want access to the IWebHostEnvironment.
That's easy. IHost has a Services property. You just need to request the appropriate service.
IHost host = CreateHostBuilder(args).Build();
IWebHostEnvironment env = host.Services.GetRequiredService<IWebHostEnvironment>();
// now do something with env
SeedDatabase(host);
host.Run();
Now if you need the environment prior to everything else, you can also access the environment from the WebHostBuilderContext inside of a different overload of ConfigureServices:
webBuilder.ConfigureServices((ctx, services) => {
IWebHostEnvironment env = ctx.HostingEnvironment;
// Do something with env
});
Similarly ConfigureAppConfiguration, ConfigureHostConfiguration and ConfigureServices on the IHostBuilder (the generic host) also provide access to the IHostEnvironment (note lack of "Web"):
return Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((ctx, config) => {
IHostEnvironment env = ctx.HostingEnvironment;
})
.ConfigureServices((ctx, services) => {
IHostEnvironment env = ctx.HostingEnvironment;
})
.ConfigureWebHostDefaults(/* etc */ );
The same service collection, environments etc are used by the generic host and the web host (in addition to the added IWebHostEnvironment)
Side note: Usually Startup.cs isn't invoked until the first call is made to your application. That is probably why it appears to not be running.
Try getting it from inside your main method, like this:
static void Main(string[] args)
{
var environmentName = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
}

How to Host and Deploy ASP.Net core 2.0 webapi on azure?

I've created a solution in visual studio 2017 in which I've created below projects:
Client (Angular Template using core 2.1)
Server (web api using core 2.0)
As I'm new to deploy my app on azure. So, by using references on internet I successfully deploy my client app on azure and it is up and running on
https://ebarvo.azurewebsites.net
and now What I need to do is to deploy my server on azure.
I've implemented IdentityServer 4 Resource Owner Password Grant Client in my web api. On my local iis server my (client and web api) server apps is running individually.
According to point [OPTIONAL] Step 4: Create your own Web API. I've register my web api in the B2C settings. Here is the screen shot:
Now after registering my web api according to this link my first question is how and where I can use my Application Client ID in my application code?
Here I'll show you web api (server) config.cs / startup.cs / program.cs file code:
config.cs
public class Config
{
public static IEnumerable<IdentityResource> GetIdentityResources()
{
return new List<IdentityResource>
{
new IdentityResources.OpenId(),
new IdentityResources.Email(),
new IdentityResources.Profile(),
};
}
public static IEnumerable<ApiResource> GetApiResources()
{
return new List<ApiResource>
{
new ApiResource("api1", "My API")
};
}
public static IEnumerable<Client> GetClients()
{
// client credentials client
return new List<Client>
{
// resource owner password grant client
new Client
{
ClientId = "ro.angular",
AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
ClientSecrets =
{
new Secret("secret".Sha256())
},
AllowedScopes = {
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.Email,
IdentityServerConstants.StandardScopes.Address,
"api1"
},
AllowOfflineAccess = true,
AccessTokenLifetime = 1
}
};
}
}
Startup.cs
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.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddIdentityServer()
.AddDeveloperSigningCredential()
.AddInMemoryPersistedGrants()
.AddInMemoryIdentityResources(Config.GetIdentityResources())
.AddInMemoryApiResources(Config.GetApiResources())
.AddInMemoryClients(Config.GetClients())
.AddAspNetIdentity<ApplicationUser>();
services.AddCors(options =>
{
options.AddPolicy("AllowClient",
builder => builder.WithOrigins("https://localhost:44335")
.AllowAnyHeader()
.AllowAnyMethod());
});
services.AddMvc();
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
// base-address of your identityserver
options.Authority = "http://localhost:52718/";
// name of the API resource
options.Audience = "api1";
options.RequireHttpsMetadata = false;
});
}
// 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();
}
app.UseIdentityServer();
app.UseAuthentication();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "areas",
template: "{area:exists}/{controller=Home}/{action=Index}/{id?}"
);
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
}
Program.cs
public class Program
{
public static void Main(string[] args)
{
BuildWebHost(args).Run();
}
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseUrls("http://localhost:52718/")
.UseStartup<Startup>()
.Build();
}
Now, If I publish my webapi to azure like below
Step # 1
Step # 2
After selecting existing app service
Step # 3
After publishing
I got this message:
and If I make a post request through post man:
on local its running fine
but after deployment on azure its shows me 500 internal server error.
Now I more explain my question that what is the right way and how to Host and Deploy ASP.Net core 2.0 webapi on azure?
and further more what I'm doing wrong in my code or in my steps so my server is not responding me? I think I explain every single step here to show you people what I'm doing and what I'm try to do. Please help me on this I'll be very thankful to you all.
I think your problem is here :
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseUrls("http://localhost:52718/")
.UseStartup<Startup>()
.Build();
try to delete .UseUrls( … )
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.Build();
I never used that command and never had a problem when I publish on azure, If you need to specify a port on the local machine, go on Project properties -> Debug -> Web Server Settings -> App URL

Categories

Resources