Browse Source

Resolved #726: Introduce IHttpClientProxy<T> and explicit usage of client proxies.

pull/732/head
Halil ibrahim Kalkan 7 years ago
parent
commit
bb2b87165f
  1. 96
      docs/en/AspNetCore/Dynamic-CSharp-API-Clients.md
  2. 114
      framework/src/Volo.Abp.Http.Client/Microsoft/Extensions/DependencyInjection/ServiceCollectionDynamicHttpClientProxyExtensions.cs
  3. 12
      framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/HttpClientProxy.cs
  4. 7
      framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/IHttpClientProxy.cs

96
docs/en/AspNetCore/Dynamic-CSharp-API-Clients.md

@ -7,13 +7,13 @@ ABP can dynamically create C# API client proxies to call remote HTTP services (R
Your service/controller should implement an interface that is shared between the server and the client. So, first define a service interface in a shared library project. Example:
````csharp
public interface IBookService : IApplicationService
public interface IBookAppService : IApplicationService
{
Task<List<BookDto>> GetListAsync();
}
````
Your interface should implement the `IRemoteService` interface. Since the `IApplicationService` inherits the `IRemoteService` interface, the `IBookService` above satisfies this condition.
Your interface should implement the `IRemoteService` interface to be automatically discovered. Since the `IApplicationService` inherits the `IRemoteService` interface, the `IBookAppService` above satisfies this condition.
Implement this class in your service application. You can use [auto API controller system](Auto-API-Controllers.md) to expose the service as a REST API endpoint.
@ -45,13 +45,6 @@ public class MyClientAppModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
//Configure remote end point
context.Services.Configure<RemoteServiceOptions>(options =>
{
options.RemoteServices.Default =
new RemoteServiceConfiguration("http://localhost:53929/");
});
//Create dynamic client proxies
context.Services.AddHttpClientProxies(
typeof(BookStoreApplicationModule).Assembly
@ -60,10 +53,24 @@ public class MyClientAppModule : AbpModule
}
````
`RemoteServiceOptions` is used to configure endpoints for remote services (This example sets the default endpoint while you can have different service endpoints used by different clients. See the "Multiple Remote Service Endpoint" section).
`AddHttpClientProxies` method gets an assembly, finds all service interfaces in the given assembly, creates and registers proxy classes.
### Endpoint Configuration
`RemoteServices` section in the `appsettings.json` file is used to get remote service address by default. Simplest configuration is shown below:
````
{
"RemoteServices": {
"Default": {
"BaseUrl": "http://localhost:53929/"
}
}
}
````
See the "RemoteServiceOptions" section below for more detailed configuration.
## Usage
It's straightforward to use. Just inject the service interface in the client application code:
@ -71,9 +78,9 @@ It's straightforward to use. Just inject the service interface in the client app
````csharp
public class MyService : ITransientDependency
{
private readonly IBookService _bookService;
private readonly IBookAppService _bookService;
public MyService(IBookService bookService)
public MyService(IBookAppService bookService)
{
_bookService = bookService;
}
@ -89,40 +96,34 @@ public class MyService : ITransientDependency
}
````
This sample injects the `IBookService` service interface defined above. The dynamic client proxy implementation makes an HTTP call whenever a service method is called by the client.
This sample injects the `IBookAppService` service interface defined above. The dynamic client proxy implementation makes an HTTP call whenever a service method is called by the client.
## Configuration Details
### IHttpClientProxy Interface
### RemoteServiceOptions
While you can inject `IBookAppService` like above to use the client proxy, you could inject `IHttpClientProxy<IBookAppService>` for a more explicit usage. In this case you will use the `Service` property of the `IHttpClientProxy<T>` interface.
While you can configure `RemoteServiceOptions` as the example shown above, you can let it to read from your `appsettings.json` file. Add a `RemoteServices` section to your `appsettings.json` file:
## Configuration
````json
{
"RemoteServices": {
"Default": {
"BaseUrl": "http://localhost:53929/"
}
}
}
````
### RemoteServiceOptions
Then you can pass your `IConfigurationRoot` instance directly to the `Configure<RemoteServiceOptions>()` method as shown below:
`RemoteServiceOptions` is automatically set from the `appsettings.json` by default. Alternatively, you can use `Configure` method to set or override it. Example:
````csharp
var configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json")
.Build();
context.Services.Configure<RemoteServiceOptions>(configuration);
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.Configure<RemoteServiceOptions>(options =>
{
options.RemoteServices.Default =
new RemoteServiceConfiguration("http://localhost:53929/");
});
//...
}
````
This approach is useful since it's easy to change the configuration later without touching to the code.
### Multiple Remote Service Endpoints
#### Multiple Remote Service Endpoint
The examples above have configured the "Default" remote service endpoint. You may have different endpoints for different services (as like in a microservice approach where each microservice has different endpoint). In this case, you can add other endpoints to your configuration file:
The examples above have configured the "Default" remote service endpoint. You may have different endpoints for different services (as like in a microservice approach where each microservice has different endpoints). In this case, you can add other endpoints to your configuration file:
````json
{
@ -137,10 +138,6 @@ The examples above have configured the "Default" remote service endpoint. You ma
}
````
See the next section to learn how to use this new endpoint.
### AddHttpClientProxies Method
`AddHttpClientProxies` method can get an additional parameter for the remote service name. Example:
````csharp
@ -150,4 +147,19 @@ context.Services.AddHttpClientProxies(
);
````
`remoteServiceName` parameter matches the service endpoint configured via `RemoteServiceOptions`. If the `BookStore` endpoint is not defined then it fallbacks to the `Default` endpoint.
`remoteServiceName` parameter matches the service endpoint configured via `RemoteServiceOptions`. If the `BookStore` endpoint is not defined then it fallbacks to the `Default` endpoint.
### As Default Services
When you create a service proxy for `IBookAppService`, you can directly inject the `IBookAppService` to use the proxy client (as shown in the usage section). You can pass `asDefaultServices: false` to the `AddHttpClientProxies` method to disable this feature.
````csharp
context.Services.AddHttpClientProxies(
typeof(BookStoreApplicationModule).Assembly,
asDefaultServices: false
);
````
If you disable `asDefaultServices`, you can only use `IHttpClientProxy<T>` interface to use the client proxies (see the related section above).
Using `asDefaultServices: false` may only be needed if your application has multiple implementation of the service, so you want to distinguish the HTTP client proxy and do not want to override the other implementation.

