Adding console app project to a solution with .Net Core web api - asp.net-core

I added reference to the web api project. The added the following to Program.cs
class Program
{
static void Main(string[] args)
{
var builder = new ConfigurationBuilder();
BuildConfig(builder);
var host = Host.CreateDefaultBuilder()
.ConfigureServices((context, services) =>
{
services.AddDbContext<ModelContext>(
options => options.UseOracle(connectionString)); // dbcontext from web api project
services.AddTransient<IEmailReminderService, EmailReminderService>();
}).Build();
var svc = ActivatorUtilities.CreateInstance<EmailReminderService>();
var er = new EmailReminderService();
er.OpenIncidentReminder();
}
static void BuildConfig(IConfigurationBuilder builder)
{
builder.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT")}.json", optional: true)
.AddEnvironmentVariables();
_configuration = builder.Build();
}
}
I get System.NullReferenceException: 'Object reference not set to an instance of an object.'
_context was null.
Edit
public class EmailReminderService : IEmailReminderService
{
private ModelContext _context;
//private readonly IExceptionLogService _exceptionLogService;
public EmailReminderService(ModelContext con)
{
_context = con;
//_exceptionLogService = exceptionLogService;
}
public void OpenIncidentReminder()
{
var openIncidents = _context.Incidents.ToList();
}
}

#terodaktil is right about the configuration of the DbContext. You need to configure it properly to specify the provider. See Microsoft's docs about this topic.
var host = Host.CreateDefaultBuilder(args)
.ConfigureServices((context, services) => {
// here i'm using in-memory DB, you'd use SqlServer, Oracle, etc.
services.AddDbContext<AppDbContext>(o => o.UseInMemoryDatabase("db"));
services.AddTransient<IEmailReminderService, EmailReminderService>();
}).Build();
That said, you need to create a scope to resolve a scoped service like DbContext.
Then you can use the scope's service provider directly to resolve IEmailReminderService, or ActivatorUtilities.CreateInstance to create an instance of the concrete implementation EmailReminderService.
using var scope = host.Services.CreateScope();
// now you can resolve the dbcontext
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
// or any service that depends on a scoped service
var reminder = scope.ServiceProvider.GetRequiredService<IEmailReminderService>();
// var reminder = ActivatorUtilities.CreateInstance<EmailReminderService>(scope.ServiceProvider);
reminder.OpenIncidentReminder();
References:
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-5.0#call-services-from-main

Related

How do you resolve a per-request item using service location in ASP.NET Core 3.1 and Autofac?

I have used this snippet to setup my application:
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.UseServiceProviderFactory(new AutofacServiceProviderFactory())
.ConfigureContainer<ContainerBuilder>(Startup.Register)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
public static void Register(ContainerBuilder builder)
{
builder.RegisterType<UserService>().As<IUserServcice>().InstancePerLifetimeScope();
}
and I have utilized it in the manner mentioned below:
public interface IUserServcice
{
public long Tick { get; }
}
public class UserService : IUserServcice
{
private long _tick;
public UserService()
{
_tick = DateTime.Now.Ticks;
}
public long Tick => _tick;
}
public WeatherForecastController(IUserServcice userServcice)
{
// _logger = logger;
iUserServcice = userServcice;
var g = Startup.AutofacContainer.Resolve<IUserServcice>();
tick2 = g.Tick;
}
private async Task Get1()
{
var list = new List<long>();
list.Add(iUserServcice.Tick);
var g=Startup.AutofacContainer.Resolve<IUserServcice>();
list.Add(g.Tick);
list.Add(tick2);
//using (var scope= SorviceLocator.Container.BeginLifetimeScope("t1"))
// {
for (int i = 0; i < 3; i++)
{
await Task.Factory.StartNew(() =>
{
var sr = Startup.AutofacContainer.Resolve<IUserServcice>();
list.Add(sr.Tick);
});
}
// }
}
[HttpGet]
public async Task<IEnumerable<WeatherForecast>> Get()
{
await Get1();
var rng = new Random();
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = rng.Next(-20, 55),
Summary = Summaries[rng.Next(Summaries.Length)]
})
.ToArray();
}
}
and unfortunately the result of debug is like image uploaded below:
as you can see in the picture item in the top is the result of controller constructor injection and, other items, are inside the controller and my question is that how can I have all these items with the same value.
When you use ASP.NET Core, while you can have Autofac be the backing container, for the most part you give up working with Autofac directly when outside the Startup class. You register your stuff in Startup, but in controllers and elsewhere, it's all standard dependency injection (no Autofac references) and the Microsoft dependency injection abstraction.
This is important because it'll help you Google for answers. Don't look for "How do I do this with Autofac?" - look for "How do I do this in ASP.NET Core?"
First, avoid service location. I see what you're doing, I see what you're getting at... but the fact you need to use service location to demonstrate the issue seems like a red flag.
Now that's out of the way:
What you want is HttpContext.RequestServices. When you have a controller, you'll have the HttpContext and the RequestServices object there is the request lifetime scope. It's backed by Autofac but the interface is the Microsoft interface.
You can read about RequestServices in the Microsoft docs.
private readonly IUserService injected;
public WeatherForecastController(IUserService userService)
{
this.injected = userService;
}
public async Task Get()
{
var located = this.HttpContext.RequestServices.GetService<IUserService>();
// located and injected will be the same instance.
}
If you need to begin a child lifetime scope, again, that's an MS DI thing. You'll need an IServiceScopeFactory. That can be a constructor dependency or you can use service location like you were doing before.
var scopeFactory = this.HttpContext.RequestServices.GetService<IServiceScopeFactory>();
using(var scope = scopeFactory.CreateScope())
{
// Now you have a scope to work with.
}
If you absolutely must get the Autofac lifetime from an IServiceProvider for whatever reason, you can resolve one. Resolving a lifetime scope from a lifetime scope returns itself.
var requestScope = this.HttpContext.RequestServices.GetService<ILifetimeScope>();
But, again, you'll notice everything we're doing here is working with the Microsoft DI abstraction, so when you're looking for answers, I'd recommend looking more broadly and not limiting your search to Autofac. This answer is basically the same regardless of the backing container you use.

