Blazor application logging with Serilog - asp.net-core

In a blazor application I have start-up logging configured in main as follows;
public static void Main(string[] args)
{
var assembly = Assembly.GetExecutingAssembly().GetName();
var appInsightsTelemetryConfiguration = TelemetryConfiguration.CreateDefault();
appInsightsTelemetryConfiguration.InstrumentationKey = "aa11aa11aa-a1a1-a1-aa-a111-aa11";
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Override("Microsoft.AspNetCore", Serilog.Events.LogEventLevel.Warning)
.Enrich.FromLogContext()
.Enrich.WithProperty("Application", $"{assembly.Name}")
.WriteTo.Console()
.WriteTo.Seq(serverUrl: "http://myseqserver.inthecloud.azurecontainer.io:5341/")
.WriteTo.ApplicationInsights(appInsightsTelemetryConfiguration, TelemetryConverter.Traces)
.CreateLogger();
try
{
Log.Information(Constants.Logging.Messages.SERVICE_STARTED, assembly.Name);
var host = CreateHostBuilder(args).Build();
using (var serviceScope = host.Services.CreateScope())
{
var context = serviceScope.ServiceProvider.GetRequiredService<MyDbContext>();
context.Database.Migrate(); // apply outstanding migrations automatically
}
host.Run();
return;
}
catch (Exception ex)
{
Log.Fatal(ex, Constants.Logging.Messages.SERVICE_STARTED, assembly.Name);
return;
}
finally
{
// make sure all batched messages are written.
Log.CloseAndFlush();
}
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.UseSerilog()
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
and request logging in StartUp Configure;
public void ConfigureServices(IServiceCollection services)
{
//...
services.AddApplicationInsightsTelemetry("aa11aa11aa-a1a1-a1-aa-a111-aa11");
//...
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
/// ...
// Add this line; you'll need `using Serilog;` up the top, too
app.UseSerilogRequestLogging();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapBlazorHub();
endpoints.MapFallbackToPage("/_Host");
});
}
On my blazor pages I cannot get this working;
#inject ILogger<ThisRazorPage> Logger
#code{
protected override void OnInitialized()
{
Logger.LogTrace("Log something. Please!");
}
}
But this does work;
#inject ILoggerFactory LoggerFactory
#code{
protected override void OnInitialized()
{
var logger = LoggerFactory.CreateLogger<Incident>();
logger.LogTrace("Log something. Please!");
}
}
According to this the .UseSerilog() method adds the DI for ILoggerFactory. Is there a way for me to do something similar for ILogger<T> within the DI framework so that ILogger<T> can be used, rather than having to explicitly create a LoggerFactory on every page.

Static method works for me.
#using Serilog
#code {
Log.Information("Log something. Please!");
}

You need:
add Serilog.Extensions.Logging
add in your Main()
public static async Task Main(string[] args)
{
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
// ...
builder.Services.AddTransient<AccountsViewModel>();
var levelSwitch = new LoggingLevelSwitch();
Log.Logger = new LoggerConfiguration()
.MinimumLevel.ControlledBy(levelSwitch)
.Enrich.WithProperty("InstanceId", Guid.NewGuid().ToString("n"))
.CreateLogger();
// ------------- this is what you'r looking for
builder.Logging.AddSerilog();
// ...
await builder.Build().RunAsync();
}
and in your ViewModel
public class AccountsViewModel
{
private readonly ILogger<AccountsViewModel> Logger;
private readonly HttpClient Http;
public AccountsViewModel(
ILogger<AccountsViewModel> logger,
HttpClient http
)
{
Http = http;
Logger = logger;
Logger.LogInformation("AccountsViewModel()");
}
}
or in your razor-page:
#inject ILogger<ThisRazorPage> logger;

Related

.NET Core API Endpoint gives 404 only in deployed version

