Creating HTTP proxies for services
Clash Royale CLAN TAG#URR8PPP
.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty margin-bottom:0;
up vote
6
down vote
favorite
I have a HTTP client factory that creates a separate HTTP proxy instance for each of my services - which are in the cloud. The factory creates each instance on the basis of an interface which the service implements. All of the proxies inherit from a base class called DalServiceBaseClient
which you will see in my code. I don't quite like the way the factory resolves the type to be returned based on the interface.
Here's a simplified version of my code:
Here's how I call my factory to create one of the possible proxies:
var gatewayFactory = new GatewayFactory();
var masterClient = gatewayFactory.CreateMasterClient("someBaseAddress");
Here that same method in GatewayFactory
:
public IMasterService CreateMasterClient(string baseAddress)
return (MasterServiceClient)CreateHttpClient<IMasterService>($"baseAddressapi/master/");
And here's the private create method in GatewayFactory
where all of the magic happens:
private DalServiceBaseClient CreateHttpClient<T>(string baseAddress)
var httpClient = new HttpClient
BaseAddress = new Uri(baseAddress)
;
if (typeof(T) == typeof(IMasterService))
return new MasterServiceClient(httpClient);
if (typeof(T) == typeof(ISlaveService))
return new SlaveServiceClient(httpClient);
if (typeof(T) == typeof(IRoleService))
return new RoleServiceClient(httpClient);
// A lot more ifs..
throw new ArgumentException("No type exists for the provided interface");
Since there are a lot of different proxies my method ends up having a lot of if
s and I haven't been able to find a more elegant way of doing this.
c# http factory-method proxy
add a comment |Â
up vote
6
down vote
favorite
I have a HTTP client factory that creates a separate HTTP proxy instance for each of my services - which are in the cloud. The factory creates each instance on the basis of an interface which the service implements. All of the proxies inherit from a base class called DalServiceBaseClient
which you will see in my code. I don't quite like the way the factory resolves the type to be returned based on the interface.
Here's a simplified version of my code:
Here's how I call my factory to create one of the possible proxies:
var gatewayFactory = new GatewayFactory();
var masterClient = gatewayFactory.CreateMasterClient("someBaseAddress");
Here that same method in GatewayFactory
:
public IMasterService CreateMasterClient(string baseAddress)
return (MasterServiceClient)CreateHttpClient<IMasterService>($"baseAddressapi/master/");
And here's the private create method in GatewayFactory
where all of the magic happens:
private DalServiceBaseClient CreateHttpClient<T>(string baseAddress)
var httpClient = new HttpClient
BaseAddress = new Uri(baseAddress)
;
if (typeof(T) == typeof(IMasterService))
return new MasterServiceClient(httpClient);
if (typeof(T) == typeof(ISlaveService))
return new SlaveServiceClient(httpClient);
if (typeof(T) == typeof(IRoleService))
return new RoleServiceClient(httpClient);
// A lot more ifs..
throw new ArgumentException("No type exists for the provided interface");
Since there are a lot of different proxies my method ends up having a lot of if
s and I haven't been able to find a more elegant way of doing this.
c# http factory-method proxy
1
Read up on strategy pattern
â Nkosi
Mar 20 at 13:12
3
Instead of having a method for this, consider using DI with a DI container. You can then useT
in the constructor and "automagically" get the right type.
â Hosch250
Mar 20 at 13:40
In the case you have't seen this: aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong
â Jesse C. Slicer
Mar 20 at 16:45
@JesseC.Slicer That's interesting. The code I'm working on is not greenfield though. It's a big migration from WCF to Service Fabric and we aim to change as little public code as possible. With that said, I think I can include a fix for that issue. Thanks.
â Alternatex
Mar 20 at 17:35
add a comment |Â
up vote
6
down vote
favorite
up vote
6
down vote
favorite
I have a HTTP client factory that creates a separate HTTP proxy instance for each of my services - which are in the cloud. The factory creates each instance on the basis of an interface which the service implements. All of the proxies inherit from a base class called DalServiceBaseClient
which you will see in my code. I don't quite like the way the factory resolves the type to be returned based on the interface.
Here's a simplified version of my code:
Here's how I call my factory to create one of the possible proxies:
var gatewayFactory = new GatewayFactory();
var masterClient = gatewayFactory.CreateMasterClient("someBaseAddress");
Here that same method in GatewayFactory
:
public IMasterService CreateMasterClient(string baseAddress)
return (MasterServiceClient)CreateHttpClient<IMasterService>($"baseAddressapi/master/");
And here's the private create method in GatewayFactory
where all of the magic happens:
private DalServiceBaseClient CreateHttpClient<T>(string baseAddress)
var httpClient = new HttpClient
BaseAddress = new Uri(baseAddress)
;
if (typeof(T) == typeof(IMasterService))
return new MasterServiceClient(httpClient);
if (typeof(T) == typeof(ISlaveService))
return new SlaveServiceClient(httpClient);
if (typeof(T) == typeof(IRoleService))
return new RoleServiceClient(httpClient);
// A lot more ifs..
throw new ArgumentException("No type exists for the provided interface");
Since there are a lot of different proxies my method ends up having a lot of if
s and I haven't been able to find a more elegant way of doing this.
c# http factory-method proxy
I have a HTTP client factory that creates a separate HTTP proxy instance for each of my services - which are in the cloud. The factory creates each instance on the basis of an interface which the service implements. All of the proxies inherit from a base class called DalServiceBaseClient
which you will see in my code. I don't quite like the way the factory resolves the type to be returned based on the interface.
Here's a simplified version of my code:
Here's how I call my factory to create one of the possible proxies:
var gatewayFactory = new GatewayFactory();
var masterClient = gatewayFactory.CreateMasterClient("someBaseAddress");
Here that same method in GatewayFactory
:
public IMasterService CreateMasterClient(string baseAddress)
return (MasterServiceClient)CreateHttpClient<IMasterService>($"baseAddressapi/master/");
And here's the private create method in GatewayFactory
where all of the magic happens:
private DalServiceBaseClient CreateHttpClient<T>(string baseAddress)
var httpClient = new HttpClient
BaseAddress = new Uri(baseAddress)
;
if (typeof(T) == typeof(IMasterService))
return new MasterServiceClient(httpClient);
if (typeof(T) == typeof(ISlaveService))
return new SlaveServiceClient(httpClient);
if (typeof(T) == typeof(IRoleService))
return new RoleServiceClient(httpClient);
// A lot more ifs..
throw new ArgumentException("No type exists for the provided interface");
Since there are a lot of different proxies my method ends up having a lot of if
s and I haven't been able to find a more elegant way of doing this.
c# http factory-method proxy
edited Mar 21 at 9:44
asked Mar 20 at 13:11
Alternatex
1807
1807
1
Read up on strategy pattern
â Nkosi
Mar 20 at 13:12
3
Instead of having a method for this, consider using DI with a DI container. You can then useT
in the constructor and "automagically" get the right type.
â Hosch250
Mar 20 at 13:40
In the case you have't seen this: aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong
â Jesse C. Slicer
Mar 20 at 16:45
@JesseC.Slicer That's interesting. The code I'm working on is not greenfield though. It's a big migration from WCF to Service Fabric and we aim to change as little public code as possible. With that said, I think I can include a fix for that issue. Thanks.
â Alternatex
Mar 20 at 17:35
add a comment |Â
1
Read up on strategy pattern
â Nkosi
Mar 20 at 13:12
3
Instead of having a method for this, consider using DI with a DI container. You can then useT
in the constructor and "automagically" get the right type.
â Hosch250
Mar 20 at 13:40
In the case you have't seen this: aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong
â Jesse C. Slicer
Mar 20 at 16:45
@JesseC.Slicer That's interesting. The code I'm working on is not greenfield though. It's a big migration from WCF to Service Fabric and we aim to change as little public code as possible. With that said, I think I can include a fix for that issue. Thanks.
â Alternatex
Mar 20 at 17:35
1
1
Read up on strategy pattern
â Nkosi
Mar 20 at 13:12
Read up on strategy pattern
â Nkosi
Mar 20 at 13:12
3
3
Instead of having a method for this, consider using DI with a DI container. You can then use
T
in the constructor and "automagically" get the right type.â Hosch250
Mar 20 at 13:40
Instead of having a method for this, consider using DI with a DI container. You can then use
T
in the constructor and "automagically" get the right type.â Hosch250
Mar 20 at 13:40
In the case you have't seen this: aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong
â Jesse C. Slicer
Mar 20 at 16:45
In the case you have't seen this: aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong
â Jesse C. Slicer
Mar 20 at 16:45
@JesseC.Slicer That's interesting. The code I'm working on is not greenfield though. It's a big migration from WCF to Service Fabric and we aim to change as little public code as possible. With that said, I think I can include a fix for that issue. Thanks.
â Alternatex
Mar 20 at 17:35
@JesseC.Slicer That's interesting. The code I'm working on is not greenfield though. It's a big migration from WCF to Service Fabric and we aim to change as little public code as possible. With that said, I think I can include a fix for that issue. Thanks.
â Alternatex
Mar 20 at 17:35
add a comment |Â
3 Answers
3
active
oldest
votes
up vote
13
down vote
accepted
Create a strategy to find the mapped implementation of the interface provided:
IDictionary<Type, Type> mappings; //Populated with all interface to implementation types
private DalServiceBaseClient CreateHttpClient<T>(string baseAddress)
var httpClient = new HttpClient
BaseAddress = new Uri(baseAddress)
;
var implementationType = mappings[typeof(T)];
if (implementationType == null)
throw new ArgumentException("No type exists for the provided interface");
return (DalServiceBaseClient)Activator.CreateInstance(implementationType, httpClient);
Note how reflection is used to create an instance of the implementation.
The mappings would be pre-populated and can look like...
mappings = new Dictionary<Type, Type>();
mappings[typeof(IMasterService)] = typeof(MasterServiceClient);
mappings[typeof(ISlaveService)] = typeof(SlaveServiceClient);
//...other types;
Another option if not too keen on using reflection, is to use a factory delegate which would allow a more strongly typed mapping.
IDictionary<Type, Func<HttpClient, DalServiceBaseClient>> mappings; //Populated with all interface to implementation types
private DalServiceBaseClient CreateHttpClient<T>(string baseAddress)
var httpClient = new HttpClient
BaseAddress = new Uri(baseAddress)
;
var implementationFactory = mappings[typeof(T)];
if (implementationFactory == null)
throw new ArgumentException("No type exists for the provided interface");
return implementationFactory(httpClient);
Which would be populated like
mappings = new Dictionary<Type, Func<HttpClient, DalServiceBaseClient>>();
mappings[typeof(IMasterService)] = (httpClient) => new MasterServiceClient(httpClient);
mappings[typeof(ISlaveService)] = (httpClient) => new SlaveServiceClient(httpClient);
//...other types;
Nice! This is just about as elegant as I can make it.
â Alternatex
Mar 20 at 14:15
1
or simply use Autofac or any other DI framework :-)
â t3chb0t
Mar 20 at 16:30
1
@jpmc26 no constructors... then how does you DI work? Via properties? Methods? Well, this does not look like you would be using any DI framework... no wonder you don't like it, I wouldn't either ;-]
â t3chb0t
Mar 20 at 20:24
1
@jpmc26 let me show you an example where I register several commands and other services in a container that then creates the entire application or another one also initializing the entire application from multiple modules nearly without anynew
s. Now if I need a new dependency anywhere I just add it to the container, to the constructor(s) and it magically works ;-D e.g. ASP.NET-Core would be useless without DI.
â t3chb0t
Mar 20 at 20:32
1
@t3chb0t I particularly like yourExitCode
class. :p
â Ian Kemp
Mar 20 at 23:06
 |Â