114
framework/src/Volo.Abp.Http.Client/Microsoft/Extensions/DependencyInjection/ServiceCollectionDynamicHttpClientProxyExtensions.cs

@ -2,6 +2,7 @@
using System.Linq;
using System.Reflection;
using Castle.DynamicProxy;
using JetBrains.Annotations;
using Volo.Abp;
using Volo.Abp.Castle.DynamicProxy;
using Volo.Abp.Http.Client;
@ -13,8 +14,28 @@ namespace Microsoft.Extensions.DependencyInjection
{
private static readonly ProxyGenerator ProxyGeneratorInstance = new ProxyGenerator();
public static IServiceCollection AddHttpClientProxies(this IServiceCollection services, Assembly assembly, string remoteServiceName = RemoteServiceConfigurationDictionary.DefaultName)
/// <summary>
/// Registers HTTP Client Proxies for all public interfaces
/// extend the <see cref="IRemoteService"/> interface in the
/// given <paramref name="assembly"/>.
/// </summary>
/// <param name="services">Service collection</param>
/// <param name="assembly">The assembly containing the service interfaces</param>
/// <param name="remoteServiceConfigurationName">
/// The name of the remote service configuration to be used by the HTTP Client proxies.
/// See <see cref="RemoteServiceOptions"/>.
/// </param>
/// <param name="asDefaultService">
/// True, to register the HTTP client proxy as the default implementation for the services.
/// </param>
public static IServiceCollection AddHttpClientProxies(
[NotNull] this IServiceCollection services,
[NotNull] Assembly assembly,
[NotNull] string remoteServiceConfigurationName = RemoteServiceConfigurationDictionary.DefaultName,
bool asDefaultService = true)
{
Check.NotNull(services, nameof(assembly));
//TODO: Make a configuration option and add remoteServiceName inside it!
//TODO: Add option to change type filter
@ -24,36 +45,101 @@ namespace Microsoft.Extensions.DependencyInjection
foreach (var serviceType in serviceTypes)
{
services.AddHttpClientProxy(serviceType, remoteServiceName);
services.AddHttpClientProxy(
serviceType,
remoteServiceConfigurationName,
asDefaultService
);
}
return services;
}
public static IServiceCollection AddHttpClientProxy<T>(this IServiceCollection services, string remoteServiceName = RemoteServiceConfigurationDictionary.DefaultName)
/// <summary>
/// Registers HTTP Client Proxy for given service type <typeparamref name="T"/>.
/// </summary>
/// <typeparam name="T">Type of the service</typeparam>
/// <param name="services">Service collection</param>
/// <param name="remoteServiceConfigurationName">
/// The name of the remote service configuration to be used by the HTTP Client proxy.
/// See <see cref="RemoteServiceOptions"/>.
/// </param>
/// <param name="asDefaultService">
/// True, to register the HTTP client proxy as the default implementation for the service <typeparamref name="T"/>.
/// </param>
public static IServiceCollection AddHttpClientProxy<T>(
[NotNull] this IServiceCollection services,
[NotNull] string remoteServiceConfigurationName = RemoteServiceConfigurationDictionary.DefaultName,
bool asDefaultService = true)
{
return services.AddHttpClientProxy(typeof(T), remoteServiceName);
return services.AddHttpClientProxy(
typeof(T),
remoteServiceConfigurationName,
asDefaultService
);
}
public static IServiceCollection AddHttpClientProxy(this IServiceCollection services, Type type, string remoteServiceName = RemoteServiceConfigurationDictionary.DefaultName)
/// <summary>
/// Registers HTTP Client Proxy for given service <paramref name="type"/>.
/// </summary>
/// <param name="services">Service collection</param>
/// <param name="type">Type of the service</param>
/// <param name="remoteServiceConfigurationName">
/// The name of the remote service configuration to be used by the HTTP Client proxy.
/// See <see cref="RemoteServiceOptions"/>.
/// </param>
/// <param name="asDefaultService">
/// True, to register the HTTP client proxy as the default implementation for the service <paramref name="type"/>.
/// </param>
public static IServiceCollection AddHttpClientProxy(
[NotNull] this IServiceCollection services,
[NotNull] Type type,
[NotNull] string remoteServiceConfigurationName = RemoteServiceConfigurationDictionary.DefaultName,
bool asDefaultService = true)
{
Check.NotNull(services, nameof(services));
Check.NotNull(type, nameof(type));
Check.NotNull(remoteServiceConfigurationName, nameof(remoteServiceConfigurationName));
services.Configure<AbpHttpClientOptions>(options =>
{
options.HttpClientProxies[type] = new DynamicHttpClientProxyConfig(type, remoteServiceName);
options.HttpClientProxies[type] = new DynamicHttpClientProxyConfig(type, remoteServiceConfigurationName);
});
var interceptorType = typeof(DynamicHttpProxyInterceptor<>).MakeGenericType(type);
services.AddTransient(interceptorType);
var interceptorAdapterType = typeof(CastleAbpInterceptorAdapter<>).MakeGenericType(interceptorType);
return services.AddTransient(
type,
serviceProvider => ProxyGeneratorInstance
.CreateInterfaceProxyWithoutTarget(
type,
(IInterceptor) serviceProvider.GetRequiredService(interceptorAdapterType)
)
);
if (asDefaultService)
{
services.AddTransient(
type,
serviceProvider => ProxyGeneratorInstance
.CreateInterfaceProxyWithoutTarget(
type,
(IInterceptor)serviceProvider.GetRequiredService(interceptorAdapterType)
)
);
}
services.AddTransient(
typeof(IHttpClientProxy<>).MakeGenericType(type),
serviceProvider =>
{
var service = ProxyGeneratorInstance
.CreateInterfaceProxyWithoutTarget(
type,
(IInterceptor) serviceProvider.GetRequiredService(interceptorAdapterType)
);
return Activator.CreateInstance(
typeof(HttpClientProxy<>).MakeGenericType(type),
service
);
});
return services;
}
}
}

12
framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/HttpClientProxy.cs

@ -0,0 +1,12 @@
namespace Volo.Abp.Http.Client.DynamicProxying
{
public class HttpClientProxy<TRemoteService> : IHttpClientProxy<TRemoteService>
{
public TRemoteService Service { get; }
public HttpClientProxy(TRemoteService service)
{
Service = service;
}
}
}

7
framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/IHttpClientProxy.cs

@ -0,0 +1,7 @@
namespace Volo.Abp.Http.Client.DynamicProxying
{
public interface IHttpClientProxy<out TRemoteService>
{
TRemoteService Service { get; }
}
}
Loading…
Cancel
Save