I am building a .NET Core (3.1) Web API which is being hosted in IIS.
I have 2 endpoints:
/api/status
/api/widget/config/{id}
Both endpoints work perfectly when running locally. The /api/status endpoint works in my deployed version too. But the other endpoint gives a 404 error in the deployed version. As it works locally, I believe this to be an issue with how it is deployed. Please can you help me understand the issue?
Here are my 2 controllers code:
[Route("api/[controller]")]
[ApiController]
public class StatusController : ControllerBase
{
[HttpGet]
public ActionResult Get()
{
return Ok("API is available");
}
}
and
[Route("api/[controller]")]
[ApiController]
public class WidgetController : ControllerBase
{
private readonly IWidgetService service;
public WidgetController(IWidgetService _service)
{
service = _service;
}
[HttpGet]
[Route("~/api/[controller]/[action]/{id}")]
public ActionResult Config(Guid id)
{
return Ok(service.GetWidgetConfig(id));
}
}
and below is my Program.cs and Startup.cs:
public static void Main(string[] args)
{
var host = CreateHostBuilder(args).Build();
using (var scope = host.Services.CreateScope())
{
var services = scope.ServiceProvider;
try
{
SeedDatabase.Initialize(services);
}
catch (Exception ex)
{
var logger = services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "An error occured seeding the DB");
}
}
host.Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseKestrel();
webBuilder.UseContentRoot(Directory.GetCurrentDirectory());
webBuilder.UseIIS();
webBuilder.UseStartup<Startup>();
});
and
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(opts => opts.UseSqlServer(Configuration.GetConnectionString("sqlConnection"),
options => options.MigrationsAssembly("MyProject")));
services.AddIdentity<ApplicationUser, IdentityRole>(opt =>
{
opt.Password.RequiredLength = 8;
opt.Password.RequireDigit = true;
opt.Password.RequireUppercase = true;
opt.Password.RequireNonAlphanumeric = true;
opt.SignIn.RequireConfirmedAccount = false;
opt.SignIn.RequireConfirmedAccount = false;
opt.SignIn.RequireConfirmedPhoneNumber = false;
}).AddEntityFrameworkStores<ApplicationDbContext>();
services.AddScoped<IWidgetService, WidgetService>();
services.AddCors(o => o.AddPolicy("CorsPolicy", builder => {
builder
.WithMethods("GET", "POST")
.AllowAnyHeader()
.AllowAnyOrigin();
}));
services.AddMvc()
.AddNewtonsoftJson(options => options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore);
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
// 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.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseCors("CorsPolicy");
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
}
Change your controller code to this:
[Route("api/[controller]")]
[ApiController]
public class WidgetController : ControllerBase
{
private readonly IWidgetService service;
public WidgetController(IWidgetService _service)
{
service = _service;
}
[HttpGet("Config/{id}")]
public ActionResult Config(Guid id)
{
return Ok(service.GetWidgetConfig(id));
}
}
Change your code like below:-
Startup.cs
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
Controller:-
[ApiController]
[Route("api/[controller]")]
public class WidgetController : ControllerBase
{
private readonly IWidgetService service;
public WidgetController(IWidgetService _service)
{
service = _service;
}
[HttpGet("Config/{id}")]
public ActionResult Config(Guid id)
{
return Ok(service.GetWidgetConfig(id));
}
}
Also try your write connection string in appsettings.Development.json file.
It will resolve your issue.

Quartz - .NET Core - Null Reference Exception