show 10 more comments
up vote
3
down vote
Too much indirection
Your public methods, e.g. CreateMasterClient
, already tell you which type the caller needs. When you design the shared private method the way you have, you add a lot of indirection that forces you to create convoluted logic just to pass along the information about which type is needed. What you gain is that you centralize the creation of the HttpClient
. This is not a good trade off.
Instead, use an alternate means of centralizing the construction of HttpClient
: create a factory method for that.
private HttpClient CreateHttpClient(string baseAddress)
return new HttpClient()
BaseAddress = new Uri(baseAddress)
;
And just construct the actual object directly in the public method:
public IMasterService CreateMasterClient(string baseAddress)
return new MasterServiceClient(
CreateHttpClient($"baseAddressapi/master/")
);
You can generalize this to any other common constructor parameters these types have by creating additional methods.
This is vastly easier for another developer to follow. No need for generics. No need for extra casts. No need for long if
statements or conditions based on type parameters. If your if
block actually contains more logic (like parameters specific to each type or actually choosing a different type for each interface), this also nicely separates out those pieces and makes it clear when they're actually relevant.
I don't have time right now, but I intend to expand later on whether you even need a factory class here.
â jpmc26
Mar 20 at 20:11
add a comment |Â
up vote
1
down vote
This is a wonderful case for dependency injection. You don't need any if
s, just a properly configured container. You can easily do it with a foreach
loop and let the DI framework do the heavy lifting of creating your objects.
Here's a simple example how you could implement it with Autofac
.
I need something to work with so I'll use this two interfaces...
interface IMasterServiceClient
interface ISlaveServiceClient
and their implementations:
class MasterServiceClient : IMasterServiceClient
public MasterServiceClient(HttpClient client)
Console.WriteLine($"GetType().Name initialized with client.BaseAddress");
class SlaveServiceClient : ISlaveServiceClient
public SlaveServiceClient(HttpClient client)
Console.WriteLine($"GetType().Name initialized with client.BaseAddress");
class ServiceClientUser
public ServiceClientUser(IMasterServiceClient masterServiceClient)
Console.WriteLine($"GetType().Name initialized");
The first step is to register all components in the container. In this case you can use tuples to define your interfaces, their implementations and base-addresses. Then you loop over this list and register each component.
At the bottom I also register one of the users that depends on the IMasterServiceClient
.
IContainer InitializeContainer()
var builder = new ContainerBuilder();
var clients = new(Type InterfaceType, Type ImplementationType, string BaseAddress)
(typeof(IMasterServiceClient), typeof(MasterServiceClient), "http://foo/api/master/"),
(typeof(ISlaveServiceClient), typeof(SlaveServiceClient), "http://bar/api/slave/")
;
foreach (var client in clients)
builder
.RegisterType(client.ImplementationType)
.WithParameter(new TypedParameter(typeof(HttpClient), new HttpClient
BaseAddress = new Uri(client.BaseAddress)
))
.As(client.InterfaceType)
// You only want to create it once and share the same instance among all components.
.InstancePerLifetimeScope();
builder
.RegisterType<ServiceClientUser>();
return builder.Build();
The second and last step is to let Autofac
construct all objects for you so that you can simply Resolve
it.
void Main()
using (var container = InitializeContainer())
using (var scope = container.BeginLifetimeScope())
var user = scope.Resolve<ServiceClientUser>();
This will print:
MasterServiceClient initialized with http://foo/
ServiceClientUser initialized
Keep in mind that this is just a simple example. You can do a lot more than that.
Your demonstration does not append the subpaths that the OP's factory methods does.
â jpmc26
Mar 21 at 15:32
@jpmc26 well, it doesn't have to be because it's just a demo and not a complete implementation; there is no such requirement as having to implement everything.
â t3chb0t
Mar 21 at 15:35
Your choice, but that is basically the reason for having the factory methods in the first place. The OP also specifically comments on it elsewhere. It's also not entirely obvious how you would handle this with dependency injection, since you can no longer simply call the constructor.
â jpmc26
Mar 21 at 15:58
@jpmc26 why would you want to call the constructor explicitly? You know all addresses so you initialize the input array and let the framework do the rest. Here you are, adjusted...
â t3chb0t
Mar 21 at 16:03
add a comment |Â
3 Answers
3
active
oldest
votes
3 Answers
3
active
oldest
votes
active
oldest
votes
active
oldest
votes
up vote
13
down vote
accepted
Create a strategy to find the mapped implementation of the interface provided:
IDictionary<Type, Type> mappings; //Populated with all interface to implementation types
private DalServiceBaseClient CreateHttpClient<T>(string baseAddress)
var httpClient = new HttpClient
BaseAddress = new Uri(baseAddress)
;
var implementationType = mappings[typeof(T)];
if (implementationType == null)
throw new ArgumentException("No type exists for the provided interface");
return (DalServiceBaseClient)Activator.CreateInstance(implementationType, httpClient);
Note how reflection is used to create an instance of the implementation.
The mappings would be pre-populated and can look like...
mappings = new Dictionary<Type, Type>();
mappings[typeof(IMasterService)] = typeof(MasterServiceClient);
mappings[typeof(ISlaveService)] = typeof(SlaveServiceClient);
//...other types;
Another option if not too keen on using reflection, is to use a factory delegate which would allow a more strongly typed mapping.
IDictionary<Type, Func<HttpClient, DalServiceBaseClient>> mappings; //Populated with all interface to implementation types
private DalServiceBaseClient CreateHttpClient<T>(string baseAddress)
var httpClient = new HttpClient
BaseAddress = new Uri(baseAddress)
;
var implementationFactory = mappings[typeof(T)];
if (implementationFactory == null)
throw new ArgumentException("No type exists for the provided interface");
return implementationFactory(httpClient);
Which would be populated like
mappings = new Dictionary<Type, Func<HttpClient, DalServiceBaseClient>>();
mappings[typeof(IMasterService)] = (httpClient) => new MasterServiceClient(httpClient);
mappings[typeof(ISlaveService)] = (httpClient) => new SlaveServiceClient(httpClient);
//...other types;
Nice! This is just about as elegant as I can make it.
â Alternatex
Mar 20 at 14:15
1
or simply use Autofac or any other DI framework :-)
â t3chb0t
Mar 20 at 16:30
1
@jpmc26 no constructors... then how does you DI work? Via properties? Methods? Well, this does not look like you would be using any DI framework... no wonder you don't like it, I wouldn't either ;-]
â t3chb0t
Mar 20 at 20:24
1
@jpmc26 let me show you an example where I register several commands and other services in a container that then creates the entire application or another one also initializing the entire application from multiple modules nearly without anynew
s. Now if I need a new dependency anywhere I just add it to the container, to the constructor(s) and it magically works ;-D e.g. ASP.NET-Core would be useless without DI.
â t3chb0t
Mar 20 at 20:32
1
@t3chb0t I particularly like yourExitCode
class. :p
â Ian Kemp
Mar 20 at 23:06
 |Â
