SignalR - Multitenant Dependency Injection - c#

I need to resolve a DbContext based on tenant's owin value. But in the pipeline of method OnDisconnected of hub, the HttpContext is not accessible.
My hub class:
public class UserTrackingHub : Hub
{
private readonly UserContext _context;
public UserTrackingHub(UserContext context) { ... }
public override async Task OnConnected() { /* OK HERE...*/ }
public override async Task OnDisconnected(bool stopCalled)
{
// NEVER FIRES WITH IF I USE THE CTOR INJECTION.
var connection = await _context.Connections.FindAsync(Context.ConnectionId);
if (connection != null)
{
_context.Connections.Remove(connection);
await _context.SaveChangesAsync();
}
}
}
Here's my Autofac config:
public static IContainer Register(IAppBuilder app)
{
var builder = new ContainerBuilder();
// Other registers...
builder.Register<UserContext>(c =>
{
// Details and conditions omitted for brevity.
var context = HttpContext.Current; // NULL in OnDisconnected pipeline.
var owinContext = context.GetOwinContext();
var tenant = owinContext.Environment["app.tenant"].ToString();
var connection = GetConnectionString(tenant);
return new UserContext(connection);
});
var container = builder.Build();
var config = new HubConfiguration
{
Resolver = new AutofacDependencyResolver(container)
};
app.MapSignalR(config);
return container;
}
Can someone help me to identify the tenant OnDisconnected in this or any other way?
Thanks!

For anyone interested, I end up injecting a context factory instead the context itself:
public class UserTrackingHub : Hub
{
private readonly Func<string, UserContext> _contextFactory;
public UserTrackingHub(Func<string, UserContext> contextFactory) { ... }
public override async Task OnConnected() { ... }
public override async Task OnDisconnected(bool stopCalled)
{
var tenant = Context.Request.Cookies["app.tenant"].Value;
using (var context = _contextFactory.Invoke(tenant))
{
var connection = await context.Connections.FindAsync(Context.ConnectionId);
if (connection != null)
{
context.Connections.Remove(connection);
await context.SaveChangesAsync();
}
}
}
}
And Autofac's config:
// Resolve context based on tenant
builder.Register<Func<string, UserContext>>(c => new Func<string, UserContext>(tenant => UserContextResolver.Resolve(tenant)));

Related

Conflict with mocking service when seeding database for testing