I'm trying to implement the Quartz for .NET Core.
At the moment I have the following:
public class Startup
{
**private IScheduler _scheduler { get; set; }//quartz**
public Startup(IConfiguration configuration)
{
Configuration = configuration;
**_scheduler = ConfigureQuartz();//quartz**
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<Context>(options =>
{
options.UseSqlServer(Configuration.GetConnectionString("notebook14"));
});
...
...
...
**#region for Quartz DI
services.AddScoped<Job>();
services.AddSingleton(provider => _scheduler);
#endregion**
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapRazorPages();
endpoints.MapControllerRoute(
name: "default",
pattern: "{area:exists}/{controller=Home}/{action=Index}/{id?}");
});
}
**#region for Quartz
private IScheduler ConfigureQuartz()
{
NameValueCollection properties = new NameValueCollection()
{
{"quartz.serializer.type","binary"}
};
StdSchedulerFactory factory = new StdSchedulerFactory(properties);
_scheduler = factory.GetScheduler().Result;
_scheduler.Start();
return _scheduler;
}
#endregion**
...and Program.cs because I would like to start a schedule at first deploy and then run the task every day:
public class Program
{
public static void Main(string[] args)
{
var host = CreateHostBuilder(args).Build();
using (var scope = host.Services.CreateScope())
{
var services = scope.ServiceProvider;
UserManager<AppUser> userManager = services.GetRequiredService<UserManager<AppUser>>();
RoleManager<AppRole> roleManager = services.GetRequiredService<RoleManager<AppRole>>();
**IScheduler scheduler = services.GetRequiredService<IScheduler>();
Start.StartSchedule(scheduler);**
Init.AssignAdmin(userManager, roleManager).Wait();
}
host.Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
...and Start.cs, where I have created the job and the trigger :
public class Start
{
public static Context _context { get; set; }
public Start(Context context)
{
_context = context;
}
public async static void StartSchedule(IScheduler _scheduler)
{
IJobDetail job = JobBuilder.Create<Job>().WithIdentity("Generate", "FreeSlots").Build();
ITrigger trigger = TriggerBuilder.Create().WithIdentity("Trigger", "FreeSlots").StartNow()
.WithDailyTimeIntervalSchedule(t=>t.StartingDailyAt(new TimeOfDay(10,59)))
.Build();
await _scheduler.ScheduleJob(job, trigger);
}
}
...and finally, the Job itself:
public class Job : IJob
{
public Task Execute(IJobExecutionContext context)
{
try
{
Console.WriteLine("TRIGGERED!");
List<AppUser> doctors = Start._context.Users.Where(x => x.Type == AppUser.UserType.Doctor).ToList();
DateTime First = new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day, 8, 0, 0);
List<Appointments> appointments = new List<Appointments>()
{
new Appointments(){Date=First, Time=First,Status=Appointments.SlotStatus.Free},
new Appointments(){Date=First, Time=First.AddHours(1),Status=Appointments.SlotStatus.Free},
new Appointments(){Date=First, Time=First.AddHours(2),Status=Appointments.SlotStatus.Free},
new Appointments(){Date=First, Time=First.AddHours(3),Status=Appointments.SlotStatus.Free},
new Appointments(){Date=First, Time=First.AddHours(4),Status=Appointments.SlotStatus.Free},
new Appointments(){Date=First, Time=First.AddHours(5),Status=Appointments.SlotStatus.Free},
new Appointments(){Date=First, Time=First.AddHours(6),Status=Appointments.SlotStatus.Free},
new Appointments(){Date=First, Time=First.AddHours(7),Status=Appointments.SlotStatus.Free},
new Appointments(){Date=First, Time=First.AddHours(8),Status=Appointments.SlotStatus.Free},
};
foreach (AppUser doc in doctors)
{
foreach (Appointments slot in appointments)
{
slot.DoctorId = doc.Id;
Start._context.Appointments.Add(slot);
Start._context.SaveChanges();
var message = "Slot for " + slot.DoctorId.ToString() + " " + slot.Time + " " + slot.Status;
Console.WriteLine(message);
}
}
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
return Task.CompletedTask;
}
}
The problem is that I get a null reference exception when the Job is executed:
Does anybody have any ideas? Is there a way to make this work? Thank you in advance!

I can't access index.html in wwwroot when I use windows service to run my site

I can't access my static files in wwwroot when I use the windows service to run my site, but it works when I use IIS Express or IIS.
I build The project using Asp.Net Core 2.2.
The actions in the controllers are ok, just the static files cannot be accessed.
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.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)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
var df = new DefaultFilesOptions();
df.DefaultFileNames.Add("Index.html");
app.UseDefaultFiles(df);
app.UseStaticFiles();
app.UseMvc();
}
}
public class Program
{
public static void Main(string[] args)
{
var isService = !(Debugger.IsAttached || args.Contains("--console"));
if (isService)
{
var pathToExe = Process.GetCurrentProcess().MainModule.FileName;
var pathToContentRoot = Path.GetDirectoryName(pathToExe);
Directory.SetCurrentDirectory(pathToContentRoot);
var host = CreateWebHostBuilder(args.Where(arg => arg != "--console").ToArray()).Build();
host.RunAsService();
}
else
{
WebHost.CreateDefaultBuilder(args).UseStartup<Startup>().Build().Run();
}
}
static int Port = 9099;
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseKestrel(options => { options.Listen(IPAddress.Any, Port); })
.UseStartup<Startup>();
}
It is because you are setting the directory when running the application as a windows-service.
Instead of doing this
var pathToExe = Process.GetCurrentProcess().MainModule.FileName;
var pathToContentRoot = Path.GetDirectoryName(pathToExe);
Directory.SetCurrentDirectory(pathToContentRoot);
Adjust your Webhost-Build definition
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseKestrel(options => { options.Listen(IPAddress.Any, Port); })
.UseStartup<Startup>()
.UseContentRoot(AppContext.BaseDirectory); // add this line
And then in the Startup class add static-file options
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
var df = new DefaultFilesOptions();
// these options are not necessary index.html is added by default
df.DefaultFileNames.Add("Index.html");
app.UseDefaultFiles(df);
app.UseStaticFiles(new StaticFileOptions
{
FileProvider = env.WebRootFileProvider
});
app.UseMvc();
}
Also make sure that your index.html is always copied to the output directory.
Either add this to your csproj-file
<Content Update="wwwroot\index.html">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
or in visual-studio right click on it > properties > Copy to Output Directory > Copy always