Set a custom SessionStore for ConfigureApplicationCookie without BuildServiceProvider()

I have a .NET Core 3 project (recently upgraded from 2.2) that uses a Redis distributed cache and cookie authentication.
It currently looks something like this:
public void ConfigureServices(IServiceCollection services)
{
// Set up Redis distributed cache
services.AddStackExchangeRedisCache(...);
...
services.ConfigureApplicationCookie(options =>
{
...
// Get a service provider to get the distributed cache set up above
var cache = services.BuildServiceProvider().GetService<IDistributedCache>();
options.SessionStore = new MyCustomStore(cache, ...);
}):
}
The problem is that BuildServiceProvider() causes a build error:
Startup.cs(...): warning ASP0000: Calling 'BuildServiceProvider' from application code results in an additional copy of singleton services being created. Consider alternatives such as dependency injecting services as parameters to 'Configure'.
This doesn't appear to be an option - ConfigureApplicationCookie is in Startup.ConfigureServices and can only configure new services, Startup.Configure can use the new services, but can't override CookieAuthenticationOptions.SessionStore to be my custom store.
I've tried adding services.AddSingleton<ITicketStore>(p => new MyCustomRedisStore(cache, ...)) before ConfigureApplicationCookie, but this is ignored.
Explicitly setting CookieAuthenticationOptions.SessionStore appears to be the only way to get it to use anything other than the local memory store.
Every example I've found online uses BuildServiceProvider();
Ideally I want to do something like:
services.ConfigureApplicationCookieStore(provider =>
{
var cache = provider.GetService<IDistributedCache>();
return new MyCustomStore(cache, ...);
});
Or
public void Configure(IApplicationBuilder app, ... IDistributedCache cache)
{
app.UseApplicationCookieStore(new MyCustomStore(cache, ...));
}
And then CookieAuthenticationOptions.SessionStore should just use whatever I've configured there.
How do I make the application cookie use an injected store?
Reference Use DI services to configure options
If all the dependencies of your custom store are injectable, then just register your store and required dependencies with the service collection and use DI services to configure options
public void ConfigureServices(IServiceCollection services) {
// Set up Redis distributed cache
services.AddStackExchangeRedisCache(...);
//register my custom store
services.AddSingleton<ITicketStore, MyCustomRedisStore>();
//...
//Use DI services to configure options
services.AddOptions<CookieAuthenticationOptions>(IdentityConstants.ApplicationScheme)
.Configure<ITicketStore>((options, store) => {
options.SessionStore = store;
});
services.ConfigureApplicationCookie(options => {
//do nothing
}):
}
If not then work around what is actually registered
For example
//Use DI services to configure options
services.AddOptions<CookieAuthenticationOptions>(IdentityConstants.ApplicationScheme)
.Configure<IDistributedCache>((options, cache) => {
options.SessionStore = new MyCustomRedisStore(cache, ...);
});
Note:
ConfigureApplicationCookie uses a named options instance. - #KirkLarkin
public static IServiceCollection ConfigureApplicationCookie(this IServiceCollection services, Action<CookieAuthenticationOptions> configure)
=> services.Configure(IdentityConstants.ApplicationScheme, configure);
The option would need to include the name when adding it to services.
To implement Redis Tickets in .NET Core 3.0 we did the following which is the above in a bit more of a final form::
services.AddSingleton<ITicketStore, RedisTicketStore>();
services.AddOptions<CookieAuthenticationOptions>(CookieAuthenticationDefaults.AuthenticationScheme)
.Configure<ITicketStore>((options, store) => {
options.SessionStore = store;
});
services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme)
.AddIdentityServerAuthentication(options =>
{
// ...configure identity server options
}).AddCookie(CookieAuthenticationDefaults.AuthenticationScheme);
Here is a Redis implementation:
public class RedisTicketStore : ITicketStore
{
private const string KeyPrefix = "AuthSessionStore-";
private IDistributedCache cache;
public RedisTicketStore(IDistributedCache cache)
{
this.cache = cache;
}
public async Task<string> StoreAsync(AuthenticationTicket ticket)
{
var guid = Guid.NewGuid();
var key = KeyPrefix + guid.ToString();
await RenewAsync(key, ticket);
return key;
}
public Task RenewAsync(string key, AuthenticationTicket ticket)
{
var options = new DistributedCacheEntryOptions();
var expiresUtc = ticket.Properties.ExpiresUtc;
if (expiresUtc.HasValue)
{
options.SetAbsoluteExpiration(expiresUtc.Value);
}
byte[] val = SerializeToBytes(ticket);
cache.Set(key, val, options);
return Task.FromResult(0);
}
public Task<AuthenticationTicket> RetrieveAsync(string key)
{
AuthenticationTicket ticket;
byte[] bytes = null;
bytes = cache.Get(key);
ticket = DeserializeFromBytes(bytes);
return Task.FromResult(ticket);
}
public Task RemoveAsync(string key)
{
cache.Remove(key);
return Task.FromResult(0);
}
private static byte[] SerializeToBytes(AuthenticationTicket source)
{
return TicketSerializer.Default.Serialize(source);
}
private static AuthenticationTicket DeserializeFromBytes(byte[] source)
{
return source == null ? null : TicketSerializer.Default.Deserialize(source);
}
}
Redis implementation from: https://mikerussellnz.github.io/.NET-Core-Auth-Ticket-Redis/