I have a multi tenant web API where I seed a database with initial data.
I also have a transient IUserService which has a GetCustomerId function to retrieve the current customerId. This service is used in the databaseContext to store the CustomerId foreign key on the created domain entity "under the hood".
So when I seed the database I create a new scope and use a ICurrentUserInitializer to set the CustomerId in the IUserService for that scope, so the CustomerId is valid when the database context stores the entity.
This works just fine in development, but not for testing. Since I want to mock the IUserService when I test, this means that Moq overrides the GetCustomerId. But I only want to mock that service AFTER I've finished seeding the test database.
I've also tried not mocking the IUserService, and instead use a ICurrentUserInitializer for every test that runs, i.e. for every test, create a new scope, set the CustomerId with the ICurrentUserInitializer in that scope, and run the test in that scope, and then reset for the next test. This seems to work, but isn't as flexible when you want to run tests as different users and it doesn't seem as elegant, since I have to write more code to handle the scope correctly.
I Use xUnit, Moq, Respawn, and Microsoft.AspNetCore.Mvc.Testing
DbContext :
public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = new CancellationToken())
{
int? customerId = CurrentUser.GetCustomerId();
HandleAuditingBeforeSaveChanges(customerId);
int result = await base.SaveChangesAsync(cancellationToken);
return result;
}
private void HandleAuditingBeforeSaveChanges(int? customerId)
{
foreach (var entry in ChangeTracker.Entries<IMustHaveTenant>().ToList())
{
entry.Entity.CustomerId = entry.State switch
{
EntityState.Added => customerId.Value,
_ => entry.Entity.CustomerId
};
}
}
DatabaseInitializer :
public async Task InitializeApplicationDbForCustomerAsync(Customer Customer, CancellationToken cancellationToken)
{
// First create a new scope
using var scope = _serviceProvider.CreateScope();
// This service injects a CustomerId, so that ICurrentUser retrieves this value, but
// doesn't work, since Moq overrides the value
scope.ServiceProvider.GetRequiredService<ICurrentUserInitializer>()
.SetCurrentCustomerId(customer.Id);
// Then run the initialization in the new scope
await scope.ServiceProvider.GetRequiredService<ApplicationDbSeeder>()
.SeedDatabaseAsync(_dbContext, cancellationToken);
}
CustomWebApplicationFactory:
public class CustomWebApplicationFactory : WebApplicationFactory<Program>
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureAppConfiguration(configurationBuilder =>
{
var integrationConfig = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.AddEnvironmentVariables()
.Build();
configurationBuilder.AddConfiguration(integrationConfig);
});
builder.ConfigureServices((context, services) =>
{
services
.Remove<DbContextOptions<ApplicationDbContext>>()
.AddDbContext<ApplicationDbContext>((sp, options) =>
{
options.UseSqlServer(context.Configuration.GetConnectionString("DefaultConnection"),
builder => builder.MigrationsAssembly(typeof(ApplicationDbContext).Assembly.FullName));
});
});
builder.ConfigureTestServices(services =>
{
services
.Remove<ICurrentUser>()
.AddTransient(_ => Mock.Of<ICurrentUser>(s =>
s.GetCustomerId() == GetCurrentCustomerId()));
});
}
}
Testing / CollectionFixture :
public class DatabaseCollection : ICollectionFixture<Testing>
{
}
public partial class Testing : IAsyncLifetime
{
private static WebApplicationFactory<Program> _factory = null!;
private static IConfiguration _configuration = null!;
private static IServiceScopeFactory _scopeFactory = null!;
private static Checkpoint _checkpoint = null!;
private static int? _currentCustomerId = null;
public Task InitializeAsync()
{
_factory = new CustomWebApplicationFactory();
_scopeFactory = _factory.Services.GetRequiredService<IServiceScopeFactory>();
_configuration = _factory.Services.GetRequiredService<IConfiguration>();
_checkpoint = new Checkpoint
{
TablesToIgnore = new[] { new Table("__EFMigrationsHistory") },
};
return Task.CompletedTask;
}
public static int? GetCurrentCustomerId()
{
return _currentCustomerId;
}
public static void RunAsDefaultUserAsync()
{
_currentCustomerId = DefaultValues.Customer.Id;
}
public static async Task<TResponse> SendAsync<TResponse>(IRequest<TResponse> request)
{
using var scope = _scopeFactory.CreateScope();
var mediator = scope.ServiceProvider.GetRequiredService<ISender>();
return await mediator.Send(request);
}
public static async Task<TEntity?> FindAsync<TEntity>(params object[] keyValues)
where TEntity : class
{
using var scope = _scopeFactory.CreateScope();
var context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
return await context.FindAsync<TEntity>(keyValues);
}
public static async Task AddAsync<TEntity>(TEntity entity)
where TEntity : class
{
using var scope = _scopeFactory.CreateScope();
var context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
context.Add(entity);
await context.SaveChangesAsync();
}
public static async Task ResetState()
{
await _checkpoint.Reset(_configuration.GetConnectionString("DefaultConnection"));
await _factory.Services.InitializeDatabasesAsync();
_currentCustomerId = null;
}
public Task DisposeAsync()
{
return Task.CompletedTask;
}
}
DepartmentTest:
[Collection("Database collection")]
public class GetDepartmentsTest : BaseTestFixture
{
[Fact]
public async Task ShouldReturnDepartments()
{
RunAsDefaultUserAsync();
var query = new ListDepartmentRequest();
var result = await SendAsync(query);
result.ShouldNotBeNull();
}
[Fact]
public async Task ShouldReturnAllDepartments()
{
RunAsDefaultUserAsync();
await AddAsync(new Department
{
Description = "Department 1",
});
await AddAsync(new Department
{
Description = "Department 2",
});
var query = new ListDepartmentRequest();
var result = await SendAsync(query);
result.ShouldNotBeNull();
result.Count.ShouldBe(2);
}
}
BaseTestFixture:
public class BaseTestFixture : IAsyncLifetime
{
public async Task InitializeAsync()
{
await ResetState();
}
public async Task DisposeAsync()
{
await ResetState();
//return Task.CompletedTask;
}
}