How do I log in Program.Main in .net core 2.2?

Previously I could use LoggerFactory but now it is marked as obsolete.
How can I access ILoggingBuilder and create logger instance at the beginning of Mainmethod? I want to use builtin logger not any 3rd party logging library.
To get what you want, you have to edit CreateWebHostBuilder method:
public static IWebHost CreateWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.ConfigureLogging(logging =>
{
logging.ClearProviders();
logging.AddConsole(); // Or any other provider
}).Build();
Now this method returns the host with configured logging. Now you can get you ILogger-Service from the services of your host:
public static void Main(string[] args)
{
var host = CreateWebHost(args);
try
{
var logger = host.Services.GetRequiredService<ILogger<Program>>();
logger.LogInformation("Called from Main");
host.Run();
}
catch (Exception)
{
// do something
}
}
To use Logging in Startup you can still use ILoggerFactory:
private ILoggerFactory _loggerFactory;
public Startup(IConfiguration configuration, ILoggerFactory loggerFactory)
{
Configuration = configuration;
_loggerFactory = loggerFactory;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
try
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}
catch (Exception ex)
{
var logger = _loggerFactory.CreateLogger("Startup");
// log
throw;
}
}
Here are additional ressources for logging and app startup in asp.net core 2.2.

Asp.net core get logger in ConfigureAppConfiguration

I would like to use logger in "TODO" line, because I need to log when I add a "AddJsonConfigurationSourceDecryption" method, Is there any way to get a logger?
Logging in ASP.NET Core
public class Program
{
public static void Main(string[] args)
{
ILogger<Program> logger = null;
try
{
var host = CreateWebHostBuilder(args).Build();
ApplicationLogging.LoggerFactory = host.Services.GetRequiredService<ILoggerFactory>();
logger = host.Services.GetRequiredService<ILogger<Program>>();
logger.LogInformation("start thin API service...");
host.Run();
}
catch (Exception ex)
{
logger?.LogError(ex, "Stopped program because of exception");
throw;
}
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args)
{
return WebHost.CreateDefaultBuilder(args)
.UseKestrel(option => option.AddServerHeader = false)
.ConfigureLogging((host, builder) => { builder.SetMinimumLevel(LogLevel.Trace); })
.UseNLog()
.ConfigureServices(services => services.AddAutofac())
.ConfigureAppConfiguration((context, builder) =>
{
//TODO: Logger.............
builder.SetBasePath(Directory.GetCurrentDirectory());
builder.AddJsonConfigurationSourceDecryption();
})
.UseStartup<Startup>();
}
}
#0xced answered this question here
How to create a LoggerFactory with a ConsoleLoggerProvider?
as of dotnetcore3.1 the code could be written this way
private static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args).ConfigureAppConfiguration(
(hostContext, config) =>
{
var loggerFactory = LoggerFactory.Create(
builder =>
{
builder
.AddFilter("Microsoft", LogLevel.Warning)
.AddFilter("System", LogLevel.Warning)
.AddFilter("YourProgramNamespsace.Program", LogLevel.Debug)
.AddConsole();
});
var logger = loggerFactory.CreateLogger<Program>();
logger.LogInformation("Hello from ConfigureAppConfiguration");
...
})