Set up logging with Blazor WebAssembly - asp.net-core

I'm doing some experiments with Blazor and want to set up logging. I see that Blazor logs to Microsoft.Extensions.Logging out of the box and that the log messages go to the developer console inside the browser. That is a nice start.
Now I want to try and log messages to other destinations as well. It could be a cloud-service. I'm wondering where to set that up. In ASP.NET Core, you would set it up using the ConfigureLogging method in Program.cs. But this isn't available with Blazor:
public static IWebAssemblyHostBuilder CreateHostBuilder(string[] args) =>
BlazorWebAssemblyHost.CreateDefaultBuilder()
.UseBlazorStartup<Startup>()
.ConfigureLogging(...); // <- compile error
As a fallback, I'm trying to set it up through ConfigureServices in Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddLogging(builder => builder
.AddMyLogger()
.SetMinimumLevel(LogLevel.Information));
}
with AddMyLogger:
public static ILoggingBuilder AddMyLogger(this ILoggingBuilder builder)
{
builder.Services.AddSingleton<ILoggerProvider, MyLoggerProvider>();
return builder;
}
and MyLoggerProvider:
public class MyLoggerProvider : ILoggerProvider
{
public ILogger CreateLogger(string categoryName)
{
return new MyLogger();
}
public void Dispose()
{
}
}
and MyLogger:
public class MyLogger : ILogger
{
public MyLogger()
{
}
public IDisposable BeginScope<TState>(TState state)
{
return null;
}
public bool IsEnabled(LogLevel logLevel)
{
return true;
}
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{
}
}
The AddMyLogger-method is called but my logger is never created or receives any Log-calls.
Am I doing something wrong here or is logging with Blazor WebAssembly simply not ready yet?

I was trying something similar. In my case, the Log method in MyLogger gets called; however it fails at following line of code
using (var streamWriter = new StreamWriter(fullFilePath, true)) //Fails here
{
streamWriter.WriteLine(logRecord);
}
When I put it in try catch block, I got the exception "Children could not be evaluated".
While researching I came across following link. Steve Sanderson's response might make sense of the behavior
Reading local files #16156
BTW It's been a long time, please let me know the solution you came up with.

Related

ASP.NET Core SignalR Adding service to hub breaks

I'm currently working on an ASP.NET Core Web Application.
I have a MQTT Server, which is connected to a service (IHostedService) and this service references a SignalR Hub.
So if there is a new message comming from the MQTT Server, it is forwarded to the hub and therefore to the client.
This works fine. But now I would like to add a button to send MQTT messages back to the MQTT server.
To do so, I added a function in the hub, which es called by the button via SignalR.
So far so good but when adding the service now to the constructor of the hub it fails, when I open the web app (not during startup), with the following message:
fail: Microsoft.AspNetCore.SignalR.HubConnectionHandler[1]
Error when dispatching 'OnConnectedAsync' on hub.
System.InvalidOperationException: Unable to resolve service for type 'websiteApp.HostedServices.UserPromptService' while attempting to activate 'websiteApp.Hubs.UserPromptHub'.
at Microsoft.Extensions.DependencyInjection.ActivatorUtilities.GetService(IServiceProvider sp, Type type, Type requiredBy, Boolean isDefaultParameterRequired)
at lambda_method(Closure , IServiceProvider , Object[] )
at Microsoft.AspNetCore.SignalR.Internal.DefaultHubActivator'1.Create()
at Microsoft.AspNetCore.SignalR.Internal.DefaultHubDispatcher'1.OnConnectedAsync(HubConnectionContext connection)
at Microsoft.AspNetCore.SignalR.Internal.DefaultHubDispatcher'1.OnConnectedAsync(HubConnectionContext connection)
at Microsoft.AspNetCore.SignalR.HubConnectionHandler'1.RunHubAsync(HubConnectionContext connection)
The service declaration looks like this:
public class UserPromptService : IHostedService, IDisposable
{
public UserPromptService(ILogger<UserPromptService> logger, IConfiguration config, UserPromptContext userPromptContext, IHubContext<UserPromptHub> userPromptHub)
{
}
}
And my hub looks like this:
public class UserPromptHub : Hub<IUserPromptHub>
{
public UserPromptHub(UserPromptService service) // everything works until I add the service here
{
service.ToString(); // just for testing
}
}
And they are configured in the Startup.cs:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// ...
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
endpoints.MapHub<Hubs.UserPromptHub>("/userPromptHub");
});
}
As well as in the Program.cs:
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
// ...
.ConfigureServices(services =>
{
services.AddSingleton<websiteApp.DataContext.UserPromptContext>();
services.AddHostedService<HostedServices.UserPromptService>();
});
Could you please help me to fix the problem?
One option to solve your problem would be to restructure your code a little bit.So instead of your UserPromptService be responsable for the MQTT connection you create a seperate class for that.
The following is only sudo code
You could create a new class
public class MQTTConnection
{
private readonly _yourMQTTServerConnection;
public MQTTConnection()
{
_yourMQTTServerConnection = new ServerConnection(connectionstring etc);
}
public Task<Message> GetMessage()
{
return _yourMQTTServerConnection.GetMessageAsync();
}
public Task SendMessage(Message message)
{
return _yourMQTTServerConnection.SendMessageAsync(message);
}
}
So your Hub look something like this
public class UserPromptHub : Hub<IUserPromptHub>
{
private readonly MQTTConnection _connection;
public UserPromptHub(MQTTConnection connection)
{
_connection = connection
}
public async Task MessageYouReceiveFromTheUser(object object)
{
// your business logic
await _connection.SendMessage(message);
}
public Task MessageYouSendToTheClient(object object)
{
await Clients.All.MessageYouSendToTheClient(message);
}
}
And your UserPromptService looks somehting like that
public class UserPromptService : IHostedService, IDisposable
{
public UserPromptService(ILogger<UserPromptService> logger,
IConfiguration config,
UserPromptContext userPromptContext,
IHubContext<UserPromptHub> userPromptHub,
MQTTConnection connection)
{
// map arguments to private fields
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while(yourAppIsUpAndRunning)
{
var message = await _connection.GetMessage()
// process the message
await _hub.MessageYouSendToTheClient(yourMessage)
}
}
}
I hope my approach is understandable, if not I can add more details.

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