Some services are not able to be constructed Error while validating the service descriptor

I have a ASP.NET Core 5.0 MVC solution,
public abstract class HostedService : IHostedService, IDisposable
{
private Task _currentTask;
private readonly CancellationTokenSource _cancellationTokenSource = new
CancellationTokenSource();
protected abstract Task ExecuteAsync(CancellationToken token);
public virtual Task StartAsync(CancellationToken cancellationToken)
{
_currentTask = ExecuteAsync(cancellationToken);
return _currentTask.IsCompleted ? _currentTask : Task.CompletedTask;
}
public virtual async Task StopAsync(CancellationToken cancellationToken)
{
if (_currentTask == null) return;
try
{
_cancellationTokenSource.Cancel();
}
finally
{
await Task.WhenAny(_currentTask, Task.Delay(Timeout.Infinite, cancellationToken));
}
}
public virtual void Dispose()
{
_cancellationTokenSource.Cancel();
}
}
Gets the exchange rates from URL
public class ExchangeSyncManager : HostedService
{
private readonly CurrencyServices _currencyServices;
private readonly ExchangeRateServices _exchangeRateServices;
public ExchangeSyncManager(CurrencyServices currencyServices, ExchangeRateServices
exchangeRateServices)
{
_currencyServices = currencyServices;
_exchangeRateServices = exchangeRateServices;
}
protected override async Task ExecuteAsync(CancellationToken token)
{
// işlem iptal edilmemişse…
if (!token.IsCancellationRequested)
{
var url = "http://www.tcmb.gov.tr/kurlar/today.xml";
XmlDocument xmlVerisi = new XmlDocument();
List<ExchangeRate> list = new List<ExchangeRate>();
xmlVerisi.Load(url);
foreach (var currency in _currencyServices.GetCurrencies())
{
var format = string.Format("Tarih_Date/Currency[#Kod='{0}']/ForexSelling", currency.Name);
var selectAndReplace = xmlVerisi.SelectSingleNode(format).InnerText.Replace('.', ',');
decimal value = Convert.ToDecimal(selectAndReplace);
list.Add(new ExchangeRate
{
Date = DateTime.Now,
Value = value,
CurrencyId = currency.Id
});
}
_exchangeRateServices.AddRange(list);
await Task.Delay(TimeSpan.FromDays(1), token);
}
}
}
And in startup :
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
//other services here
......................................................
.....................................................
services.AddScoped<CurrencyServices, CurrencyServices>();
services.AddScoped<ExchangeRateServices>();
services.AddHostedService<ExchangeSyncManager>();
}
Still I am getting this error :
Some services are not able to be constructed (Error while validating the service descriptor 'ServiceType: Microsoft.Extensions.Hosting.IHostedService Lifetime: Singleton ImplementationType: IPMMS.Business.Managers.ExchangeSyncManager': Cannot consume scoped service 'IPMMS.Business.Services.CurrencyServices' from singleton 'Microsoft.Extensions.Hosting.IHostedService'.)'
What is wrong ?
You cannot inject a scoped services inside a singleton. They are bound to HTTP requests.
A HostedService's lifetime is singleton.
However you can use the IServiceProvider to create a scope and retrieve an instance of your scoped service.
You will find how to fix your issue here :
https://learn.microsoft.com/en-us/dotnet/core/extensions/scoped-service

Connect with SingalR client with IHubContext provided in diffrent thread

