how to handle exception for IHostBuilder CreateHostBuilder - asp.net-core

To simulate the error, I gave the wrong azure key vault address. With the below code; I tried all the possible ways to try/catch the exception, but still I get an error when the app is start.
How do I handle this exception so the application does NOT throw the error during startup?
I have ASP.NET Core 3.1 web API application.
HTTP Error 500.30 - ANCM In-Process Start Failure
The actual reason for the error is that I put wrong key vault address,
System.Net.Http.HttpRequestException: 'No such host is known.'
public class Program
{
public static void Main(string[] args)
{
try
{
CreateHostBuilder(args).Build().Run();
}
catch (Exception exception)
{
Console.WriteLine(exception);
}
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((context, config) =>
{
try
{
var keyVaultClient = KeyVaultClient();
if (keyVaultClient != null)
config.AddAzureKeyVault("https://testkeyvault07021.vault.azure.net", keyVaultClient,
new DefaultKeyVaultSecretManager());
}
catch (Exception exception)
{
Console.WriteLine(exception);
}
})
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
private static KeyVaultClient KeyVaultClient()
{
try
{
var azureServiceTokenProvider = new AzureServiceTokenProvider();
var keyVaultClient =
new KeyVaultClient(
new KeyVaultClient.AuthenticationCallback(azureServiceTokenProvider.KeyVaultTokenCallback));
return keyVaultClient;
}
catch (Exception exception)
{
Console.WriteLine(exception);
return null;
}
}
}

The application is actually working just fine, I don't think there is any exact way to solve this situation.
When you start the application, it's the work of the program class to configure the hosting environment, that includes setting up the server, before calling the Startup class to finish the configuration of the application.
Startup class is responsible for creating the pipeline that handles HTTP request. Which means if any error occurs before the Startup class is configured, the server won't know what do with the error or how to handle the error and hence you get the HTTP 500,
If the error had to be handled after the Startup class has been called, and the HTTP pipeline configured with the Configure method, and you had included the
app.UseDeveloperExceptionPage();
Then the correct error message would have been printed back.
The error is generated because you make an HTTP request to the API when building it

Related

How to add SoapEndPoint after server is started on ASP.NET Core?

I'm using simple console app to expose a soap web service. It works as expected.
Now i want to add another web service after the server is started. How to make it work?
I have following simple console application:
static void Main(string[] args)
{
var host = WebApplication.CreateBuilder();
_App = host.Build();
_App.UseRouting();
_App.UseEndpoints(endpoints =>
{
endpoints.UseSoapEndpoint<ISimpleServiceInterface>("/SimpleService.asmx", new SoapEncoderOptions(), SoapSerializer.XmlSerializer);
});
_App.Urls.Add("http://*:5000");
_App.RunAsync();
Console.WriteLine("Server has been started successfully ...");
AddNewService();
Console.ReadLine();
}
Server starts and i can access the wsdl http://localhost:5000/SimpleService.asmx?wsdl)
Now the AddNewService method simple try to define a new SoapEndPoint after service started.
Code looks like this:
static private void AddNewService()
{
try
{
System.Threading.Thread.Sleep(5000); // Wait 5 seconds to make sure web application is running
Console.WriteLine("Adding new service ..."); // Add new Soap service now, after startup
_App?.UseEndpoints(endpoints =>
{
endpoints.UseSoapEndpoint<ISimpleServiceInterface2>("/SimpleService2.asmx", new SoapEncoderOptions(), SoapSerializer.XmlSerializer);
});
Console.WriteLine("Added new service.");
}
catch(Exception ex)
{
Console.WriteLine("Failed to Add new service. Error=" + ex.Message);
}
}
This works ok if first request to url is done after the service is created: (http://localhost:5000/SimpleService2.asmx?wsdl)
But if a request is sent before the service is created. Then any request done after the creation of the service will fail:
I'm guessing i need to raise some event or something to the web server to get it refreshed or something.
How can i do that?
Also is there a way to remove a SoapEndPoint once is has been defined/exposed?
Idea is basically being able to add/remove/update SoapEndPoint on the fly.
Any help will be appreciated. Thanks in advance
Progess a little bit on this.
I basically need to register IActionDescriptorChangeProvider class to be able to notify the web application.
I also needed to slightly change my main routine.
Here is the main function:
static void Main(string[] args)
{
var host = WebApplication.CreateBuilder();
host.Services.AddControllers();
host.Services.AddSingleton<IActionDescriptorChangeProvider>(MyActionDescriptorChangeProvider.Instance);
host.Services.AddSingleton(MyActionDescriptorChangeProvider.Instance);
host.Services.AddSingleton<ISimpleServiceInterface, SimpleService>();
_App = host.Build();
_App.MapControllers();
_App.UseRouting();
_App.UseEndpoints(endpoints =>
{
endpoints.UseSoapEndpoint<ISimpleServiceInterface>("/SimpleService.asmx", new SoapEncoderOptions(), SoapSerializer.XmlSerializer);
});
_App.Urls.Add("http://*:5000");
_App.RunAsync();
Console.WriteLine("Server has been started successfully ...");
AddNewService();
Console.ReadLine();
}
Then the AddService function (note the 2 lines added to make the notification):
static private void AddNewService()
{
try
{
System.Threading.Thread.Sleep(5000); // Wait 5 seconds to make sure web application is running
Console.WriteLine("Adding new service ..."); // Add new Soap service now, after startup
_App?.UseEndpoints(endpoints =>
{
endpoints.UseSoapEndpoint<ISimpleServiceInterface2>("/SimpleService2.asmx", new SoapEncoderOptions(), SoapSerializer.XmlSerializer);
});
// Notify the web application of the changes
MyActionDescriptorChangeProvider.Instance.HasChanged = true;
MyActionDescriptorChangeProvider.Instance.TokenSource.Cancel();
Console.WriteLine("Added new service.");
}
catch(Exception ex)
{
Console.WriteLine("Failed to Add new service. Error=" + ex.Message);
}
}
and class implementing IActionDescriptorChangeProvider:
public class MyActionDescriptorChangeProvider : IActionDescriptorChangeProvider
{
public static MyActionDescriptorChangeProvider Instance { get; } = new MyActionDescriptorChangeProvider();
public CancellationTokenSource TokenSource { get; private set; } = new CancellationTokenSource();
public bool HasChanged { get; set; }
public IChangeToken GetChangeToken()
{
TokenSource = new CancellationTokenSource();
return new CancellationChangeToken(TokenSource.Token);
}
}
Once you do that, it will work fine on the second request (wsdl request).
Problem is that the wsdl may be accessible but the function itself (route to the actual method on the singleton) is not there.
Registration of the singleton for ISimpleServiceInterface2 need to be done but not sure how to achieve this.
My end goal is to be able to add/remove/update soap web service after server is built.
Basically idea is to update the soap service with a newer assembly.
If anybody has some idea, comments, response, please post them here. That will be appreciated.

.Net core log Startup errors to app insights

I'm trying to log any startup errors but the logs are not flowing into application insights. I tried configuring app insights in program.cs but still I don't see any logs.
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureLogging(logging =>
{
logging.ClearProviders();
logging.AddApplicationInsights();
logging.SetMinimumLevel(LogLevel.Information);
});
To Capture logs from program.cs or startup.cs, you need to configure like this.
.ConfigureLogging((context, builder) =>
{
// Providing a connection string is required if need to capture logs during application startup, such as
// in Program.cs or Startup.cs itself.
builder.AddApplicationInsights(
configureTelemetryConfiguration: (config) => config.ConnectionString = context.Configuration["APPLICATIONINSIGHTS_CONNECTION_STRING"],
configureApplicationInsightsLoggerOptions: (options) => { }
);
// Capture all log-level entries from Program
builder.AddFilter<ApplicationInsightsLoggerProvider>(
typeof(Program).FullName, LogLevel.Trace);
// Capture all log-level entries from Startup
builder.AddFilter<ApplicationInsightsLoggerProvider>(
typeof(Startup).FullName, LogLevel.Trace);
});
For those using .NET 6 projects with minimal hosting model, this worked for me to capture the startup/shutdown messages into Application Insights:
// Program.cs
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddApplicationInsightsTelemetry();
builder.Logging.AddFilter<ApplicationInsightsLoggerProvider>("Microsoft.Hosting.Lifetime", LogLevel.Information);
...
By default it is not supported to handle exceptions from application startup, see this link.
What I did to get the logs into the application insights is to add a try/catch block in the Main of Program.cs and initialize the TelemetryClient manually.
See the following code:
public static void Main(string[] args)
{
try
{
CreateHostBuilder(args)
.Build().Run();
}
catch(Exception ex)
{
TelemetryConfiguration telemetryConfiguration = TelemetryConfiguration.CreateDefault();
telemetryConfiguration.ConnectionString = "application-insights-connection-string";
var telemetryClient = new TelemetryClient(telemetryConfiguration);
telemetryClient.TrackException(ex);
telemetryClient.Flush();
throw;
}
}

ASP.NET Core 6 MVC app custom ExceptionFilter does not catch all exceptions

I have web app with custom exception filter.
public class CustomExceptionFilter : ExceptionFilterAttribute
{
public override void OnException(ExceptionContext context)
{
// do stuff to log exception
}
}
Exception filter is added to filters inside startup class.
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(options =>
{
// ...
options.Filters.Add(new CustomExceptionFilter());
// ...
});
}
}
This custom filter catches almost all non-handled exceptions besides this one.
Microsoft.AspNetCore.Mvc.ViewFeatures.CookieTempDataProvider
The temp data cookie .AspNetCore.Mvc.CookieTempDataProvider could not be loaded.
System.IndexOutOfRangeException: Index was outside the bounds of the array.
at System.Text.Json.JsonHelpers.TryParseDateTimeOffset(ReadOnlySpan`1 source, DateTimeParseData& parseData)
at System.Text.Json.JsonHelpers.TryParseAsISO(ReadOnlySpan`1 source, DateTime& value)
at System.Text.Json.JsonReaderHelper.TryGetEscapedDateTime(ReadOnlySpan`1 source, DateTime& value)
at System.Text.Json.JsonDocument.TryGetValue(Int32 index, DateTime& value)
at System.Text.Json.JsonElement.TryGetDateTime(DateTime& value)
at Microsoft.AspNetCore.Mvc.ViewFeatures.Infrastructure.DefaultTempDataSerializer.DeserializeDictionary(JsonElement rootElement)
at Microsoft.AspNetCore.Mvc.ViewFeatures.Infrastructure.DefaultTempDataSerializer.Deserialize(Byte[] value)
at Microsoft.AspNetCore.Mvc.ViewFeatures.CookieTempDataProvider.LoadTempData(HttpContext context)
I'm using TempData to preserve some data between posts and redirects. I've looked at all calls where TempData is used but cannot find the place where this error could show up. This particular error is spat out using Serilog.
My question is why the custom exception filter does not catch this IndexOutOfRangeException? Is there a way to catch them or configure Serilog to be more specific? I would like trace where it comes from to get rid of it.
Follow up
Found similar bug that is described in aspnet core git issues. But my problem is not with some format of string. I get out of range exception even if I check TempData count or Keys.
public static bool HasValue(this ITempDataDictionary tempData, string key)
{
try
{
if (tempData == null)
return false;
// if no tempData is set, it enters here, generates no
// exception, but spits out warning through Serilog.
if (tempData.ContainsKey(key) == false)
return false;
if (tempData.Count == 0)
return false;
return tempData.Peek(key) != null;
}
catch (Exception ex)
{
// ...
}
return false;
}
Temp solution so I can sleep at night
Adding logging override to serilog configuration.
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Override("Microsoft.AspNetCore.Mvc.ViewFeatures.CookieTempDataProvider", LogEventLevel.Error);

Asp Net Core SQL Custom Configuration Provider with Dapper and Error Handling

I am setting up my MVC web application to pull configuration data from my SQL Azure database on startup. I have used these two articles (Microsoft, Medium) to guide me but neither include error handling and I want to avoid any Entity Framework references as i'm using Dapper. So far I've got it working with below code but I'm not sure how to handle errors in this scenario. For instance if I remove the try/catch from the Load method in SQLConfigurationProvider then the app crashes on startup but if I include the try/catch then the error is handled and the app starts normally but no config data is available so will eventually break when trying to access a config value. What is the best way to handle these errors gracefully (ie app still loads but displays an error page/message instead)? Also is there any benefit to having the SQLConfigurationSource or would it make more sense just to create the new SqlConnection instance inside SQLConfigurationProvider instead?
Program.cs
public class Program
{
public static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.CaptureStartupErrors(true)
.UseSetting(WebHostDefaults.DetailedErrorsKey, "true")
.UseApplicationInsights()
.ConfigureAppConfiguration((hostingContext, config) =>
{
config.AddSQLConfiguration(); // Custom configuration here
})
.UseStartup<Startup>();
}
ConfigurationExtensions.cs
public static class ConfigurationExtensions
{
public static IConfigurationBuilder AddSQLConfiguration(this IConfigurationBuilder builder)
{
var connectionString = builder.Build().GetConnectionString("DefaultConnection");
return builder.Add(new SQLConfigurationSource(connectionString));
}
}
SQLConfigurationSource.cs
public class SQLConfigurationSource : IConfigurationSource
{
private readonly SqlConnection _connection;
public SQLConfigurationSource(string connectionString)
{
_connection = new SqlConnection(connectionString);
}
public IConfigurationProvider Build(IConfigurationBuilder builder)
{
return new SQLConfigurationProvider(_connection);
}
}
SQLConfigurationProvider.cs
public class SQLConfigurationProvider : ConfigurationProvider
{
private readonly SqlConnection _connection;
public SQLConfigurationProvider(SqlConnection connection)
{
_connection = connection;
}
public override void Load()
{
try
{
var model = _connection.Query<SQLConfigurationModel>("sp does not exist for example", commandType: CommandType.StoredProcedure);
Data = model.ToDictionary(x => x.Property, x => x.Value);
}
catch (Exception ex)
{
// WHAT TO DO HERE?
}
}
}
public class SQLConfigurationModel
{
public string Property { get; set; }
public string Value { get; set; }
}
---- UPDATE: CLOSE BUT NOT QUITE THERE ----
I added the exception as a configuration value which I then check for in the Configure method of Startup.cs as per below. This helps ensure the app doesn't crash on startup but when I throw the exception it is not getting routed to the Error view even though the exception handler has already been configured with app.UseExceptionHandler("/Home/Error")
// Inside SQLConfigurationProvider
public override void Load()
{
try
{
var model = _connection.Query<SQLConfigurationModel>("sp does not exist for example", commandType: CommandType.StoredProcedure);
Data = model.ToDictionary(x => x.Property, x => x.Value);
}
catch (Exception ex)
{
Data.Add("ConfigurationLoadException", ex.Message);
}
}
// Inside Startup.cs
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseExceptionHandler("/Home/Error");
// Check for custom config exception
string configurationLoadException = Configuration["ConfigurationLoadException"];
if (configurationLoadException.Length > 0)
{
throw new Exception("Configuration Failed: " + configurationLoadException);
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
If your application can't work without the configurations stored in SQL, you should move this code to fetch data to have better error management. That way you will be able to show a proper error message to user and log it better. Other option is use try/catch block in program.cs, and the assumption is that the not having the SQL driven configuration, will not break the startup project but further in the application usage. If that's the case, you will already have error management placed in startup and it can show you a functional error page for this.
This link will give you some views about startup/program.cs error handling
You should configure a custom error handling page Please read following. it's easy to do
Custom Error Page .net Core

Why does the following code return an error when consuming multiple endpoints of a WCF service?

I have created a multi-endpoint WCF service and consumed and it is working fine.
But when I am trying to close the service client then am getting error.
This is how I am creating the client object and disposing its working fine for single endpoint WCF service
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
ICardPrintingService Service = null;
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
Service = new CardPrintingServiceClient();
var response = this.Service.GetCardData(new GetCardDataRequest { NIK = 6666620501740003 });
try
{
((CardPrintingServiceClient)Service).Close();
}
catch (Exception ex)
{
MessageBox.Show("error");
}
}
}
}
This is going to the catch block when closing the connection with error message
The remote endpoint no longer recognizes this sequence. This is most
likely due to an abort on the remote endpoint. The value of
wsrm:Identifier is not a known Sequence identifier. The reliable
session was faulted.
Can some one tell me why?
Thanks a ton in adv
Raghavendra
What is the need of casting overhere.
((CardPrintingServiceClient)Service).Close(); //pls explain this.
you can try this in finally block.
if (Service.State != System.ServiceModel.CommunicationState.Closed)
{
Service.Abort();
}