Storing and retrieving values from IDistributedCache (Redis) in .NET Core 2

I have an ASP.NET Core2 application. I am using both builtin and Autofac IoC containers. I am setting up all the component registrations in my Startup.cs file. While doing this, I am also setting up my DBContext which inherits from a custom DataContext which in turn inherits from DbContext and implements a custom IDataContextAsync. This DbContext expects a connection string as a constructor parameter.
My problem is that the connection string is stored in the Redis Cache which is an IDistributedCache. The cache is setup in the startup.cs file. The Connection String also is required in the same ConfigureServices method in Startup.cs. So, I don't seem to have access to this cache at this point.
Everything was working when I was using the HttpContext Session to store the connection string. Now that the application is being deployed to a Web farm, I can't use in proc session. We are using Redis for state management. This is where I am having a problem with.
Here is my ConfigureServices method from startup.cs file (unnecessary code removed for brevity).
public IServiceProvider ConfigureServices(IServiceCollection services)
{
services.AddMvc()
.AddJsonOptions(op => op.SerializerSettings.ContractResolver = new DefaultContractResolver());
services.AddSession(opt =>
{
opt.IdleTimeout = TimeSpan.FromMinutes(20);
opt.Cookie.Name = "apexportal.RulesSession";
opt.Cookie.HttpOnly = true;
});
services.AddDistributedRedisCache(o =>
{
var host = Configuration.GetValue<string>($"{AppConstants.REDIS}:{AppConstants.REDISHOST}");
var port = Configuration.GetValue<string>($"{AppConstants.REDIS}:{AppConstants.REDISPORT}");
o.Configuration = $"{host}";
o.InstanceName = Configuration.GetValue<string>($"{AppConstants.REDIS}:{AppConstants.REDISNAME}");
});
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
//services.AddTransient<IConnectionStringProvider, ConnectionStringProvider>();
services.AddTransient<IDataContextAsync>(s => new PortalEFContext(GetPortalConnectionString()));
services.AddAuthentication(IISDefaults.AuthenticationScheme);
ContainerBuilder builder = new ContainerBuilder();
builder.Populate( services );
var container = builder.Build();
return container.Resolve<IServiceProvider>();
}
and here is my GetPortalConnectionString() method which is also in the startup.cs file. I want to replace the line accessor.HttpContext.Session.Get() with an injected RedisCache.Get().
private string GetPortalConnectionString()
{
IHttpContextAccessor accessor = new HttpContextAccessor();
//this is where I need to access the RedisCache and access the stored properties
// instead of using HttpContext.Session. But I don't know how to inject the IDistributedCache
// to this spot.
var connString = accessor.HttpContext.Session.Get<string>(AppConstants.SPCONNSTRING);
return connString ?? Configuration.GetConnectionString("PortalEFContext");
}
Later when the user has selected a database to use in the application, I am storing the connectionstring to that database in Redis Cache like so.
Here is my BaseController class which does that.
public abstract class BaseController : Controller
{
//private readonly IRulesEngineService reService;
protected readonly IHttpContextAccessor httpCtxAccessor;
protected readonly IConfiguration config;
private readonly IAuthService authService;
protected readonly IDistributedCache redisCache;
public BaseController(IHttpContextAccessor _httpContext, IConfiguration _config, IAuthService _authService, IDistributedCache _redisCache)
{
//reService = _reService;
httpCtxAccessor = _httpContext;
config = _config;
authService = _authService;
redisCache = _redisCache;
//SetupCurrentWindowsUserAsync();
}
protected async Task<string> SetCurrentDBConnString( int dbId )
{
var currDbId = await GetCurrentDBId();
if ( currDbId == 0 || currDbId != dbId )
{
var envConnStr = config.GetConnectionString( AppConstants.ENVCONNSTRING );
var connStr = await AppHelper.SetCurrentDBConnectionString( dbId, envConnStr );
//httpCtxAccessor.HttpContext.Session.Set<string>( AppConstants.SPCONNSTRING, connStr );
//httpCtxAccessor.HttpContext.Session.Set<int>( AppConstants.CURRDBID, dbId );
await redisCache.SetAsync<string>( AppConstants.SPCONNSTRING, connStr );
await redisCache.SetAsync<int>( AppConstants.CURRDBID, dbId );
await SetupCurrentWindowsUserAsync();
return connStr;
}
return null;
}
}
Can someone please tell me how I can access the Redis cache in my startup.cs file? Thanks.
It's actually very simple. You were almost there already.
Take a closer look at this line in your startup:
services.AddTransient<IDataContextAsync>(s => new PortalEFContext(GetPortalConnectionString()));
See the s parameter in the lambda? This is the DI container of .NET Core called IServiceProvider. This is what you were looking for. Just pass it down into your function and use it there to resolve anything you want.
So, the code will be the following:
public IServiceProvider ConfigureServices(IServiceCollection services)
...
services.AddTransient<IDataContextAsync>(s => new PortalEFContext(GetPortalConnectionString(s))); // <-- pass the container to the function
...
}
private string GetPortalConnectionString(IServiceProvider container)
{
// Here you go:
var cache = container.GetService<IDistributedCache>();
// and now do whatever you want with it.
var connString = cache.Get<string>(AppConstants.SPCONNSTRING);
// BTW, configuration can be resolved from container as well in order to avoid hard dependency on global Configuration object:
var config = container.GetService<IConfiguration>();
return connString ?? config.GetConnectionString("PortalEFContext");
}