show 10 more comments
up vote
13
down vote
accepted
Create a strategy to find the mapped implementation of the interface provided:
IDictionary<Type, Type> mappings; //Populated with all interface to implementation types
private DalServiceBaseClient CreateHttpClient<T>(string baseAddress)
var httpClient = new HttpClient
BaseAddress = new Uri(baseAddress)
;
var implementationType = mappings[typeof(T)];
if (implementationType == null)
throw new ArgumentException("No type exists for the provided interface");
return (DalServiceBaseClient)Activator.CreateInstance(implementationType, httpClient);
Note how reflection is used to create an instance of the implementation.
The mappings would be pre-populated and can look like...
mappings = new Dictionary<Type, Type>();
mappings[typeof(IMasterService)] = typeof(MasterServiceClient);
mappings[typeof(ISlaveService)] = typeof(SlaveServiceClient);
//...other types;
Another option if not too keen on using reflection, is to use a factory delegate which would allow a more strongly typed mapping.
IDictionary<Type, Func<HttpClient, DalServiceBaseClient>> mappings; //Populated with all interface to implementation types
private DalServiceBaseClient CreateHttpClient<T>(string baseAddress)
var httpClient = new HttpClient
BaseAddress = new Uri(baseAddress)
;
var implementationFactory = mappings[typeof(T)];
if (implementationFactory == null)
throw new ArgumentException("No type exists for the provided interface");
return implementationFactory(httpClient);
Which would be populated like
mappings = new Dictionary<Type, Func<HttpClient, DalServiceBaseClient>>();
mappings[typeof(IMasterService)] = (httpClient) => new MasterServiceClient(httpClient);
mappings[typeof(ISlaveService)] = (httpClient) => new SlaveServiceClient(httpClient);
//...other types;
Nice! This is just about as elegant as I can make it.
â Alternatex
Mar 20 at 14:15
1
or simply use Autofac or any other DI framework :-)
â t3chb0t
Mar 20 at 16:30
1
@jpmc26 no constructors... then how does you DI work? Via properties? Methods? Well, this does not look like you would be using any DI framework... no wonder you don't like it, I wouldn't either ;-]
â t3chb0t
Mar 20 at 20:24
1
@jpmc26 let me show you an example where I register several commands and other services in a container that then creates the entire application or another one also initializing the entire application from multiple modules nearly without anynew
s. Now if I need a new dependency anywhere I just add it to the container, to the constructor(s) and it magically works ;-D e.g. ASP.NET-Core would be useless without DI.
â t3chb0t
Mar 20 at 20:32
1
@t3chb0t I particularly like yourExitCode
class. :p
â Ian Kemp
Mar 20 at 23:06
 |Â