I am using ASP.NET Core, and I am adding some users to a collection via SingalR hub endpoint:
public class MatchMakingHub : Hub
{
//....
// called by client
public async Task EnlistMatchMaking(int timeControlMs)
{
Guid currentId = Guid.Parse(this.Context.User.GetSubjectId());
GetPlayerByIdQuery getPlayerByIdQuery = new GetPlayerByIdQuery(currentId);
Player currentPlayer = await requestSender.Send<Player>(getPlayerByIdQuery);
var waitingPlayer = new WaitingPlayer(currentPlayer, timeControlMs);
this.matchMakePool.Add(waitingPlayer);
}
}
matchMakePool being a singleton collection.
Later, I have an ASP.NET Core background service fetch the users from the collection, and notify them about being fetched:
public class MatchMakingBackgroundService : BackgroundService
{
private readonly MatchMakePoolSingleton matchMakePoolSingleton;
private readonly IServiceProvider serviceProvider;
private const int RefreshTimeMs = 1000;
public MatchMakingBackgroundService(MatchMakePoolSingleton matchMakePoolSingleton, IServiceProvider serviceProvider)
{
this.matchMakePoolSingleton = matchMakePoolSingleton;
this.serviceProvider = serviceProvider;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while(!stoppingToken.IsCancellationRequested)
{
var result = matchMakePoolSingleton.RefreshMatches();
var tasks = new List<Task>();
foreach(var match in result)
{
tasks.Add(StartGameAsync(match));
}
await Task.WhenAll(tasks);
await Task.Delay(RefreshTimeMs, stoppingToken);
}
}
private async Task StartGameAsync(MatchMakeResult match)
{
using var scope = serviceProvider.CreateScope();
var sender = scope.ServiceProvider.GetRequiredService<ISender>();
var hubContext = serviceProvider.GetRequiredService<IHubContext<MatchMakingHub>>();
CreateNewGameCommand newGameCommand = new CreateNewGameCommand(match.WhitePlayer.Id, match.BlackPlayer.Id, TimeSpan.FromMilliseconds(match.TimeControlMs));
Guid gameGuid = await sender.Send(newGameCommand);
await hubContext.Clients.User(match.WhitePlayer.Id.ToString()).SendAsync("NotifyGameFound", gameGuid);
await hubContext.Clients.User(match.BlackPlayer.Id.ToString()).SendAsync("NotifyGameFound", gameGuid);
}
}
My problem is that NotifyGameFound is not being called in the client side. When I notified them straight from the hub itself it was received, but for some reason it doesn't when I call it through the provided IHubContext<MatchMakingHub>. I suspect that this is because it runs on another thread.
Here is the client code:
// in blazor
protected override async Task OnInitializedAsync()
{
var tokenResult = await TokenProvider.RequestAccessToken();
if(tokenResult.TryGetToken(out var token))
{
hubConnection
= new HubConnectionBuilder().WithUrl(NavigationManager.ToAbsoluteUri("/hubs/MatchMaker"), options =>
{
options.AccessTokenProvider = () => Task.FromResult(token.Value);
}).Build();
await hubConnection.StartAsync();
hubConnection.On<Guid>("NotifyGameFound", id =>
{
//do stuff
});
await MatchMakeRequast();
}
}
async Task MatchMakeRequast() =>
await hubConnection.SendAsync("EnlistMatchMaking", Secs * 1000);
I use injection to achieve this.
In my servers Startup.cs ConfigureServices mothod I have:
services.AddScoped<INotificationsBroker, NotificationsBroker>();
In your case I am assuming you are injecting MatchMakingBackgroundService
Something like:
services.AddScoped<MatchMakingBackgroundService>();
In my NotificationsBroker constructor I inject the context:
private readonly IHubContext<NotificationsHub> hub;
public NotificationsBroker(IHubContext<NotificationsHub> hub)
=> this.hub = hub;
I then inject the broker into any service I require it and the service can call the hubs methods I expose via the interface.
You don't have to go the extra step, I do this for testing, you could inject the context directly into your MatchMakingBackgroundService.

How to do Integration Tests with Mediatr on .net framework 4.7?

