From 956f8c23b9bb7b760832bd05002f1c20f5d9e392 Mon Sep 17 00:00:00 2001 From: Halil ibrahim Kalkan Date: Fri, 21 Dec 2018 17:02:32 +0300 Subject: [PATCH 1/2] Get remote endpoint from the configuration. --- .../Acme.BookStore.ConsoleApiClient.csproj | 7 +++++++ .../ApiClientDemoService.cs | 1 + .../ConsoleApiClientModule.cs | 14 +++++++++----- .../Acme.BookStore.ConsoleApiClient/Program.cs | 2 ++ .../appsettings.json | 7 +++++++ 5 files changed, 26 insertions(+), 5 deletions(-) create mode 100644 samples/BookStore/test/Acme.BookStore.ConsoleApiClient/appsettings.json diff --git a/samples/BookStore/test/Acme.BookStore.ConsoleApiClient/Acme.BookStore.ConsoleApiClient.csproj b/samples/BookStore/test/Acme.BookStore.ConsoleApiClient/Acme.BookStore.ConsoleApiClient.csproj index 0582d6f067..bc66bc8ded 100644 --- a/samples/BookStore/test/Acme.BookStore.ConsoleApiClient/Acme.BookStore.ConsoleApiClient.csproj +++ b/samples/BookStore/test/Acme.BookStore.ConsoleApiClient/Acme.BookStore.ConsoleApiClient.csproj @@ -8,7 +8,14 @@ + + + + Always + + + diff --git a/samples/BookStore/test/Acme.BookStore.ConsoleApiClient/ApiClientDemoService.cs b/samples/BookStore/test/Acme.BookStore.ConsoleApiClient/ApiClientDemoService.cs index d16746bdb8..5aa617da5e 100644 --- a/samples/BookStore/test/Acme.BookStore.ConsoleApiClient/ApiClientDemoService.cs +++ b/samples/BookStore/test/Acme.BookStore.ConsoleApiClient/ApiClientDemoService.cs @@ -16,6 +16,7 @@ namespace Acme.BookStore.ConsoleApiClient public async Task RunAsync() { + //While it seems like a regular method call, it actually calls a remote REST API. var output = await _bookAppService.GetListAsync(new PagedAndSortedResultRequestDto()); foreach (var bookDto in output.Items) { diff --git a/samples/BookStore/test/Acme.BookStore.ConsoleApiClient/ConsoleApiClientModule.cs b/samples/BookStore/test/Acme.BookStore.ConsoleApiClient/ConsoleApiClientModule.cs index 8bd13aee76..ed5c08e4f1 100644 --- a/samples/BookStore/test/Acme.BookStore.ConsoleApiClient/ConsoleApiClientModule.cs +++ b/samples/BookStore/test/Acme.BookStore.ConsoleApiClient/ConsoleApiClientModule.cs @@ -1,4 +1,6 @@ -using Microsoft.Extensions.DependencyInjection; +using System.IO; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; using Volo.Abp.Autofac; using Volo.Abp.Http.Client; using Volo.Abp.Modularity; @@ -14,10 +16,12 @@ namespace Acme.BookStore.ConsoleApiClient { public override void ConfigureServices(ServiceConfigurationContext context) { - context.Services.Configure(options => - { - options.RemoteServices.Default = new RemoteServiceConfiguration("http://localhost:53929/"); - }); + var configuration = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json") + .Build(); + + context.Services.Configure(configuration); context.Services.AddHttpClientProxies( typeof(BookStoreApplicationModule).Assembly diff --git a/samples/BookStore/test/Acme.BookStore.ConsoleApiClient/Program.cs b/samples/BookStore/test/Acme.BookStore.ConsoleApiClient/Program.cs index 3be13e1281..2e0ffa25c7 100644 --- a/samples/BookStore/test/Acme.BookStore.ConsoleApiClient/Program.cs +++ b/samples/BookStore/test/Acme.BookStore.ConsoleApiClient/Program.cs @@ -5,6 +5,8 @@ using Volo.Abp.Threading; namespace Acme.BookStore.ConsoleApiClient { + /* Before running this application, ensure that the Acme.BookStore.Web application is running. + */ class Program { static void Main(string[] args) diff --git a/samples/BookStore/test/Acme.BookStore.ConsoleApiClient/appsettings.json b/samples/BookStore/test/Acme.BookStore.ConsoleApiClient/appsettings.json new file mode 100644 index 0000000000..1f3cc219b5 --- /dev/null +++ b/samples/BookStore/test/Acme.BookStore.ConsoleApiClient/appsettings.json @@ -0,0 +1,7 @@ +{ + "RemoteServices": { + "Default": { + "BaseUrl": "http://localhost:53929/" + } + } +} \ No newline at end of file From c82c8ae2db1463484f2254405c3565fd72885255 Mon Sep 17 00:00:00 2001 From: Halil ibrahim Kalkan Date: Fri, 21 Dec 2018 17:03:05 +0300 Subject: [PATCH 2/2] Resolved #651: Document Dynamic C# API Clients. --- .../AspNetCore/Dynamic-CSharp-API-Clients.md | 153 ++++++++++++++++++ docs/en/docs-nav.json | 4 + 2 files changed, 157 insertions(+) create mode 100644 docs/en/AspNetCore/Dynamic-CSharp-API-Clients.md diff --git a/docs/en/AspNetCore/Dynamic-CSharp-API-Clients.md b/docs/en/AspNetCore/Dynamic-CSharp-API-Clients.md new file mode 100644 index 0000000000..eee2abacc0 --- /dev/null +++ b/docs/en/AspNetCore/Dynamic-CSharp-API-Clients.md @@ -0,0 +1,153 @@ +# Dynamic C# API Clients + +ABP can dynamically create C# API client proxies to call remote HTTP services (REST APIs). In this way, you don't need to deal with `HttpClient` and other low level HTTP features to call remote services and get results. + +## Service Interface + +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 +{ + Task> GetListAsync(); +} +```` + +Your interface should implement the `IRemoteService` interface. Since the `IApplicationService` inherits the `IRemoteService` interface, the `IBookService` 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. + +## Client Proxy Generation + +First, add [Volo.Abp.Http.Client](https://www.nuget.org/packages/Volo.Abp.Http.Client) nuget package to your client project: + +```` +Install-Package Volo.Abp.Http.Client +```` + +Then add `AbpHttpClientModule` dependency to your module: + +````csharp +[DependsOn(typeof(AbpHttpClientModule))] //add the dependency +public class MyClientAppModule : AbpModule +{ +} +```` + +Now, it's ready to create the client proxies. Example: + +````csharp +[DependsOn( + typeof(AbpHttpClientModule), //used to create client proxies + typeof(BookStoreApplicationModule) //contains the application service interfaces + )] +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 + ); + } +} +```` + +`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. + +## Usage + +It's straightforward to use. Just inject the service interface in the client application code: + +````csharp +public class MyService : ITransientDependency +{ + private readonly IBookService _bookService; + + public MyService(IBookService bookService) + { + _bookService = bookService; + } + + public async Task DoIt() + { + var books = await _bookService.GetListAsync(); + foreach (var book in books) + { + Console.WriteLine($"[BOOK {book.Id}] Name={book.Name}"); + } + } +} +```` + +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. + +## Configuration Details + +### RemoteServiceOptions + +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: + +````json +{ + "RemoteServices": { + "Default": { + "BaseUrl": "http://localhost:53929/" + } + } +} +```` + +Then you can pass your `IConfigurationRoot` instance directly to the `Configure()` method as shown below: + +````csharp +var configuration = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json") + .Build(); + +context.Services.Configure(configuration); +```` + +This approach is useful since it's easy to change the configuration later without touching to the code. + +#### 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: + +````json +{ + "RemoteServices": { + "Default": { + "BaseUrl": "http://localhost:53929/" + }, + "BookStore": { + "BaseUrl": "http://localhost:48392/" + } + } +} +```` + +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 +context.Services.AddHttpClientProxies( + typeof(BookStoreApplicationModule).Assembly, + remoteServiceName: "BookStore" +); +```` + +`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 diff --git a/docs/en/docs-nav.json b/docs/en/docs-nav.json index 01f8fb884b..a3e3355a55 100644 --- a/docs/en/docs-nav.json +++ b/docs/en/docs-nav.json @@ -194,6 +194,10 @@ { "text": "Auto API Controllers", "path": "AspNetCore/Auto-API-Controllers.md" + }, + { + "text": "Dynamic C# API Clients", + "path": "AspNetCore/Dynamic-CSharp-API-Clients.md" } ] },