How to set custom URL in MVC4

I need in MVC4 in C#...
{id}.example.com or
{id}.example.com/{controller}/{action}
Or
In localhost how can I test it.
I means, can I debug this code in below format ...
{id}.localhost:51782 or
{id}.localhost:51782/{controller}/{action}
Please explain this in full steps.
You need to handle your requests with "virtual" URL, for example {user}.example.com and internally update httpcontext path using httpContext.RewritePath(). Finally you will have two types of URIs.
Virtual: {id}.example.com/{controller}/{action}
Real: {id}/{controller}/{action}
Handling is possible in Application_BeginRequest inside your Global.asax.cs file
protected void Application_BeginRequest(object sender, EventArgs e)
{
var domainHandler = new DomainsHandler(Context);
domainHandler.Handle();
}
And example of DomainHandler:
public class DomainsHandler
{
private readonly HttpContext httpContext;
public DomainsHandler(HttpContext httpContext)
{
this.httpContext = httpContext;
}
public void Handle()
{
httpContext.RewritePath(path);
}
}
Rewriting logic inside Handle() method is up to you.

HttpContext.Current.Session is null when injecting it to interceptor / or using in inside interceptor (mvc4 webapi)

I have mvc4 WebAPI application with castle Windsor interception.
The interceptor class needs HttpContext.Current.Session.
When I call it directly from the interceptor, it is null.
So I read here that I need to inject the Session and not just access it in the interceptor.
This the code I ended up with...
protected void Application_Start()
{
ControllerBuilder.Current.SetControllerFactory(new WindsorControllerFactory(container.Kernel));
this.RegisterDependencyResolver();
this.container.Install(new WindsorWebApiInstaller());
}
public class WindsorWebApiInstaller : IWindsorInstaller
{
public void Install(IWindsorContainer container, IConfigurationStore store)
{
// the following interceptor needs Session object
container.Register( Component.For<IInterceptor>()
.ImplementedBy<SecurityInterceptor>()
.LifestylePerWebRequest()
.Named("SecurityInterceptor"));
// session is null here, So Castle wont inject it and throw exception...
container.Register(
Component.For<HttpSessionStateBase>().UsingFactoryMethod(
() => new HttpSessionStateWrapper(HttpContext.Current.Session)).LifestylePerWebRequest());
}
}
Is there any other way to access the session from the interceptor?
Thanks
I always forget that WEBAPI is not MVC.
this is not castle issue.
this did the trick!
public override void Init()
{
this.PostAuthenticateRequest += MvcApplication_PostAuthenticateRequest;
base.Init();
}
void MvcApplication_PostAuthenticateRequest(object sender, EventArgs e)
{
System.Web.HttpContext.Current.SetSessionStateBehavior(
SessionStateBehavior.Required);
}
https://stackoverflow.com/a/15038669/936651
+1 to #Soren

ServiceStack NHibernate and Ninject in Self Hosting App (Request Context)

I have a self hosted ServiceStack application, and I try to build ISession per request. I suppose the following will work:
Bind<ISession>()
.ToMethod(NapraviSesiju)
.InNamedScope(ControllerScope)
.InScope(s => ReuseScope.Request)
.OnActivation(s => s.BeginTransaction())
.OnDeactivation(s =>
{
if (!s.Transaction.IsActive) return;
try
{
s.Transaction.Commit();
}
catch (Exception e)
{
s.Transaction.Rollback();
}
});
private ISession NapraviSesiju(IContext kontekst)
{
var sesija = kontekst.Kernel.Get<ISessionFactory>().OpenSession();
return sesija;
}
This works, but request deactivation is not instant (it happens after 30 seconds, or 1 minute, and some requests don't deactivate at all).
Can someone please tell me the correct way to handle NHibernate Sessions this way?
UPDATE
Can I use this then:
public class AppHost : AppHostHttpListenerBase
{
private IKernel _jezgro;
public override void Configure(Container container)
{
_jezgro = new StandardKernel(new NHibernateModul());
container.Adapter = new NinjectIocAdapter(_jezgro);
}
public override void Release(object instance)
{
_jezgro.Release(((IHasSession)instance).Sesija); //Release Sesija from SomeServis object below
}
}
public class SomeServis : RestServiceBase<Some>, IHasSession //implements NHibernate Session
{
public ISession Sesija { get; set; } //IHasSession implementation. Injected by Ninject.
}
Bind<ISession>()
.ToMethod(NapraviSesiju)
.InScope(s => ReuseScope.Request) //reuse per request scope. Is this really needed, since release is happening at Release in AppHost?
.OnActivation(s => s.BeginTransaction())
.OnDeactivation(s =>
{
if (!s.Transaction.IsActive) return;
try
{
s.Transaction.Commit();
}
catch (Exception)
{
s.Transaction.Rollback();
}
});
The bottom of the IOC Container wiki page explains the Release behavior of IOC resources. The easiest way to handle disposed resources is to implement the IRelease method and delegate the Released instances back into Ninject, e.g:
public class NinjectIocAdapter : IContainerAdapter, IRelease
{
private readonly IKernel kernel;
//...
public void Release(object instance)
{
this.kernel.Release(instance);
}
}