I have an issue where some logging calls are not reaching App Insights, but others are
I added a controller to test this
public class LogController : ControllerBase
{
private readonly ILogger<BankingController> _logger;
private readonly IServiceProvider _services;
public LogController(ILogger<LogController> logger,
IServiceProvider services)
{
_logger = logger;
_services = services;
}
[HttpGet("test-logging")]
public async Task TestLogging()
{
_logger.Log(LogLevel.Information, "Test Logging controller - information manual");
_logger.LogTrace("Test Logging controller - trace");
_logger.LogInformation("Test Logging controller - information");
_logger.LogWarning("Test Logging controller - warning");
_logger.LogError("Test Logging controller - error");
_logger.LogCritical("Test Logging controller - critical");
var logger = _services.GetRequiredService<ILogger<LoggingController>>();
logger.Log(LogLevel.Information, "DIRECT Test Logging controller - information manual");
logger.LogTrace("DIRECT Test Logging controller - trace");
logger.LogInformation("DIRECT Test Logging controller - information");
logger.LogWarning("DIRECT Test Logging controller - warning");
logger.LogError("DIRECT Test Logging controller - error");
logger.LogCritical("DIRECT Test Logging controller - critical");
}
}
None of the logging calls here reach App Insights
However, in another part of the API, in program.cs we have a logging call that does reach App Insights
public static class IWebHostExtensions
{
public static IWebHost MigrateDbContext<TContext>(this IWebHost webHost, Action<TContext,IServiceProvider> seeder) where TContext : DbContext
{
using (var scope = webHost.Services.CreateScope())
{
var services = scope.ServiceProvider;
var logger = services.GetRequiredService<ILogger<TContext>>();
var context = services.GetService<TContext>();
try
{
logger.LogInformation($"Migrating database associated with context {typeof(TContext).Name}");
var retry = Policy.Handle<SqlException>()
.WaitAndRetry(new TimeSpan[]
{
TimeSpan.FromSeconds(3),
TimeSpan.FromSeconds(5),
TimeSpan.FromSeconds(8),
});
retry.Execute(() =>
{
//if the sql server container is not created on run docker compose this
//migration can't fail for network related exception. The retry options for DbContext only
//apply to transient exceptions.
context.Database.Migrate();
});
logger.LogInformation($"Migrated database associated with context {typeof(TContext).Name}");
}
catch (Exception ex)
{
logger.LogError(ex, $"An error occurred while migrating the database used on context {typeof(TContext).Name}");
}
}
return webHost;
}
}
Has anyone ever come across this?
I tried a totally fresh App Insights instance
As you can see, the method used for logger uses ServiceProvider like the approach that works for migrations
I really need the normal ILogger convention to work reliably
Im using .NET Core 3.1
In my startup.cs I have
services.AddApplicationInsightsTelemetry();
I have tried changing the logging levels in my appsettings and still my custom messages are not being logged
"Logging": {
"ApplicationInsights": {
"LogLevel": {
"Default": "Trace",
"Microsoft": "Trace",
"System": "Trace"
}
},
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"System": "Warning"
}
},
Paul
Have you tried using the right type for ILogger? In your code I can see you're using BankingController as the Type but on a different class. Try this:
from:
public class LogController : ControllerBase
{
private readonly ILogger<BankingController> _logger;
to:
public class LogController : ControllerBase
{
private readonly ILogger<LogController > _logger;
Related
Things to clarify first is that I use NLog/Serilog/etc for neat configuration, across all applications I only use Microsoft.* namespce logging interfaces.
Here is example of my Site startup class, which just reference host builder form real app, so it is complete integration friendly class which I can use in SetUp and TearDown in NUnit:
public sealed class Site
{
private IHost _host;
public readonly string BaseAddress;
public Site(string baseAddress)
{
BaseAddress = baseAddress;
}
public async Task StartAsync()
{
if (_host != null)
throw new InvalidOperationException("Already started.");
var cfgBuilder = new ConfigurationBuilder();
var args = string.Format("--urls {0}", BaseAddress);
cfgBuilder.AddCommandLine(args.Split(' ', System.StringSplitOptions.RemoveEmptyEntries));
cfgBuilder.AddJsonFile("appsettings.json");
cfgBuilder.AddEnvironmentVariables();
using (var cts = new CancellationTokenSource())
{
cts.CancelAfter(5000);
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Debug()
.WriteTo.Console()
.CreateLogger();
_host = Performance.Program.CreateHostBuilder(cfgBuilder.Build()).UseSerilog(Log.Logger).Build();
await _host.StartAsync(cts.Token).ConfigureAwait(false);
}
}
public async Task StopAsync()
{
if (_host == null)
return;
using (var cts = new CancellationTokenSource())
{
cts.CancelAfter(5000);
await _host.StopAsync(cts.Token).ConfigureAwait(false);
}
}
}
My appsettings..json* pretty much default:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*"
}
But here is the problem. Microsoft ILogger work inside IHostedService or Startup instances, BUT it stop working entirely in any controller/middleware:
public class ErrorController : ControllerBase
{
private readonly ILogger<ErrorController> _logger;
public ErrorController(ILogger<ErrorController> logger)
{
_logger = logger;
}
[HttpGet]
[Route("error")]
public async Task<IActionResult> OnError()
{
_logger.LogError("This one won't ever show up in test library except if you stop using serilog/nlog/whatever.");
return BadRequest();
}
}
If I start my app normally through Program.cs rather than from TestExplorer - everything fine and logging. So here is table of error replication with same configuration on different loggers I used:
Framework |NUnit.Startup |NUnit.Controller |Program.Startup |Program.Controller
___________________________________________________________________________________
NLog |good |bad |good |good
Serilog |good |bad |good |good
Microsoft logger |good |good |good |good
So, as you see, Microsoft logger works everywhere and project is pretty much clean default generation in VS. I tried completely clearing providers, various combination of extension, etc. What am I missing?
Turns out this some strange behavior of..... Console+ IHost and thread in which its running! Yes, you didn't misread. Things happens and for some reason Console will not work in thread of your IHost class after it starts - it will be back once you call StopAsync though. It will be nice if someone would explain this behavior further, I don't have decompiler at the moment in machine.
Solution is just to enable background processing of your logs, so they will be logged to Console but in some other thread.
For example in Serilog:
var logger = new LoggerConfiguration()
.MinimumLevel.Debug()
.WriteTo.Async(a=>
{
a.Console();
})
.CreateLogger();
In NLog you could use their async wrapper, but I didn't tested that.
Microsoft probably knew this behavior and their logging.AddConsole() extension works through background process, so this is the reason why their logger worked and others didn't.
Essentially, there are two different ways I get ILogger instances. One works perfectly fine, the other doesn't.
I have an Azure Function like this:
class AzureFunctionClass {
private readonly ISomeClass _someclass;
public AzureFunctionClass(ISomeClass someClass){
_someclass = someClass;
}
public Task<IActionResult> AzureFunction(ILogger log){
log.LogInformation("This works, I see this message when run");
_someclass.ExecuteMethod();
}
}
Another class, not containing Azure functions, like this:
class SomeClass : ISomeClass {
private readonly ILogger<SomeClass> _log;
public SomeClass(ILogger log){
_log = log;
}
public void ExecuteMethod(){
_log.LogInformation("This doesn't crash so _log isn't null, but it
doesn't write anything");
}
}
Startup.cs:
class Startup : IWebJobsStartup {
public void Configure(IWebJobsBuilder builder){
builder.Services.AddScoped<ISomeClass, SomeClass>();
builder.Services.AddTransient(typeof(ILogger<>), typeof(Logger<>));
builder.Services.AddScoped<ILogger<SomeClass>, Logger<SomeClass>>();
}
}
And no, I'm afraid that AzureFunctionClass cannot just pass its ILogger instance to ISomeClass as a parameter.
I've also looked everywhere for log files, such as in Azure Storage Explorer, to see if it's possibly just not writing to the Azure Portal console. Every log file I found had logs for the working case described above, and none of them had logs for the other case.
Current syntax shown has some issues with this injected dependencies.
class SomeClass : ISomeClass {
private readonly ILogger _log;
public SomeClass(ILogger<SomeClass> log) {
_log = log;
}
public void ExecuteMethod() {
_log.LogInformation("This doesn't crash so _log isn't null, but it doesn't write anything");
}
}
Second issue is that logging is added by default and your manually added settings are overriding the default setup.
class Startup : FunctionsStartup {
public override void Configure(IFunctionsHostBuilder builder) {
builder.Services.AddScoped<AzureFunctionClass>();
builder.Services.AddScoped<ISomeClass, SomeClass>();
//...
}
}
Technically all you needed to add was your function class and its dependencies.
Reference Use dependency injection in .NET Azure Functions
At present, the function runtime has a bug due to which it filters out any log that is created with a category that doesn't start with string Function..
See these GitHub issues:
#4425 - ILogger is not injected when using new DI functionality
#4345 - Remove filters for ILoggers created by customer DI
The logger injected in the function method is done by the function runtime which creates the logger with category set to Function.<FunctionName>.User. So this gets logged properly. But the logger that is injected into the constructor is done by the asp.net core DI framework, which sets the category name for the logger as Type.FullName (type in your example case is SomeClass). Because it's fullname doesn't start with Function, the lines logged with this category are filtered out.
There are two ways to workaround this.
Option 1: Change host.json to not filter logs from your namespace
{
"version": "2.0",
"logging": {
"logLevel": {
"<YourNameSpace>": "Information"
}
}
}
Option 2: Inject ILoggerFactory in your ctor, and create a logger with a category that won't get filtered
class SomeClass : ISomeClass {
private readonly ILogger _log;
public SomeClass(ILoggerFactory loggerFactory){ // Note that we inject ILoggerFactory
this._log = loggerFactory.CreateLogger(
LogCategories.CreateFunctionUserCategory(this.GetType().FullName)); // Must use CreateFunctionUserCategory to create the log category name otherwise the log gets filtered out.
}
public void ExecuteMethod(){
_log.LogInformation("This should get logged correctly.");
}
}
Note that, ILogger is already registered into DI framework by the function runtime (as mentioned in NKosi's answer), so those lines can be removed.
I have Serilog configured to write to both an MSSQL table and to a File (to try and determine whats happening.) on a WebApi with ASP.NET core (running Core 2.1).
I add the Serilog logging in my startup.cs as follows;
public async void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
var log = new LoggerConfiguration()
.MinimumLevel.Verbose()
.WriteTo.MSSqlServer(
DbGlobals.DevDatabase, "Logs", schemaName:DbGlobals.SchemaName)
.WriteTo.File("Logs\\Serilog\\log.txt", rollingInterval: RollingInterval.Day)
.CreateLogger();
loggerFactory.AddSerilog(log);
where DbGlobals.DevDatbase and Dbglobals.SchemaName are string read from the configuration.
UPDATE For reference these values are;
DbGlobals.Schema = "MySchema";
DbGlobals.DevDatabase = "Data Source=xx.xx.xx.xx;Initial Catalog=DevDatabase;Integrated Security=False;Persist Security Info=False;User ID=LOGIN;Password=PASSWORD;MultipleActiveResultSets=true;";
When running locally with IIS Express and under both debug and release configurations, both logging functions are working correctly. So for example in my controller;
public class ReviewPublicController : Controller
{
private readonly IReviewService _service;
private readonly IMapper _mapper;
private readonly IMailService _mailService;
private readonly ILogger _logger;
public ReviewController(
IReviewService service,
ILogger<ReviewController> logger)
{
_service = service;
_logger = logger;
}
[HttpPost]
[ProducesResponseType(typeof(Review), 201)]
[Route("")]
public async Task<IActionResult> Post([FromBody]ReviewPostModel model)
{
_logger.LogDebug("Posting Review");
...
return Ok();
}
So locally "Posting Review" is written to both my File and DbSchema.
However, when I publish to my server, the above "Posting Review" entry is not written to either.
Both appear to be working as information entries are being added from EF and routing etc but not when I specifically try to write an entry?
Can anyone direct me here as to what the issue could be please?
The current nuget versions are;
Serilog 2.7.1
Serilog.AspNetCore 2.1.1
Serilog.Sinks.MSSqlServer 5.1.2
Serilog.Sinks.File 4.0.0
Also, I have the following in my appsettings.json;
"Logging": {
"IncludeScopes": false,
"Debug": {
"LogLevel": {
"Default": "Warning"
}
},
"Release": {
"LogLevel": {
"Default": "Warning"
}
},
"Console": {
"LogLevel": {
"Default": "Warning"
}
}
update
As in the docs:
Default Level - if no MinimumLevel is specified, then Information level events and higher will be processed.
So basically, it seems that your configuration is either invalid or overwritten. see: https://github.com/serilog/serilog/wiki/Configuration-Basics
LogDebug is disabled by default in a Release build, which is probably what your publish is.
Please try with
_logger.LogInformation("Posting Review");
For me what was the solution is to remove the "restrictedToMinimumLevel": "Information" from appsettings.json.
It was exactly the same in appsettings.Development.json and it was working well in Development environment.
For me, I have configured all above. It logs db in my local environment, where as it does not work in dev environment. The issue is due to authentication. I am using azure ad authentication for my application that restricts serilog to write into server. So, In my program.cs file, I have added the below settings.
Program.cs
var sqlSinkOptions = new MSSqlServerSinkOptions
{
TableName = "MyLogTableName",
UseAzureManagedIdentity = true, // This line
AzureServiceTokenProviderResource = "https://database.windows.net/" // This line
};
After that, I can see logging is happening in dev environment too. May be this helps some one. Happy coding!
So in an ASPNETCORE 2.0 project, I added a logging provider (serilog) to the logging factory along with a console sink. It works great, however I noticed that all the framework request pipelines (like http request response) are logging every little detail as INFO.
[17:37:26 INF] Request starting HTTP/1.1 GET http://localhost:5000/test/health
[17:37:26 INF] Executing action method DAS.Gateways.Command.Api.Controllers.TestController.HealthCheck (DAS.Gateways.Command.Api) with arguments (null) - ModelState is Valid
[17:37:26 INF] Health check called.
[17:37:27 INF] Executing ObjectResult, writing value Microsoft.AspNetCore.Mvc.ControllerContext.
[17:37:27 INF] Executed action DAS.Gateways.Command.Api.Controllers.TestController.HealthCheck (DAS.Gateways.Command.Api) in 203.8825ms
[17:37:27 INF] Request finished in 343.9801ms 200 application/json; charset=utf-8
[17:38:07 INF] Request starting HTTP/1.1 GET http://localhost:5000/test/health
[17:38:07 INF] Executing action method DAS.Gateways.Command.Api.Controllers.TestController.HealthCheck (DAS.Gateways.Command.Api) with arguments (null) - ModelState is Valid
[17:38:07 INF] Health check called.
[17:38:07 INF] Executing ObjectResult, writing value Microsoft.AspNetCore.Mvc.ControllerContext.
[17:38:07 INF] Executed action DAS.Gateways.Command.Api.Controllers.TestController.HealthCheck (DAS.Gateways.Command.Api) in 53.5876ms
[17:38:07 INF] Request finished in 60.2195ms 200 application/json; charset=utf-8
Info is the minimum log level we use for production logging and we have a Loggly sink that has a 1GB/day limit so I feel like MVC logging all request information as INFO is a bit obnoxious and I'd like to lower it to DEBUG. To be clear, I don't want to raise my logging level to prevent INFO from reaching the sink, I want .Net to lower it's logging level from INFO to DEBUG.
Is this possible and how?
You need to use log filtering.
You can specify a minimum log level for a specific provider and category or for all providers or all categories. Any logs below the minimum level aren't passed to that provider, so they don't get displayed or stored.
Filter rules may be defined via configuration or in code. Example of how it may look in code:
// using Serilog.Extensions.Logging;
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.ConfigureLogging(logging =>
{
logging.AddFilter<SerilogLoggerProvider>("Microsoft", LogLevel.Warning);
}
.UseStartup<Startup>()
.Build();
Note, that AddFilter in this case applies only to Serilog log provider as we specify a provider type. If you want to define a filter for all providers, use:
logging.AddFilter("Microsoft", LogLevel.Warning);
One possible solution is to create your own ILoggerProvider and ILogger implementations that act as a shim for the underlying provider and then have your ILogger.Log() method make the actual decision to log based on the logger's category name and then modify the log level.
In my case, I assumed that all ASP.NET Core applications have logger category names that start with "Microsoft.AspNetCore" and then I change the log level from Information to Debug for these events, something like:
public class MyLogger : ILogger
{
private string categoryName;
private ILogger baseLogger;
public MyLogger(string categoryName, ILogger baseLogger)
{
this.categoryName = categoryName;
this.baseLogger = baseLogger;
}
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{
if (logLevel == LogLevel.Information
&& categoryName.StartsWith("Microsoft.AspNetCore"))
{
logLevel = LogLevel.Debug;
}
baseLogger.Log(logLevel, eventId, state, exception, formatter);
}
}
This is a bit of a hack but it effectively converts ASP.NET events logged as INFORMATION to DEBUG events, so they won't fill up your logs when you don't need them, but you then you can always set your log level to DEBUG to show them.
UPDATE:
The Startup.cs file below demonstrates how to wire this up:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Logging.Console;
using Microsoft.Extensions.Logging.Debug;
namespace WebApplication1
{
public class LoggerProviderShim : ILoggerProvider
{
private ILoggerProvider baseLoggerProvider;
public LoggerProviderShim(ILoggerProvider baseLoggerProvider)
{
this.baseLoggerProvider = baseLoggerProvider;
}
public ILogger CreateLogger(string categoryName)
{
return new LoggerShim(baseLoggerProvider.CreateLogger(categoryName), categoryName);
}
public void Dispose()
{
baseLoggerProvider.Dispose();
}
}
public class LoggerShim : ILogger
{
private ILogger baseLogger;
private bool treatInfoAsDebug;
public LoggerShim(ILogger baseLogger, string categoryName)
{
this.baseLogger = baseLogger;
this.treatInfoAsDebug = categoryName != null && categoryName.StartsWith("Microsoft.AspNetCore.");
}
public IDisposable BeginScope<TState>(TState state)
{
return baseLogger.BeginScope(state);
}
public bool IsEnabled(LogLevel logLevel)
{
return baseLogger.IsEnabled(logLevel);
}
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{
if (treatInfoAsDebug && logLevel == LogLevel.Information)
{
logLevel = LogLevel.Debug;
}
baseLogger.Log(logLevel, eventId, state, exception, formatter);
}
}
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
var debugLogProvider = new DebugLoggerProvider();
loggerFactory.AddProvider(debugLogProvider);
//loggerFactory.AddProvider(new LoggerProviderShim(debugLogProvider));
app.UseMvc();
}
}
}
I've added the LoggerProviderShim and LoggerShim classes at the top of the file. LoggerProviderShim is intended to wrap a specific logger provider implementation. For this example I'm wrapping a DebugLoggerProvider. LoggerShim is used the wrap loggers returned by the wrapped (aka real) logger provider.
The idea here is that instead of adding real ILoggerProvider to the logger factory in Startup.Configure(), you'll wrap the real provider in a LoggerProviderShim and then add the shim to the logger factory.
LoggerProviderShim works by obtaining a real ILogger from the real logger provider and then wrapping that in a LoggerShim and then returning the shim. The LoggerShim constructor examines the created logger category name to decide whether it should log INFORMATION events as DEBUG. In this case, we're doing this for events that look like they're coming from an ASP.NET CORE component by looking for logger category names that come from ASP (e.g. that are prefixed by "Microsoft.AspNetCore."
The logger shim's Log() method then changes INFORMATION loglevel to DEBUG for these loggers before calling the underlying real logger.
You can see this in action by including this code in a simple WebAPI project:
Make sure the log levels in appsettings.json are set to Debug.
Run the application and hit the controller endpoint a few times.
In the Visual Studio DEBUG output panel, note the ASP.NET events logged as INFORMATION.
Stop the app and in the Startup.Configure() method, comment out the first AddProvider() call and uncomment the second one and test the application again.
Now note that the ASP.NET events are logged as DEBUG. The shims transformed the event level.
You'll need to specifically wrap any of the logging providers you care about. In this example, I wrapped the DebugLogProvider. You'd need to do the same for the Application Insights or any others separately (did I already mention that this is a bit of a hack :)
All the information level logs about http request and response can be turned off by setting the "MinimumLevel" in LoggerConfiguration for "Microsoft" as those logs are from "Microsoft.AspNetCore" . This can be done in code as below
Log.Logger = new LoggerConfiguration()
.ReadFrom.Configuration(Configuration)
.MinimumLevel.Override("Microsoft",LogEventLevel.Warning)
.CreateLogger();
This can also be done through configuration in appsettings.json file as below
"Serilog": {
"MinimumLevel": {
"Default": "Debug",
"Override": {
"Microsoft": "Warning"
}
},
"WriteTo": [
{
"Name": "Async",
"Args": {
"configure": [
{
"Name": "Console",
"Args": {
"outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}"
}
},
{
"Name": "File",
"Args": {
"path": "Logs/log.txt",
"outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}",
//"fileSizeLimitBytes": "1073741824",
//"buffered": false,
//"shared": false,
"rollingInterval": "Minute"
//"rollOnFileSizeLimit": false,
//"retainedFileCountLimit": 31
}
}
]
}
}
]
}
How am I supposed to inject my MyDbContext into my database service layer MyService?
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<MyDbContext>(options =>
options.UseSqlServer(Configuration["ConnectionStrings:DefaultConnection"]));
services.AddMvc();
}
MyDbContext.cs
public partial class MyDbContext : DbContext
{
public virtual DbSet<User> User { get; set; }
public MyDbContext(DbContextOptions<MyDbContext> options)
:base(options)
{
}
}
appsettings.json
{
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Debug",
"System": "Information",
"Microsoft": "Information"
}
},
"ConnectionStrings": {
"DefaultConnection": "Server=MyServer;Database=MyDatabase;user id=MyUser;password=MyPassword;"
}
}
MyService.cs
public class MyService
{
public User GetUser(string username)
{
// Should i remove this call and get the reference from the injection somehow?
MyDbContext db_context = new MyDbContext(optionsBuilder.Options);
using (db_context)
{
var users = from u in db_context.User where u.WindowsLogin == username select u;
if (users.Count() == 1)
{
return users.First();
}
return null;
}
}
}
In my GetUser method, I know I am supposed to be using my injected MyDbContext here but I am not quite sure how to fetch it. Which piece of the puzzle am I missing?
You don't have to include the dbcontext yourself, ASP.NET core dependency injection service will do this for you.
You have just to declare your services and your database context in your startup class, and put the dbcontext you need in your service's constructor :
Startup.cs (your have to choose the service lifetime you want, here it's a scoped service, once per request):
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<MyDbContext>(options =>
options.UseSqlServer(Configuration["ConnectionStrings:DefaultConnection"]));
services.AddMvc();
services.AddScoped<IMyService, MyService>();
}
Your service class:
public class MyService : IMyService
{
private readonly MyDbContext _context;
public MyService(MyDbContext ctx){
_context = ctx;
}
public User GetUser(string username)
{
var users = from u in _context.User where u.WindowsLogin == username select u;
if (users.Count() == 1)
{
return users.First();
}
return null;
}
}
public interface IMyService
{
User GetUser(string username);
}
In your controller, you have to declare the services (or the database context) you need to use in the same way :
public class TestController : Controller
{
private readonly IMyService _myService;
public TestController(IMyService serv)
{
_myService = serv;
}
public IActionResult Test()
{
return _myService.MyMethod(); // No need to instanciate your service here
}
}
Note about the controller : you don't have to add them in your startup class like you do with your database context or your services. Just implement their constructor.
If you need more information about the .NET Core depencency injection, the official documentation is clear and very complete : https://learn.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection
NB:
In the startup.cs, the AddScoped line is an option. Your can choose the lifetime you want for your service.
There is the different lifetimes you can choose :
Transient
Transient lifetime services are created each time they are requested.
This lifetime works best for lightweight, stateless services.
Scoped
Scoped lifetime services are created once per request.
Singleton
Singleton lifetime services are created the first time they are
requested (or when ConfigureServices is run if you specify an instance
there) and then every subsequent request will use the same instance.
Above taken from: https://learn.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection
Note: It's not the question here but your GetUser data query looks like a little strange to me. If your count()==1 goal is to check the unicity of the user, the good way is to add a unicity constraint in your database. If count()==1 goal is to check you have data to avoid a object null reference exception, you can use .FirstOrDefault(), it will manage this for you. You can simplify this method :
public User GetUser(string username) => (from u in _context.User where u.WindowsLogin == username select u).FirstOrDefault();