I'm using the Mediatr library to register and call my RequestHandlers.
Everything went fine until I started reading more about integrated tests.
PLEASE READ AFTER EDIT
I can't call my class which inherits from the RequesHandler.
My class looks like this:
public class MyRequestHandler : RequestHandler<MyRequest, MyResponse>
{
....
}
I'm not using the Meditr async and I'm using .net framework 4.7 instead of asp.net core, so, everything looks like returns me answers for asp.net core.
When I construct MyTestClass, to construct the RequestHandler I have to create a ServiceFactory and maybe this is the problem because I don't know how.
public MyClassTest()
{
ServiceFactory sv = null;
_mediator = new Mediator(sv);
}
EDIT
Providing more info
I have this Handler in my Application Layer
public class LogInUserByFormHandler : RequestHandler<LogInUserByFormRequest, LogInUserByFormResponse>
{
private readonly IValidator<LogInUserByFormRequest> _validator;
public LogInUserByFormHandler(IValidator<LogInUserByFormRequest> validator)
{
_validator = validator;
}
protected override LogInUserByFormResponse Handle(LogInUserByFormRequest request)
{
_validator.ValidateAndThrow(request);
var userInfo = GetUserInfo(request);
ValidateLogInUserByFormRules(userInfo);
var userLoginInfo = GetValidUserLoginInfo(request);
ValidateUserLoginInfoByFormRules(userLoginInfo);
var sessionKey = CreateUserSessionKey(userInfo);
var response = new LogInUserByFormResponse
{
UserName = request.UserName,
SessionKey = sessionKey,
UserId = userInfo.id_usuario
};
return response;
}
//A LOT OF CODE HERE, methods and etc
}
As it's possible to see, it implements the Mediatr.
On my Web Project on Presentation Layer, I used AutoFac to Inject the Handlers, so, any Request I do is always handled by the right method.
All I have to do is call, like this:
var logInByFormRequest = new LogInUserByFormRequest
{
UserName = viewModel.UserName,
Password = viewModel.Password
};
var response = _mediator.Send(logInByFormRequest).Result;
This works like a charm. The problem now is on the Test project. It references the Application as the Presentation Project does.
I don't know how to make the mediator.send find the right method.
EDIT²
Here comes my test code
[TestClass]
public class LogInUserByFormTest
{
private LogInUserByFormRequest CreateRequest(string userName, string password)
{
LogInUserByFormRequest request = new LogInUserByFormRequest
{
UserName = userName,
Password = password
};
return request;
}
[TestMethod]
[Description("")]
public void UserName_ShouldHave_Max_30Characters_Exception()
{
try
{
var request = CreateRequest("UserNameIsGreaterThanAllowed", "password");
var mediator = new Mock<IMediator>();
var response = mediator.Object.Send(request).Result;
}
catch (System.Exception ex)
{
throw;
}
}
}
The result (response) is always null and the mediator doesn't call the right handler.
EDIT3
Here is how I register the handlers and validators.
I use autofac. This class here is called on the global.asax
public class AutofacConfig
{
public static void ConfigureContainer()
{
var builder = new ContainerBuilder();
builder.RegisterControllers(Assembly.GetExecutingAssembly()).InstancePerRequest();
builder.RegisterType<Mediator>().As<IMediator>().InstancePerLifetimeScope();
builder.RegisterType<AutofacValidatorFactory>().As<IValidatorFactory>().SingleInstance();
builder.RegisterType<FluentValidationModelValidatorProvider>().As<ModelValidatorProvider>();
builder.RegisterType<RegistryManagerService>().As<IRegistryManagerService>().SingleInstance().WithParameter("appName", ConfigurationManager.AppSettings["APPNAME"]);
builder.Register<ServiceFactory>(context =>
{
var c = context.Resolve<IComponentContext>();
return t => c.Resolve(t);
});
builder.RegisterAssemblyTypes(Assembly.Load("Docspider.Application"))
.Where(x => x.Name.EndsWith("Handler"))
.AsImplementedInterfaces();
builder.RegisterAssemblyTypes(Assembly.Load("Docspider.Application"))
.Where(x => x.Name.EndsWith("Validator"))
.AsImplementedInterfaces()
.InstancePerLifetimeScope();
var container = builder.Build();
DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
}
}
public class AutofacValidatorFactory : ValidatorFactoryBase
{
private readonly IComponentContext _context;
public AutofacValidatorFactory(IComponentContext context)
{
_context = context;
}
public override IValidator CreateInstance(Type validatorType)
{
if (_context.TryResolve(validatorType, out object instance))
{
var validator = instance as IValidator;
return validator;
}
return null;
}
}
For such an integration test you would need to configure the necessary dependencies. Since you have indicated that Autofac is being used then configure a container just as you would have in production. Use the container to get the mediator and perform the desired test.
For example.
[TestClass]
public class LogInUserByForm_IntegrartionTest {
private LogInUserByFormRequest CreateRequest(string userName, string password) {
LogInUserByFormRequest request = new LogInUserByFormRequest {
UserName = userName,
Password = password
};
return request;
}
IMediator BuildMediator() {
//AutoFac
var builder = new ContainerBuilder();
builder.RegisterAssemblyTypes(typeof(IMediator).GetTypeInfo().Assembly).AsImplementedInterfaces();
var mediatrOpenTypes = new[] {
typeof(IRequestHandler<,>)
};
foreach (var mediatrOpenType in mediatrOpenTypes) {
builder
.RegisterAssemblyTypes(typeof(LogInUserByFormRequest).GetTypeInfo().Assembly)
.AsClosedTypesOf(mediatrOpenType)
.AsImplementedInterfaces();
}
builder.Register<ServiceFactory>(ctx => {
var c = ctx.Resolve<IComponentContext>();
return t => c.Resolve(t);
});
//...all other needed dependencies.
//...
var container = builder.Build();
var mediator = container.Resolve<IMediator>();
return mediator;
}
[TestMethod]
[Description("")]
public async Task UserName_ShouldHave_Max_30Characters_Exception() {
try
{
//Arrange
var request = CreateRequest("UserNameIsGreaterThanAllowed", "password");
var mediator = BuildMediator();
//Act
var response = await mediator.Send(request);
//Assert
//...assert the expected values of response.
}
catch (System.Exception ex)
{
throw;
}
}
}
The above was modeled after the examples provided by MediatR.Examples.Autofac