show 10 more comments
up vote
13
down vote
accepted
up vote
13
down vote
accepted
Create a strategy to find the mapped implementation of the interface provided:
IDictionary<Type, Type> mappings; //Populated with all interface to implementation types
private DalServiceBaseClient CreateHttpClient<T>(string baseAddress)
var httpClient = new HttpClient
BaseAddress = new Uri(baseAddress)
;
var implementationType = mappings[typeof(T)];
if (implementationType == null)
throw new ArgumentException("No type exists for the provided interface");
return (DalServiceBaseClient)Activator.CreateInstance(implementationType, httpClient);
Note how reflection is used to create an instance of the implementation.
The mappings would be pre-populated and can look like...
mappings = new Dictionary<Type, Type>();
mappings[typeof(IMasterService)] = typeof(MasterServiceClient);
mappings[typeof(ISlaveService)] = typeof(SlaveServiceClient);
//...other types;
Another option if not too keen on using reflection, is to use a factory delegate which would allow a more strongly typed mapping.
IDictionary<Type, Func<HttpClient, DalServiceBaseClient>> mappings; //Populated with all interface to implementation types
private DalServiceBaseClient CreateHttpClient<T>(string baseAddress)
var httpClient = new HttpClient
BaseAddress = new Uri(baseAddress)
;
var implementationFactory = mappings[typeof(T)];
if (implementationFactory == null)
throw new ArgumentException("No type exists for the provided interface");
return implementationFactory(httpClient);
Which would be populated like
mappings = new Dictionary<Type, Func<HttpClient, DalServiceBaseClient>>();
mappings[typeof(IMasterService)] = (httpClient) => new MasterServiceClient(httpClient);
mappings[typeof(ISlaveService)] = (httpClient) => new SlaveServiceClient(httpClient);
//...other types;
Create a strategy to find the mapped implementation of the interface provided:
IDictionary<Type, Type> mappings; //Populated with all interface to implementation types
private DalServiceBaseClient CreateHttpClient<T>(string baseAddress)
var httpClient = new HttpClient
BaseAddress = new Uri(baseAddress)
;
var implementationType = mappings[typeof(T)];
if (implementationType == null)
throw new ArgumentException("No type exists for the provided interface");
return (DalServiceBaseClient)Activator.CreateInstance(implementationType, httpClient);
Note how reflection is used to create an instance of the implementation.
The mappings would be pre-populated and can look like...
mappings = new Dictionary<Type, Type>();
mappings[typeof(IMasterService)] = typeof(MasterServiceClient);
mappings[typeof(ISlaveService)] = typeof(SlaveServiceClient);
//...other types;
Another option if not too keen on using reflection, is to use a factory delegate which would allow a more strongly typed mapping.
IDictionary<Type, Func<HttpClient, DalServiceBaseClient>> mappings; //Populated with all interface to implementation types
private DalServiceBaseClient CreateHttpClient<T>(string baseAddress)
var httpClient = new HttpClient
BaseAddress = new Uri(baseAddress)
;
var implementationFactory = mappings[typeof(T)];
if (implementationFactory == null)
throw new ArgumentException("No type exists for the provided interface");
return implementationFactory(httpClient);
Which would be populated like
mappings = new Dictionary<Type, Func<HttpClient, DalServiceBaseClient>>();
mappings[typeof(IMasterService)] = (httpClient) => new MasterServiceClient(httpClient);
mappings[typeof(ISlaveService)] = (httpClient) => new SlaveServiceClient(httpClient);
//...other types;
edited Mar 20 at 23:17
Ian Kemp
1054
1054
answered Mar 20 at 13:44
Nkosi
1,870619
1,870619
Nice! This is just about as elegant as I can make it.
â Alternatex
Mar 20 at 14:15
1
or simply use Autofac or any other DI framework :-)
â t3chb0t
Mar 20 at 16:30
1
@jpmc26 no constructors... then how does you DI work? Via properties? Methods? Well, this does not look like you would be using any DI framework... no wonder you don't like it, I wouldn't either ;-]
â t3chb0t
Mar 20 at 20:24
1
@jpmc26 let me show you an example where I register several commands and other services in a container that then creates the entire application or another one also initializing the entire application from multiple modules nearly without anynew
s. Now if I need a new dependency anywhere I just add it to the container, to the constructor(s) and it magically works ;-D e.g. ASP.NET-Core would be useless without DI.
â t3chb0t
Mar 20 at 20:32
1
@t3chb0t I particularly like yourExitCode
class. :p
â Ian Kemp
Mar 20 at 23:06
 |Â