How to properly configure mock dependencies for testing Web Api (ASP.NET Core) controllers using Autofac

I'm using ASP.NET Core (2.0) with Autofac, and a Microsoft.AspNetCore.TestHost.TestServer for integration testing. However, for some test scenarios, I would like to inject some service mocks instead of the implementations loaded in ConfigureContainer method (as described here: http://docs.autofac.org/en/latest/integration/aspnetcore.html#quick-start-with-configurecontainer).
Example:
public class Program
{
public static void Main(string[] args)
{
var host = new WebHostBuilder()
.UseKestrel()
.ConfigureServices(s => s.AddAutofac())
.UseStartup<Startup>()
.Build();
host.Run();
}
}
public class Startup
{
...
public void ConfigureContainer(ContainerBuilder builder)
{
builder.RegisterModule(new Modules.ApiModule());
}
...
}
And the test class:
public class BasicControllerTests
{
TestServer server;
HttpClient client;
public BasicControllerTests()
{
var resellerRepo = new Mock<IResellerProvider>();
resellerRepo.Setup(a => a.Query())
.Returns(new[] {
new Model.Reseller
{
Id = Guid.NewGuid(),
Code = "R1",
Name = "Reseller 1"
}
}.AsQueryable());
// How to inject mock properly in the lines below?
server = new TestServer(new WebHostBuilder()
.ConfigureServices(a => a.AddAutofac())
.UseStartup<Startup>());
client = server.CreateClient();
}
...
What I would like to do is to use the TestServer with all the dependencies as they are, but just the IResellerProvider mocked as in the test example. What is the best way to accomplish that? Of course, I could create a TestStartup class for this exact case, but I would like to know what is the proper way to handle this situation.
I found a workaround that works just fine and lets you inject any other dependency in .net core api.
You will have this standard code to start up in your tests
var clientFactory = new WebApplicationFactory<Startup>();
var client = clientFactory.WithWebHostBuilder(builder =>
builder.ConfigureTestServices(services =>
{
}));
var _httpClient = client.CreateClient();
Now you need to pass to .ConfigureTestServices an Action and you can use this to remove the registration you have on the normal app startup and add another one that lets say it's a fake one.This in possible because if you look with debugger on services you will see that all those you registered are presend and you will just need to replace the ones you want to moke.Here is a simple example that I used
RemoveVehicleServiceRegistrationFrom(services);
services.AddScoped<IVehicleService, FakeVehicleService>();
In the Remove method you just need to find and remove old registration.Something like this
private static void RemoveVehicleServiceRegistrationFrom(IServiceCollection services)
{
var vehicleService = services.Single(x => x.ServiceType == typeof(IVehicleService));
services.Remove(vehicleService);
}
Final version looks like this
private HttpClient _httpClient;
[OneTimeSetUp]
public void Setup()
{
var clientFactory = new WebApplicationFactory<Startup>();
var client = clientFactory.WithWebHostBuilder(builder =>
builder.ConfigureTestServices(services =>
{
RemoveVehicleServiceRegistrationFrom(services);
services.AddScoped<IVehicleService, FakeVehicleService>();
}));
var _httpClient = client.CreateClient();
}
private static void RemoveVehicleServiceRegistrationFrom(IServiceCollection services)
{
var vehicleService = services.Single(x => x.ServiceType == typeof(IVehicleService));
services.Remove(vehicleService);
}

Best practices for config file in netcoreapp1.1

I have added the access to the appsettings.json file as a framework service in my Startup.cs:
public IConfigurationRoot Configuration { get; }
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables();
Configuration = builder.Build();
}
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.Configure<AppConfig>(Configuration);
services.AddMvc();
}
So now I have access to the configuration file from my controllers:
public class HomeController : Controller
{
private readonly AppConfig _appConfig;
public HomeController(IOptions<AppConfig> appConfig, ConfigContext configContext)
{
_appConfig = appConfig.Value;
}
}
That's working but what's currently a good practice in netcoreapps for accessing the config file from classes outsite my controller scope?
I mean that I would not like to pass always the required config variables to other methods, example:
public IActionResult AnyAction() {
SomeStaticClass.SomeMethod(_appConfig.var1, _appConfig.var2, _appConfig.var3...)
//or always have to pass the _appConfig reference
SomeStaticClass.SomeMethod(_appConfig)
}
In previous versions of .NET Framework if I required access to the config file from "SomeStaticClass" I used to use ConfigurationManager in any class that I need access to the web.config.
What's the correct way to do it in a netcoreapp1.1 ? either ConfigurationManager like or dependency injection approach works for me.
I think this question is more about how you can get a contextual variable from a static class. I think this will accomplish what you want but I'm not sure why you want a static class or what you are trying to do with it (see XY Problem).
public class HomeController : Controller
{
private readonly AppConfig _appConfig;
public HomeController(IOptions<AppConfig> appConfig, ConfigContext configContext)
{
_appConfig = appConfig.Value;
SomeStaticClass.SomeStaticMember = appConfig.Value
}
public IActionResult AnyAction() {
SomeStaticClass.SomeMethod(); //The get the value you want from within
}
}
EDIT:
You can use Newtonsoft.Json, it's a dependency of Microsoft.AspNet.Mvc.ModelBinding which is a dependency of Microsoft.AspNet.Mvc
string fileContents = string.Empty;
using (StreamReader file = File.OpenText(#".\appsettings.json"))
{
fileContents = file.ReadAllLines();
}
configs= JsonConvert.DeserializeObject(fileContents );
What I did is to create the following class:
public static class Configuration
{
private static IConfiguration _configuration;
static Configuration()
{
var builder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json");
_configuration = builder.Build();
}
public static string GetValue(string key)
{
return _configuration.GetValue<string>(key, null);
}
public static string GetValue(string section, string key)
{
return _configuration.GetSection(section).GetValue<string>(key);
}
}
However it doesn't use the environment logic that is used in Startup.cs using the IHostingEnvironment parameter.