ASP.NET MVC Post Authentication IoC setup

I have an http client wrapper that I'm injecting into all my controllers. If a user is authenticated, the injected wrapper should have some properties set with the authenticated user information.
I currently have this:
[System.Web.Mvc.Authorize]
public class ProfileController : Controller
{
private readonly IMyClient client;
public ProfileController()
{
string apiKey = ConfigurationManager.AppSettings["ApiKey"];
client = new MyClient(apiKey);
SetupClient();
}
private void SetupClient()
{
if (Thread.CurrentPrincipal.Identity.IsAuthenticated)
{
var identity = Thread.CurrentPrincipal.Identity as ClaimsIdentity;
var tokenClaim = identity.Claims.First(c => c.Type == ClaimTypes.Sid);
client.AddCredentials(tokenClaim.Value);
}
}
}
I would like to offload SetupClient to somewhere that will allow me to do dependency injection of IMyClient.
Essentially I want to implement this solution:
ProfileController.cs
[System.Web.Mvc.Authorize]
public class ProfileController : Controller
{
private readonly IMyClient client;
public ProfileController(IMyClient client)
{
this.client = client;
}
}
Startup.cs
public partial class Startup
{
public void Configuration(IAppBuilder app)
{
IoCConfig.RegisterIoC(app);
ConfigureAuth(app);
}
}
IoCConfig.cs
public class IoCConfig
{
public static void RegisterIoC(IAppBuilder app)
{
var container = new Container();
container.Register<IMyClient>(
() =>
{
var apiKey = ConfigurationManager.AppSettings["APIKey"];
var myClient= new MyClient(apiKey);
// This will not work as this code is executed on app start
// The identity will not be of the user making the web request
var identity = Thread.CurrentPrincipal.Identity as ClaimsIdentity;
var tokenClaim = identity.Claims.First(c => c.Type == ClaimTypes.Sid);
client.AddCredentials(tokenClaim.Value);
return myClient;
});
// Register the dependency resolver.
DependencyResolver.SetResolver(
new SimpleInjectorDependencyResolver(container));
}
}
I'm stuck in the code for IoCConfig to extract information of the authenticated user (if the user is authenticated) and setup the client for injection. Any help here?
My IoC framework is SimpleInjector but I'd like an agnostic solution.
This is how I would do it
public class ProfileController : Controller
{
private readonly MyClient _client;
public ProfileController()
{
var clientInfo = Resolve<IClientInfo>(); // call out to your service locator
_client = clientInfo.GetClient();
}
}
public interface IClientInfo
{
MyClient GetClient();
}
public interface IAuth
{
System.Security.Claim GetSidClaim();
}
public class ClientInfo : IClientInfo
{
private readonly IAuth _auth;
public ClientInfo(IAuth auth)
{
_auth = auth;
}
public MyClient GetClient()
{
var apiKey = ApiKey;
var client = new MyClient(apiKey);
var claim = _auth.GetSidClaim();
client.AddCredentials(claim.Value);
return client;
}
protected virtual string ApiKey
{
get { return ConfigurationManager.AppSettings["APIKey"]; }
}
}
I'd take a look at NInject and the MVC extensions...
http://ninject.codeplex.com/wikipage?title=Dependency%20Injection%20With%20Ninject
http://www.codeproject.com/Articles/412383/Dependency-Injection-in-asp-net-mvc-and-webapi-us
When setup correctly it's just a matter of creating a binding for IMyClient NInject will implicitly inject it for you. There are lots of other injection frameworks out there, NInject is just the one I've chosen. Each of them will give you a substantial benefit over anything you could cook up on your own. e.g. with NInject you can create bindings that inject a singleton across your app or a binding that injects a singleton for each request.
In NInject you could create a binding something like
Bind<IMyClient>().ToMethod(x => SetupClient(x)).InRequestScope();
private IMyClient SetupClient(IContext context)
{
string apiKey = ConfigurationManager.AppSettings["ApiKey"];
var client = new MyClient(apiKey);
if (Thread.CurrentPrincipal.Identity.IsAuthenticated)
{
var identity = Thread.CurrentPrincipal.Identity as ClaimsIdentity;
var tokenClaim = identity.Claims.First(c => c.Type == ClaimTypes.Sid);
client.AddCredentials(tokenClaim.Value);
}
return client;
}
InRequestScope says that NInject should create a single instance for each request...
https://github.com/ninject/Ninject.Web.Common/wiki/InRequestScope
I think the equivalent in SimpleInjector is...
https://simpleinjector.codeplex.com/wikipage?title=ObjectLifestyleManagement#PerWebRequest
Is the answer as simple as changing your code to...
public static void RegisterIoC(IAppBuilder app)
{
var container = new Container();
container.RegisterPerWebRequest<IMyClient>(
() =>
{
...
I solved this by a version of what CRice posted by using a factory delegate:
ProfileController.cs
[System.Web.Mvc.Authorize]
public class ProfileController : Controller
{
private readonly IMyClient client;
public ProfileController(Func<IMyClient> clientFactory)
{
client = clientFactory.Invoke();
}
}
IoCConfig.cs
public class IoCConfig
{
public static void RegisterIoC(IAppBuilder app)
{
// Create the container as usual.
Container container = new Container();
// Registering as a factory delegate because we need the user authentication information if any.
container.RegisterSingle<Func<IMyClient>>(() =>
{
string apiKey = ConfigurationManager.AppSettings["ApiKey"];
var myClient = new MyClient(apiKey);
if (Thread.CurrentPrincipal.Identity.IsAuthenticated)
{
var identity = Thread.CurrentPrincipal.Identity as ClaimsIdentity;
var tokenClaim = identity.Claims.First(c => c.Type == ClaimTypes.Sid);
myClient.AddCredentials(tokenClaim.Value);
}
return myClient;
});
// This is an extension method from the integration package.
container.RegisterMvcControllers(Assembly.GetExecutingAssembly());
// This is an extension method from the integration package as well.
container.RegisterMvcIntegratedFilterProvider();
container.Verify();
DependencyResolver.SetResolver(
new SimpleInjectorDependencyResolver(container));
}
}

Categories

Resources