show 10 more comments
Nice! This is just about as elegant as I can make it.
â Alternatex
Mar 20 at 14:15
1
or simply use Autofac or any other DI framework :-)
â t3chb0t
Mar 20 at 16:30
1
@jpmc26 no constructors... then how does you DI work? Via properties? Methods? Well, this does not look like you would be using any DI framework... no wonder you don't like it, I wouldn't either ;-]
â t3chb0t
Mar 20 at 20:24
1
@jpmc26 let me show you an example where I register several commands and other services in a container that then creates the entire application or another one also initializing the entire application from multiple modules nearly without anynew
s. Now if I need a new dependency anywhere I just add it to the container, to the constructor(s) and it magically works ;-D e.g. ASP.NET-Core would be useless without DI.
â t3chb0t
Mar 20 at 20:32
1
@t3chb0t I particularly like yourExitCode
class. :p
â Ian Kemp
Mar 20 at 23:06
Nice! This is just about as elegant as I can make it.
â Alternatex
Mar 20 at 14:15
Nice! This is just about as elegant as I can make it.
â Alternatex
Mar 20 at 14:15
1
1
or simply use Autofac or any other DI framework :-)
â t3chb0t
Mar 20 at 16:30
or simply use Autofac or any other DI framework :-)
â t3chb0t
Mar 20 at 16:30
1
1
@jpmc26 no constructors... then how does you DI work? Via properties? Methods? Well, this does not look like you would be using any DI framework... no wonder you don't like it, I wouldn't either ;-]
â t3chb0t
Mar 20 at 20:24
@jpmc26 no constructors... then how does you DI work? Via properties? Methods? Well, this does not look like you would be using any DI framework... no wonder you don't like it, I wouldn't either ;-]
â t3chb0t
Mar 20 at 20:24
1
1
@jpmc26 let me show you an example where I register several commands and other services in a container that then creates the entire application or another one also initializing the entire application from multiple modules nearly without any
new
s. Now if I need a new dependency anywhere I just add it to the container, to the constructor(s) and it magically works ;-D e.g. ASP.NET-Core would be useless without DI.â t3chb0t
Mar 20 at 20:32
@jpmc26 let me show you an example where I register several commands and other services in a container that then creates the entire application or another one also initializing the entire application from multiple modules nearly without any
new
s. Now if I need a new dependency anywhere I just add it to the container, to the constructor(s) and it magically works ;-D e.g. ASP.NET-Core would be useless without DI.â t3chb0t
Mar 20 at 20:32
1
1
@t3chb0t I particularly like your
ExitCode
class. :pâ Ian Kemp
Mar 20 at 23:06
@t3chb0t I particularly like your
ExitCode
class. :pâ Ian Kemp
Mar 20 at 23:06
 |Â
