diff --git a/docs/en/AspNetCore/Dynamic-CSharp-API-Clients.md b/docs/en/AspNetCore/Dynamic-CSharp-API-Clients.md index eee2abacc0b..a96526318f2 100644 --- a/docs/en/AspNetCore/Dynamic-CSharp-API-Clients.md +++ b/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> 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(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` for a more explicit usage. In this case you will use the `Service` property of the `IHttpClientProxy` 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()` 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(configuration); +public override void ConfigureServices(ServiceConfigurationContext context) +{ + context.Services.Configure(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. \ No newline at end of file +`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` 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. \ No newline at end of file diff --git a/framework/src/Volo.Abp.Http.Client/Microsoft/Extensions/DependencyInjection/ServiceCollectionDynamicHttpClientProxyExtensions.cs b/framework/src/Volo.Abp.Http.Client/Microsoft/Extensions/DependencyInjection/ServiceCollectionDynamicHttpClientProxyExtensions.cs index cb120b1315f..40e2f11b730 100644 --- a/framework/src/Volo.Abp.Http.Client/Microsoft/Extensions/DependencyInjection/ServiceCollectionDynamicHttpClientProxyExtensions.cs +++ b/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 @@ public static class ServiceCollectionDynamicHttpClientProxyExtensions { private static readonly ProxyGenerator ProxyGeneratorInstance = new ProxyGenerator(); - public static IServiceCollection AddHttpClientProxies(this IServiceCollection services, Assembly assembly, string remoteServiceName = RemoteServiceConfigurationDictionary.DefaultName) + /// + /// Registers HTTP Client Proxies for all public interfaces + /// extend the interface in the + /// given . + /// + /// Service collection + /// The assembly containing the service interfaces + /// + /// The name of the remote service configuration to be used by the HTTP Client proxies. + /// See . + /// + /// + /// True, to register the HTTP client proxy as the default implementation for the services. + /// + 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 @@ public static IServiceCollection AddHttpClientProxies(this IServiceCollection se foreach (var serviceType in serviceTypes) { - services.AddHttpClientProxy(serviceType, remoteServiceName); + services.AddHttpClientProxy( + serviceType, + remoteServiceConfigurationName, + asDefaultService + ); } return services; } - public static IServiceCollection AddHttpClientProxy(this IServiceCollection services, string remoteServiceName = RemoteServiceConfigurationDictionary.DefaultName) + /// + /// Registers HTTP Client Proxy for given service type . + /// + /// Type of the service + /// Service collection + /// + /// The name of the remote service configuration to be used by the HTTP Client proxy. + /// See . + /// + /// + /// True, to register the HTTP client proxy as the default implementation for the service . + /// + public static IServiceCollection AddHttpClientProxy( + [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) + /// + /// Registers HTTP Client Proxy for given service . + /// + /// Service collection + /// Type of the service + /// + /// The name of the remote service configuration to be used by the HTTP Client proxy. + /// See . + /// + /// + /// True, to register the HTTP client proxy as the default implementation for the service . + /// + 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(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; } } } diff --git a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/HttpClientProxy.cs b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/HttpClientProxy.cs new file mode 100644 index 00000000000..6afa2e6dee5 --- /dev/null +++ b/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 : IHttpClientProxy + { + public TRemoteService Service { get; } + + public HttpClientProxy(TRemoteService service) + { + Service = service; + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/IHttpClientProxy.cs b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/IHttpClientProxy.cs new file mode 100644 index 00000000000..3b962f523d0 --- /dev/null +++ b/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 + { + TRemoteService Service { get; } + } +}