Comments (7)
Thank you for taking time to demonstrate what can be done in my scenario. This is very elegant, well thought out, and best of all works. I agree that the registration I initially posted is difficult to grasp, and is probably not doing any favors to future programmers that will inherit this code. Additionally, this translates very well amongst the various DI frameworks.
from simpleinjector.
Why do you think you need to register AuthenticationClient
in the first place? Can't you hide all the library's logic behind a (simple) application-specified abstraction? And does the composition of the third party library's classes really have to be done by Simple Injector? Wouldn't it be simpler to just new up those classes by hand?
from simpleinjector.
Because I am not very good at software development apparently? I mean when you are using DI, you are under the impression all services (i.e. classes you make or classes that someone else made that you need) should be registered into the container, allowing you all the benefits IoC provides. So why should AuthenticationClient
and ForceClient
be any different compared to say this answer? What would you do in this scenario?
from simpleinjector.
Software development is hard; there's no single way to do things and there are many different philosophies and schools which one can follow. As you already noticed (by reading my blog posts and SO answers), I'm pretty opinionated. Even when it comes to runtime data there are multiple schools of thought, which is something I dove deeper into in the past in this series of blog posts.
When it comes to working with third-party components, I often try to hide them behind application-tailored abstractions. This is something that I learned from the Dependency Inversion Principle. But it's not just about following some arbitrary rules, I've found that this actually leads to reduced overall complexity in my applications. That said, it's not always easy to come up with an abstraction that works great for your application. I love to help you with this. But in order to do so, I need a bit more context.
In your first code example, you demonstrated how this third-party library can be used. But can you show me some typical examples of how you use this code in your application?
from simpleinjector.
I am using this third party library to query data from Salesforce. My plan was to encapsulate in a repository layer like so:
public class SalesforceRepository : ISalesforceRepository
{
private Func<IForceClient> _forceClientFactory;
public SalesforceRepository(Func<IForceClient> forceClientFactory) =>
_forceClientFactory = forceClientFactory;
public async Task<List<Things>> FindAllThingsAsync()
{
using (var client = _forceClientFactory())
{
var query = $@"SELECT Id FROM Table";
var result = await client.QueryAsync<Things>(query);
return result.Records;
}
}
// other repo methods
}
And then injecting this repository layer in my business classes so that I can retrieve the data and do things with it:
public class MyBusinessClass
{
private ISalesforceRepository _salesforceRepository;
public MyBusinessClass(ISalesforceRepository salesforceRepository) =>
_salesforceRepository = salesforceRepository;
public async Task DoStuff()
{
var data = await _salesforceRepository.FindAllThingsAsync();
// do stuff with the data
}
}
Side note: I am currently working in .NET 4.8 Framework for this project.
from simpleinjector.
Thank you for this addition. This gives me a decent idea of what you're aiming at. In your case, I'd suggest the following.
Introduce an IForceClientFactory
abstraction, with a single factory method returning a Task<IForceClient>
:
public interface IForceClientFactory
{
Task<IForceClient> CreateClient();
}
By making the CreateClient
method async, it allows you to lazily authenticate the AuthenticationClient
(will show an implementation shortly). This means you will have a small change to your SalesforceRepository
:
public class SalesforceRepository : ISalesforceRepository
{
private IForceClientFactory _factory;
public SalesforceRepository(IForceClientFactory factory) =>
_factory = factory;
public async Task<List<Things>> FindAllThingsAsync()
{
// NOTE: Here you must await the CreateClient method
using (var client = await _factory.CreateClient())
{
var query = $@"SELECT Id FROM Table";
var result = await client.QueryAsync<Things>(query);
return result.Records;
}
}
// other repo methods
}
The interesting part of the solution I'm proposing here is inside the IForceClientFactory
implementation:
public sealed class ForceAuthenticationCredentials { ... }
// This class caches AuthenticationClient. Should be register as scoped.
public sealed class ForceClientFactory : IForceClientFactory, IDisposable
{
private readonly ForceAuthenticationCredentials _credentials;
private readonly AuthenticationClient _auth;
private bool _authenticated;
public ForceClientFactory(ForceAuthenticationCredentials credentials)
{
_credentials = credentials ?? throw new ArgumentNullException();
_auth = new AuthenticationClient();
}
public async Task<IForceClient> CreateClient()
{
var auth = await GetAuthenticatedClient();
return new ForceClient(
auth.InstanceUrl, auth.AccessToken, auth.ApiVersion);
}
private async Task<AuthenticationClient> GetAuthenticatedClient()
{
if (!_authenticated)
{
await _auth.UsernamePasswordAsync(
_credentials.ConsumerKey,
_credentials.ConsumerSecret,
_credentials.UserName,
_credentials.Password + _credentials.SecurityToken);
_authenticated = true;
}
return _auth;
}
public void Dispose() => _auth.Dispose();
}
Here a few interesting things are happening:
- This
ForceClientFactory
itself contains the logic authenticate - It remembers whether it is authenticated and this information is cached
- As it stores the
AuthenticationClient
, it is responsible for disposing of it. - Any subsequent call to
CreateClient
would be fast, as the call toUsernamePasswordAsync
is skipped. - This class encapsulates what you we trying to implement inside your registrations.
While it is possible to completely remove ForceClientFactory
and have all this logic crammed inside delegates that are registered inside of Simple Injector (or any other Container), I believe you'll find the existence of the ForceClientFactory
to provide an easier to grasp solution. If you like, you can place the ForceClientFactory
inside your Composition Root; in other words, close to the place where you register Simple Injector. This keeps the code close to the place you initially wanted it to be.
The only part missing from the equation now is of course the registrations. Considering all classes you posted, the following set of Simple Injector registrations would probably do the trick:
container.RegisterInstance(new ForceAuthenticationCredentials(...));
container.Register<IForceClientFactory, ForceClientFactory>(Lifestyle.Scoped);
container.Register<ISalesforceRepository, SalesforceRepository>();
container.Register<MyBusinessClass>();
One last note: Considering that the SalesforceRepository
is about communicating with Salesforce, you could consider the IForceClientFactory
optional. You could SalesforceRepository
depend directly on the ForceClientFactory
implementation instead. Doing that, however, does mean that you won't be able to place the implementation inside the Composition Root any longer.
I hope this helps.
from simpleinjector.
You're very welcome.
from simpleinjector.
Related Issues (20)
- Is there a way to check if the Scope was disposed? HOT 5
- Using Microsoft DI and Simple injector the other way around (reversed crosswire) to integrate with NServiceBus? HOT 1
- How to handle mixed constructor parameters with multiple instances? ex. (DependentSvc svc, string p1, string p2) HOT 2
- d__15 MoveNext Could not load file or assembly 'Microsoft.Bcl.AsyncInterfaces, Version=1.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51' or one of its dependencies. HOT 1
- Single constructor requirement incompatible with ASP.NET Core HOT 2
- Resolution of IEnumerable<T> for non registered service T. HOT 1
- Hybrid Async Scoped MVC 5 + Async Method bug HOT 12
- First-chance exception thrown when registering open-generic type with type constraint HOT 3
- Degraded Register performance when AllowOverridingRegistrations is set to true HOT 10
- Exception thrown when attempting to inject COM interface HOT 4
- Remove COM support
- What would happen in case of duplicate registrations? HOT 1
- @inject support in .NET Core MVC views HOT 9
- ThreadScopedLifestyle vs AsyncScopedLifestyle HOT 1
- Sharing Instances between Parent and child scopes HOT 4
- Add debug information for Scopes HOT 2
- How to use decorator for shared interface? HOT 4
- What's best way to get .net 3.5 SimpleInjector Decorator Memory Leak bug fix? HOT 1
- Open-generic registrations for types with generic array generic type arguments can't be resolved
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from simpleinjector.