show 10 more comments
up vote
3
down vote
Too much indirection
Your public methods, e.g. CreateMasterClient
, already tell you which type the caller needs. When you design the shared private method the way you have, you add a lot of indirection that forces you to create convoluted logic just to pass along the information about which type is needed. What you gain is that you centralize the creation of the HttpClient
. This is not a good trade off.
Instead, use an alternate means of centralizing the construction of HttpClient
: create a factory method for that.
private HttpClient CreateHttpClient(string baseAddress)
return new HttpClient()
BaseAddress = new Uri(baseAddress)
;
And just construct the actual object directly in the public method:
public IMasterService CreateMasterClient(string baseAddress)
return new MasterServiceClient(
CreateHttpClient($"baseAddressapi/master/")
);
You can generalize this to any other common constructor parameters these types have by creating additional methods.
This is vastly easier for another developer to follow. No need for generics. No need for extra casts. No need for long if
statements or conditions based on type parameters. If your if
block actually contains more logic (like parameters specific to each type or actually choosing a different type for each interface), this also nicely separates out those pieces and makes it clear when they're actually relevant.
I don't have time right now, but I intend to expand later on whether you even need a factory class here.
â jpmc26
Mar 20 at 20:11
add a comment |Â
up vote
3
down vote
Too much indirection
Your public methods, e.g. CreateMasterClient
, already tell you which type the caller needs. When you design the shared private method the way you have, you add a lot of indirection that forces you to create convoluted logic just to pass along the information about which type is needed. What you gain is that you centralize the creation of the HttpClient
. This is not a good trade off.
Instead, use an alternate means of centralizing the construction of HttpClient
: create a factory method for that.
private HttpClient CreateHttpClient(string baseAddress)
return new HttpClient()
BaseAddress = new Uri(baseAddress)
;
And just construct the actual object directly in the public method:
public IMasterService CreateMasterClient(string baseAddress)
return new MasterServiceClient(
CreateHttpClient($"baseAddressapi/master/")
);
You can generalize this to any other common constructor parameters these types have by creating additional methods.
This is vastly easier for another developer to follow. No need for generics. No need for extra casts. No need for long if
statements or conditions based on type parameters. If your if
block actually contains more logic (like parameters specific to each type or actually choosing a different type for each interface), this also nicely separates out those pieces and makes it clear when they're actually relevant.
I don't have time right now, but I intend to expand later on whether you even need a factory class here.
â jpmc26
Mar 20 at 20:11
add a comment |Â
up vote
3
down vote
up vote
3
down vote
Too much indirection
Your public methods, e.g. CreateMasterClient
, already tell you which type the caller needs. When you design the shared private method the way you have, you add a lot of indirection that forces you to create convoluted logic just to pass along the information about which type is needed. What you gain is that you centralize the creation of the HttpClient
. This is not a good trade off.
Instead, use an alternate means of centralizing the construction of HttpClient
: create a factory method for that.
private HttpClient CreateHttpClient(string baseAddress)
return new HttpClient()
BaseAddress = new Uri(baseAddress)
;
And just construct the actual object directly in the public method:
public IMasterService CreateMasterClient(string baseAddress)
return new MasterServiceClient(
CreateHttpClient($"baseAddressapi/master/")
);
You can generalize this to any other common constructor parameters these types have by creating additional methods.
This is vastly easier for another developer to follow. No need for generics. No need for extra casts. No need for long if
statements or conditions based on type parameters. If your if
block actually contains more logic (like parameters specific to each type or actually choosing a different type for each interface), this also nicely separates out those pieces and makes it clear when they're actually relevant.
Too much indirection
Your public methods, e.g. CreateMasterClient
, already tell you which type the caller needs. When you design the shared private method the way you have, you add a lot of indirection that forces you to create convoluted logic just to pass along the information about which type is needed. What you gain is that you centralize the creation of the HttpClient
. This is not a good trade off.
Instead, use an alternate means of centralizing the construction of HttpClient
: create a factory method for that.
private HttpClient CreateHttpClient(string baseAddress)
return new HttpClient()
BaseAddress = new Uri(baseAddress)
;
And just construct the actual object directly in the public method:
public IMasterService CreateMasterClient(string baseAddress)
return new MasterServiceClient(
CreateHttpClient($"baseAddressapi/master/")
);
You can generalize this to any other common constructor parameters these types have by creating additional methods.
This is vastly easier for another developer to follow. No need for generics. No need for extra casts. No need for long if
statements or conditions based on type parameters. If your if
block actually contains more logic (like parameters specific to each type or actually choosing a different type for each interface), this also nicely separates out those pieces and makes it clear when they're actually relevant.
edited Mar 20 at 20:09
answered Mar 20 at 19:56
jpmc26
50127
50127
I don't have time right now, but I intend to expand later on whether you even need a factory class here.
â jpmc26
Mar 20 at 20:11
add a comment |Â
I don't have time right now, but I intend to expand later on whether you even need a factory class here.
â jpmc26
Mar 20 at 20:11
I don't have time right now, but I intend to expand later on whether you even need a factory class here.
â jpmc26
Mar 20 at 20:11
I don't have time right now, but I intend to expand later on whether you even need a factory class here.
â jpmc26
Mar 20 at 20:11
add a comment |Â
up vote
1
down vote
This is a wonderful case for dependency injection. You don't need any if
s, just a properly configured container. You can easily do it with a foreach
loop and let the DI framework do the heavy lifting of creating your objects.
Here's a simple example how you could implement it with Autofac
.
I need something to work with so I'll use this two interfaces...
interface IMasterServiceClient
interface ISlaveServiceClient
and their implementations:
class MasterServiceClient : IMasterServiceClient
public MasterServiceClient(HttpClient client)
Console.WriteLine($"GetType().Name initialized with client.BaseAddress");
class SlaveServiceClient : ISlaveServiceClient
public SlaveServiceClient(HttpClient client)
Console.WriteLine($"GetType().Name initialized with client.BaseAddress");
class ServiceClientUser
public ServiceClientUser(IMasterServiceClient masterServiceClient)
Console.WriteLine($"GetType().Name initialized");
The first step is to register all components in the container. In this case you can use tuples to define your interfaces, their implementations and base-addresses. Then you loop over this list and register each component.
At the bottom I also register one of the users that depends on the IMasterServiceClient
.
IContainer InitializeContainer()
var builder = new ContainerBuilder();
var clients = new(Type InterfaceType, Type ImplementationType, string BaseAddress)
(typeof(IMasterServiceClient), typeof(MasterServiceClient), "http://foo/api/master/"),
(typeof(ISlaveServiceClient), typeof(SlaveServiceClient), "http://bar/api/slave/")
;
foreach (var client in clients)
builder
.RegisterType(client.ImplementationType)
.WithParameter(new TypedParameter(typeof(HttpClient), new HttpClient
BaseAddress = new Uri(client.BaseAddress)
))
.As(client.InterfaceType)
// You only want to create it once and share the same instance among all components.
.InstancePerLifetimeScope();
builder
.RegisterType<ServiceClientUser>();
return builder.Build();
The second and last step is to let Autofac
construct all objects for you so that you can simply Resolve
it.
void Main()
using (var container = InitializeContainer())
using (var scope = container.BeginLifetimeScope())
var user = scope.Resolve<ServiceClientUser>();
This will print:
MasterServiceClient initialized with http://foo/
ServiceClientUser initialized
Keep in mind that this is just a simple example. You can do a lot more than that.
Your demonstration does not append the subpaths that the OP's factory methods does.
â jpmc26
Mar 21 at 15:32
@jpmc26 well, it doesn't have to be because it's just a demo and not a complete implementation; there is no such requirement as having to implement everything.
â t3chb0t
Mar 21 at 15:35
Your choice, but that is basically the reason for having the factory methods in the first place. The OP also specifically comments on it elsewhere. It's also not entirely obvious how you would handle this with dependency injection, since you can no longer simply call the constructor.
â jpmc26
Mar 21 at 15:58
@jpmc26 why would you want to call the constructor explicitly? You know all addresses so you initialize the input array and let the framework do the rest. Here you are, adjusted...
â t3chb0t
Mar 21 at 16:03
add a comment |Â
up vote
1
down vote
This is a wonderful case for dependency injection. You don't need any if
s, just a properly configured container. You can easily do it with a foreach
loop and let the DI framework do the heavy lifting of creating your objects.
Here's a simple example how you could implement it with Autofac
.
I need something to work with so I'll use this two interfaces...
interface IMasterServiceClient
interface ISlaveServiceClient
and their implementations:
class MasterServiceClient : IMasterServiceClient
public MasterServiceClient(HttpClient client)
Console.WriteLine($"GetType().Name initialized with client.BaseAddress");
class SlaveServiceClient : ISlaveServiceClient
public SlaveServiceClient(HttpClient client)
Console.WriteLine($"GetType().Name initialized with client.BaseAddress");
class ServiceClientUser
public ServiceClientUser(IMasterServiceClient masterServiceClient)
Console.WriteLine($"GetType().Name initialized");
The first step is to register all components in the container. In this case you can use tuples to define your interfaces, their implementations and base-addresses. Then you loop over this list and register each component.
At the bottom I also register one of the users that depends on the IMasterServiceClient
.
IContainer InitializeContainer()
var builder = new ContainerBuilder();
var clients = new(Type InterfaceType, Type ImplementationType, string BaseAddress)
(typeof(IMasterServiceClient), typeof(MasterServiceClient), "http://foo/api/master/"),
(typeof(ISlaveServiceClient), typeof(SlaveServiceClient), "http://bar/api/slave/")
;
foreach (var client in clients)
builder
.RegisterType(client.ImplementationType)
.WithParameter(new TypedParameter(typeof(HttpClient), new HttpClient
BaseAddress = new Uri(client.BaseAddress)
))
.As(client.InterfaceType)
// You only want to create it once and share the same instance among all components.
.InstancePerLifetimeScope();
builder
.RegisterType<ServiceClientUser>();
return builder.Build();
The second and last step is to let Autofac
construct all objects for you so that you can simply Resolve
it.
void Main()
using (var container = InitializeContainer())
using (var scope = container.BeginLifetimeScope())
var user = scope.Resolve<ServiceClientUser>();
This will print:
MasterServiceClient initialized with http://foo/
ServiceClientUser initialized
Keep in mind that this is just a simple example. You can do a lot more than that.
Your demonstration does not append the subpaths that the OP's factory methods does.
â jpmc26
Mar 21 at 15:32
@jpmc26 well, it doesn't have to be because it's just a demo and not a complete implementation; there is no such requirement as having to implement everything.
â t3chb0t
Mar 21 at 15:35
Your choice, but that is basically the reason for having the factory methods in the first place. The OP also specifically comments on it elsewhere. It's also not entirely obvious how you would handle this with dependency injection, since you can no longer simply call the constructor.
â jpmc26
Mar 21 at 15:58
@jpmc26 why would you want to call the constructor explicitly? You know all addresses so you initialize the input array and let the framework do the rest. Here you are, adjusted...
â t3chb0t
Mar 21 at 16:03
add a comment |Â
up vote
1
down vote
up vote
1
down vote
This is a wonderful case for dependency injection. You don't need any if
s, just a properly configured container. You can easily do it with a foreach
loop and let the DI framework do the heavy lifting of creating your objects.
Here's a simple example how you could implement it with Autofac
.
I need something to work with so I'll use this two interfaces...
interface IMasterServiceClient
interface ISlaveServiceClient
and their implementations:
class MasterServiceClient : IMasterServiceClient
public MasterServiceClient(HttpClient client)
Console.WriteLine($"GetType().Name initialized with client.BaseAddress");
class SlaveServiceClient : ISlaveServiceClient
public SlaveServiceClient(HttpClient client)
Console.WriteLine($"GetType().Name initialized with client.BaseAddress");
class ServiceClientUser
public ServiceClientUser(IMasterServiceClient masterServiceClient)
Console.WriteLine($"GetType().Name initialized");
The first step is to register all components in the container. In this case you can use tuples to define your interfaces, their implementations and base-addresses. Then you loop over this list and register each component.
At the bottom I also register one of the users that depends on the IMasterServiceClient
.
IContainer InitializeContainer()
var builder = new ContainerBuilder();
var clients = new(Type InterfaceType, Type ImplementationType, string BaseAddress)
(typeof(IMasterServiceClient), typeof(MasterServiceClient), "http://foo/api/master/"),
(typeof(ISlaveServiceClient), typeof(SlaveServiceClient), "http://bar/api/slave/")
;
foreach (var client in clients)
builder
.RegisterType(client.ImplementationType)
.WithParameter(new TypedParameter(typeof(HttpClient), new HttpClient
BaseAddress = new Uri(client.BaseAddress)
))
.As(client.InterfaceType)
// You only want to create it once and share the same instance among all components.
.InstancePerLifetimeScope();
builder
.RegisterType<ServiceClientUser>();
return builder.Build();
The second and last step is to let Autofac
construct all objects for you so that you can simply Resolve
it.
void Main()
using (var container = InitializeContainer())
using (var scope = container.BeginLifetimeScope())
var user = scope.Resolve<ServiceClientUser>();
This will print:
MasterServiceClient initialized with http://foo/
ServiceClientUser initialized
Keep in mind that this is just a simple example. You can do a lot more than that.
This is a wonderful case for dependency injection. You don't need any if
s, just a properly configured container. You can easily do it with a foreach
loop and let the DI framework do the heavy lifting of creating your objects.
Here's a simple example how you could implement it with Autofac
.
I need something to work with so I'll use this two interfaces...
interface IMasterServiceClient
interface ISlaveServiceClient
and their implementations:
class MasterServiceClient : IMasterServiceClient
public MasterServiceClient(HttpClient client)
Console.WriteLine($"GetType().Name initialized with client.BaseAddress");
class SlaveServiceClient : ISlaveServiceClient
public SlaveServiceClient(HttpClient client)
Console.WriteLine($"GetType().Name initialized with client.BaseAddress");
class ServiceClientUser
public ServiceClientUser(IMasterServiceClient masterServiceClient)
Console.WriteLine($"GetType().Name initialized");
The first step is to register all components in the container. In this case you can use tuples to define your interfaces, their implementations and base-addresses. Then you loop over this list and register each component.
At the bottom I also register one of the users that depends on the IMasterServiceClient
.
IContainer InitializeContainer()
var builder = new ContainerBuilder();
var clients = new(Type InterfaceType, Type ImplementationType, string BaseAddress)
(typeof(IMasterServiceClient), typeof(MasterServiceClient), "http://foo/api/master/"),
(typeof(ISlaveServiceClient), typeof(SlaveServiceClient), "http://bar/api/slave/")
;
foreach (var client in clients)
builder
.RegisterType(client.ImplementationType)
.WithParameter(new TypedParameter(typeof(HttpClient), new HttpClient
BaseAddress = new Uri(client.BaseAddress)
))
.As(client.InterfaceType)
// You only want to create it once and share the same instance among all components.
.InstancePerLifetimeScope();
builder
.RegisterType<ServiceClientUser>();
return builder.Build();
The second and last step is to let Autofac
construct all objects for you so that you can simply Resolve
it.
void Main()
using (var container = InitializeContainer())
using (var scope = container.BeginLifetimeScope())
var user = scope.Resolve<ServiceClientUser>();
This will print:
MasterServiceClient initialized with http://foo/
ServiceClientUser initialized
Keep in mind that this is just a simple example. You can do a lot more than that.
edited Mar 21 at 16:03
answered Mar 21 at 9:27
t3chb0t
32.1k54195
32.1k54195
Your demonstration does not append the subpaths that the OP's factory methods does.
â jpmc26
Mar 21 at 15:32
@jpmc26 well, it doesn't have to be because it's just a demo and not a complete implementation; there is no such requirement as having to implement everything.
â t3chb0t
Mar 21 at 15:35
Your choice, but that is basically the reason for having the factory methods in the first place. The OP also specifically comments on it elsewhere. It's also not entirely obvious how you would handle this with dependency injection, since you can no longer simply call the constructor.
â jpmc26
Mar 21 at 15:58
@jpmc26 why would you want to call the constructor explicitly? You know all addresses so you initialize the input array and let the framework do the rest. Here you are, adjusted...
â t3chb0t
Mar 21 at 16:03
add a comment |Â
Your demonstration does not append the subpaths that the OP's factory methods does.
â jpmc26
Mar 21 at 15:32
@jpmc26 well, it doesn't have to be because it's just a demo and not a complete implementation; there is no such requirement as having to implement everything.
â t3chb0t
Mar 21 at 15:35
Your choice, but that is basically the reason for having the factory methods in the first place. The OP also specifically comments on it elsewhere. It's also not entirely obvious how you would handle this with dependency injection, since you can no longer simply call the constructor.
â jpmc26
Mar 21 at 15:58
@jpmc26 why would you want to call the constructor explicitly? You know all addresses so you initialize the input array and let the framework do the rest. Here you are, adjusted...
â t3chb0t
Mar 21 at 16:03
Your demonstration does not append the subpaths that the OP's factory methods does.
â jpmc26
Mar 21 at 15:32
Your demonstration does not append the subpaths that the OP's factory methods does.
â jpmc26
Mar 21 at 15:32
@jpmc26 well, it doesn't have to be because it's just a demo and not a complete implementation; there is no such requirement as having to implement everything.
â t3chb0t
Mar 21 at 15:35
@jpmc26 well, it doesn't have to be because it's just a demo and not a complete implementation; there is no such requirement as having to implement everything.
â t3chb0t
Mar 21 at 15:35
Your choice, but that is basically the reason for having the factory methods in the first place. The OP also specifically comments on it elsewhere. It's also not entirely obvious how you would handle this with dependency injection, since you can no longer simply call the constructor.
â jpmc26
Mar 21 at 15:58
Your choice, but that is basically the reason for having the factory methods in the first place. The OP also specifically comments on it elsewhere. It's also not entirely obvious how you would handle this with dependency injection, since you can no longer simply call the constructor.
â jpmc26
Mar 21 at 15:58
@jpmc26 why would you want to call the constructor explicitly? You know all addresses so you initialize the input array and let the framework do the rest. Here you are, adjusted...
â t3chb0t
Mar 21 at 16:03
@jpmc26 why would you want to call the constructor explicitly? You know all addresses so you initialize the input array and let the framework do the rest. Here you are, adjusted...
â t3chb0t
Mar 21 at 16:03
add a comment |Â
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f190026%2fcreating-http-proxies-for-services%23new-answer', 'question_page');
);
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
1
Read up on strategy pattern
â Nkosi
Mar 20 at 13:12
3
Instead of having a method for this, consider using DI with a DI container. You can then use
T
in the constructor and "automagically" get the right type.â Hosch250
Mar 20 at 13:40
In the case you have't seen this: aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong
â Jesse C. Slicer
Mar 20 at 16:45
@JesseC.Slicer That's interesting. The code I'm working on is not greenfield though. It's a big migration from WCF to Service Fabric and we aim to change as little public code as possible. With that said, I think I can include a fix for that issue. Thanks.
â Alternatex
Mar 20 at 17:35