mirror of https://github.com/abpframework/abp.git
committed by
GitHub
193 changed files with 3677 additions and 2694 deletions
@ -0,0 +1,217 @@ |
|||
# Static C# API Client Proxies |
|||
|
|||
ABP can create C# API client proxy code to call your remote HTTP services (REST APIs). In this way, you don't need to deal with `HttpClient` and other low level details to call remote services and get results. |
|||
|
|||
Static C# proxies automatically handle the following stuff for you; |
|||
|
|||
* Maps C# **method calls** to remote server **HTTP calls** by considering the HTTP method, route, query string parameters, request payload and other details. |
|||
* **Authenticates** the HTTP Client by adding access token to the HTTP header. |
|||
* **Serializes** to and deserialize from JSON. |
|||
* Handles HTTP API **versioning**. |
|||
* Add **correlation id**, current **tenant** id and the current **culture** to the request. |
|||
* Properly **handles the error messages** sent by the server and throws proper exceptions. |
|||
|
|||
This system can be used by any type of .NET client to consume your HTTP APIs. |
|||
|
|||
## Static vs Dynamic Client Proxies |
|||
|
|||
ABP provides **two types** of client proxy generation system. This document explains the **static client proxies**, which generates client-side code in your development time. You can also see the [Dynamic C# API Client Proxies](Dynamic-CSharp-API-Clients.md) documentation to learn how to use proxies generated on runtime. |
|||
|
|||
Development-time (static) client proxy generation has a **performance advantage** since it doesn't need to obtain the HTTP API definition on runtime. However, you should **re-generate** the client proxy code whenever you change your API endpoint definition. On the other hand, dynamic client proxies are generated on runtime and provides an **easier development experience**. |
|||
|
|||
## 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, typically in the `Application.Contracts` project if you've created your solution using the startup templates. |
|||
|
|||
Example: |
|||
|
|||
````csharp |
|||
public interface IBookAppService : IApplicationService |
|||
{ |
|||
Task<List<BookDto>> GetListAsync(); |
|||
} |
|||
```` |
|||
|
|||
> 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. |
|||
|
|||
## 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 configure the application for the static client proxy generation. Example: |
|||
|
|||
````csharp |
|||
[DependsOn( |
|||
typeof(AbpHttpClientModule), //used to create client proxies |
|||
typeof(BookStoreApplicationContractsModule) //contains the application service interfaces |
|||
)] |
|||
public class MyClientAppModule : AbpModule |
|||
{ |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
// Prepare for static client proxy generation |
|||
context.Services.AddStaticHttpClientProxies( |
|||
typeof(BookStoreApplicationContractsModule).Assembly |
|||
); |
|||
} |
|||
} |
|||
```` |
|||
|
|||
`AddStaticHttpClientProxies` method gets an assembly, finds all service interfaces in the given assembly, and prepares for static client proxy generation. |
|||
|
|||
> The [application startup template](../Startup-Templates/Application.md) comes pre-configured for the **dynamic** client proxy generation, in the `HttpApi.Client` project. If you want to switch to the **static** client proxies, change `context.Services.AddHttpClientProxies` to `context.Services.AddStaticHttpClientProxies` in the module class of your `HttpApi.Client` project. |
|||
|
|||
### Endpoint Configuration |
|||
|
|||
`RemoteServices` section in the `appsettings.json` file is used to get remote service address by default. The simplest configuration is shown below: |
|||
|
|||
```json |
|||
{ |
|||
"RemoteServices": { |
|||
"Default": { |
|||
"BaseUrl": "http://localhost:53929/" |
|||
} |
|||
} |
|||
} |
|||
``` |
|||
|
|||
See the *AbpRemoteServiceOptions* section below for more detailed configuration. |
|||
|
|||
### Code Generation |
|||
|
|||
Server side must be up and running while generating the client proxy code. So, run your application that serves the HTTP APIs on the `BaseUrl` that is configured like explained in the *Endpoint Configuration* section. |
|||
|
|||
Open a command-line terminal in the root folder of your client project (`.csproj`) and type the following command: |
|||
|
|||
````bash |
|||
abp generate-proxy -t csharp -u http://localhost:53929/ |
|||
```` |
|||
|
|||
> If you haven't installed yet, you should install the [ABP CLI](../CLI.md). |
|||
|
|||
This command should generate the following files under the `ClientProxies` folder: |
|||
|
|||
 |
|||
|
|||
`BookClientProxy.Generated.cs` is the actual generated proxy class in this example. `BookClientProxy` is a `partial` class where you can write your custom code (ABP won't override it). `app-generate-proxy.json` contains information about the remote HTTP endpoint, so ABP can properly perform HTTP requests. |
|||
|
|||
> `generate-proxy` command generates proxies for only the APIs you've defined in your application. If you are developing a modular application, you can specify the `-m` (or `--module`) parameter to specify the module you want to generate proxies. See the *generate-proxy* section in the [ABP CLI](../CLI.md) documentation for other options. |
|||
|
|||
## Usage |
|||
|
|||
It's straightforward to use the client proxies. Just inject the service interface in the client application code: |
|||
|
|||
````csharp |
|||
public class MyService : ITransientDependency |
|||
{ |
|||
private readonly IBookAppService _bookService; |
|||
|
|||
public MyService(IBookAppService bookService) |
|||
{ |
|||
_bookService = bookService; |
|||
} |
|||
|
|||
public async Task DoItAsync() |
|||
{ |
|||
var books = await _bookService.GetListAsync(); |
|||
foreach (var book in books) |
|||
{ |
|||
Console.WriteLine($"[BOOK {book.Id}] Name={book.Name}"); |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
This sample injects the `IBookAppService` service interface defined above. The static client proxy implementation makes an HTTP call whenever a service method is called by the client. |
|||
|
|||
## Configuration |
|||
|
|||
### AbpRemoteServiceOptions |
|||
|
|||
`AbpRemoteServiceOptions` is automatically set from the `appsettings.json` by default. Alternatively, you can configure it in the `ConfigureServices` method of your [module](../Module-Development-Basics.md) to set or override it. Example: |
|||
|
|||
````csharp |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
context.Services.Configure<AbpRemoteServiceOptions>(options => |
|||
{ |
|||
options.RemoteServices.Default = |
|||
new RemoteServiceConfiguration("http://localhost:53929/"); |
|||
}); |
|||
|
|||
//... |
|||
} |
|||
```` |
|||
|
|||
### Multiple Remote Service Endpoints |
|||
|
|||
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 |
|||
{ |
|||
"RemoteServices": { |
|||
"Default": { |
|||
"BaseUrl": "http://localhost:53929/" |
|||
}, |
|||
"BookStore": { |
|||
"BaseUrl": "http://localhost:48392/" |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
`AddStaticHttpClientProxies` method can get an additional parameter for the remote service name. Example: |
|||
|
|||
````csharp |
|||
context.Services.AddStaticHttpClientProxies( |
|||
typeof(BookStoreApplicationContractsModule).Assembly, |
|||
remoteServiceConfigurationName: "BookStore" |
|||
); |
|||
```` |
|||
|
|||
`remoteServiceConfigurationName` parameter matches the service endpoint configured via `AbpRemoteServiceOptions`. If the `BookStore` endpoint is not defined then it fallbacks to the `Default` endpoint. |
|||
|
|||
### Retry/Failure Logic & Polly Integration |
|||
|
|||
If you want to add retry logic for the failing remote HTTP calls for the client proxies, you can configure the `AbpHttpClientBuilderOptions` in the `PreConfigureServices` method of your module class. |
|||
|
|||
**Example: Use the [Polly](https://github.com/App-vNext/Polly) library to re-try 3 times on a failure** |
|||
|
|||
````csharp |
|||
public override void PreConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
PreConfigure<AbpHttpClientBuilderOptions>(options => |
|||
{ |
|||
options.ProxyClientBuildActions.Add((remoteServiceName, clientBuilder) => |
|||
{ |
|||
clientBuilder.AddTransientHttpErrorPolicy(policyBuilder => |
|||
policyBuilder.WaitAndRetryAsync( |
|||
3, |
|||
i => TimeSpan.FromSeconds(Math.Pow(2, i)) |
|||
) |
|||
); |
|||
}); |
|||
}); |
|||
} |
|||
```` |
|||
|
|||
This example uses the [Microsoft.Extensions.Http.Polly](https://www.nuget.org/packages/Microsoft.Extensions.Http.Polly) package. You also need to import the `Polly` namespace (`using Polly;`) to be able to use the `WaitAndRetryAsync` method. |
|||
|
|||
## See Also |
|||
|
|||
* [Dynamic C# Client Proxies](Dynamic-CSharp-API-Clients.md) |
|||
@ -0,0 +1,113 @@ |
|||
# Dealing with Multiple Implementations of a Service in ASP.NET Core & ABP Dependency Injection |
|||
|
|||
ASP.NET Core provides a built-in [dependency injection system](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection) to register your services to the dependency injection container and inject/resolve them whenever you need. ABP's [dependency injection infrastructure](https://docs.abp.io/en/abp/latest/Dependency-Injection) is built on ASP.NET Core's DI system, automates service registration by conventions and provides some additional features. |
|||
|
|||
In this tutorial, I will explain how you can register multiple implementations of the same service interface and inject/resolve all these implementations when you need them. |
|||
|
|||
## Defining Services |
|||
|
|||
Assume that you have an `IExternalLogger` interface with two implementations: |
|||
|
|||
````csharp |
|||
public interface IExternalLogger |
|||
{ |
|||
Task LogAsync(string logText); |
|||
} |
|||
|
|||
public class ElasticsearchExternalLogger : IExternalLogger |
|||
{ |
|||
public async Task LogAsync(string logText) |
|||
{ |
|||
// TODO... |
|||
} |
|||
} |
|||
|
|||
public class AzureExternalLogger : IExternalLogger |
|||
{ |
|||
public async Task LogAsync(string logText) |
|||
{ |
|||
// TODO... |
|||
} |
|||
} |
|||
```` |
|||
|
|||
An example service injecting the `IExternalLogger` interface: |
|||
|
|||
````csharp |
|||
public class MyService : ITransientDependency |
|||
{ |
|||
private readonly IExternalLogger _externalLogger; |
|||
|
|||
public MyService(IExternalLogger externalLogger) |
|||
{ |
|||
_externalLogger = externalLogger; |
|||
} |
|||
|
|||
public async Task DemoAsync() |
|||
{ |
|||
await _externalLogger.LogAsync("Example log message..."); |
|||
} |
|||
} |
|||
```` |
|||
|
|||
In this example, we haven't registered any of the implementation classes to the dependency injection system yet. So, if we try to use the `MyService` class, we get an error indicating that no implementation found for the `IExternalLogger` service. We should register at least one implementation for the `IExternalLogger` interface. |
|||
|
|||
## Registering Services |
|||
|
|||
If we register both of the `ElasticsearchExternalLogger` and `AzureExternalLogger` services for the `IExternalLogger` interface, and then try to inject the `IExternalLogger` interface, the last registered implementation will be used. However, how to determine the last registered implementation? |
|||
|
|||
If we implement one of the [dependency interfaces](https://docs.abp.io/en/abp/latest/Dependency-Injection#dependency-interfaces) (e.g. `ITransientDependency`), then the registration order will be uncertain (it may depend on the namespaces of the classes). The *last registered implementation* can be different than you expect. So, it is not suggested to use the dependency interfaces to register multiple implementations. |
|||
|
|||
You can register your services in the `ConfigureServices` method of your module: |
|||
|
|||
````csharp |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
context.Services.AddTransient<IExternalLogger, ElasticsearchExternalLogger>(); |
|||
context.Services.AddTransient<IExternalLogger, AzureExternalLogger>(); |
|||
} |
|||
```` |
|||
|
|||
In this case, you get an `AzureExternalLogger` instance when you inject the `IExternalLogger` interface, because the last registered implementation is the `AzureExternalLogger` class. |
|||
|
|||
## Injecting Multiple Implementations |
|||
|
|||
When you have multiple implementation of an interface, you may want to work with all these implementations. For this example, you may want to write log to all the external loggers. We can change the `MyService` implementation as the following: |
|||
|
|||
````csharp |
|||
public class MyService : ITransientDependency |
|||
{ |
|||
private readonly IEnumerable<IExternalLogger> _externalLoggers; |
|||
|
|||
public MyService(IEnumerable<IExternalLogger> externalLoggers) |
|||
{ |
|||
_externalLoggers = externalLoggers; |
|||
} |
|||
|
|||
public async Task DemoAsync() |
|||
{ |
|||
foreach (var externalLogger in _externalLoggers) |
|||
{ |
|||
await externalLogger.LogAsync("Example log message..."); |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
In this example, we are injecting `IEnumerable<IExternalLogger>` instead of `IExternalLogger`, so we have a collection of the `IExternalLogger` implementations. Then we are using a `foreach` loop to write the same log text to all the `IExternalLogger` implementations. |
|||
|
|||
If you are using `IServiceProvider` to resolve dependencies, then use its `GetServices` method to obtain a collection of the service implementations: |
|||
|
|||
````csharp |
|||
IEnumerable<IExternalLogger> services = _serviceProvider.GetServices<IExternalLogger>(); |
|||
```` |
|||
|
|||
## Further Reading |
|||
|
|||
In this small tutorial, I explained how you can register multiple implementations of the same interface to the dependency injection system and inject/resolve all of them when you need. |
|||
|
|||
If you want to get more information about ABP's and ASP.NET Core's dependency injection systems, you can read the following documents: |
|||
|
|||
* [ABP's Dependency Injection documentation](https://docs.abp.io/en/abp/latest/Dependency-Injection) |
|||
* [ASP.NET Core Dependency Injection best practices, tips & tricks](https://medium.com/volosoft/asp-net-core-dependency-injection-best-practices-tips-tricks-c6e9c67f9d96) |
|||
* [ASP.NET Core's Dependency Injection documentation](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection) |
|||
@ -1,8 +1,3 @@ |
|||
# Getting Started with the Startup Templates |
|||
This document has been [moved to here](Getting-Started.md). |
|||
|
|||
See the following tutorials to learn how to get started with the ABP Framework using the pre-built application startup templates: |
|||
|
|||
* [Getting Started With the ASP.NET Core MVC / Razor Pages UI](Getting-Started?UI=MVC&DB=EF&Tiered=No) |
|||
* [Getting Started with the Angular UI](Getting-Started?UI=NG&DB=EF&Tiered=No) |
|||
|
|||
<!-- TODO: this document has been moved, it should be deleted in the future. --> |
|||
<!-- TODO: this document has been moved, it should be deleted in the future. --> |
|||
|
|||
@ -0,0 +1,157 @@ |
|||
# Static JavaScript API Client Proxies |
|||
|
|||
It is typical to consume your HTTP APIs from your JavaScript code. To do that, you normally deal with low level AJAX calls, like $.ajax, or better [abp.ajax](JavaScript-API/Ajax.md). ABP Framework provides **a better way** to call your HTTP APIs from your JavaScript code: JavaScript API Client Proxies! |
|||
|
|||
## Static vs Dynamic JavaScript Client Proxies |
|||
|
|||
ABP provides **two types** of client proxy generation system. This document explains the **static client proxies**, which generates client-side code in your development time. You can also see the [Dynamic JavaScript API Client Proxies](Dynamic-JavaScript-Proxies.md) documentation to learn how to use proxies generated on runtime. |
|||
|
|||
Development-time (static) client proxy generation has a **slight performance advantage** since it doesn't need to obtain the HTTP API definition on runtime. However, you should **re-generate** the client proxy code whenever you change your API endpoint definition. On the other hand, dynamic client proxies are generated on runtime and provides an **easier development experience**. |
|||
|
|||
## A Quick Example |
|||
|
|||
### The Application Service |
|||
|
|||
Assume that you have an application service defined as shown below: |
|||
|
|||
````csharp |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Application.Dtos; |
|||
using Volo.Abp.Application.Services; |
|||
|
|||
namespace Acme.BookStore.Authors |
|||
{ |
|||
public interface IAuthorAppService : IApplicationService |
|||
{ |
|||
Task<AuthorDto> GetAsync(Guid id); |
|||
|
|||
Task<PagedResultDto<AuthorDto>> GetListAsync(GetAuthorListDto input); |
|||
|
|||
Task<AuthorDto> CreateAsync(CreateAuthorDto input); |
|||
|
|||
Task UpdateAsync(Guid id, UpdateAuthorDto input); |
|||
|
|||
Task DeleteAsync(Guid id); |
|||
} |
|||
} |
|||
```` |
|||
|
|||
> You can follow the [web application development tutorial](../../Tutorials/Part-1.md) to learn how to create [application services](../../Application-Services.md), expose them as [HTTP APIs](../../API/Auto-API-Controllers.md) and consume from the JavaScript code as a complete example. |
|||
|
|||
### Generating the JavaScript Code |
|||
|
|||
Server side must be up and running while generating the client proxy code. So, first run the application that hosts your HTTP APIs (can be the Web application or the HttpApi.Host application depending on your solution structure). |
|||
|
|||
Open a command-line terminal in the root folder of your web project (`.csproj`) and type the following command: |
|||
|
|||
````bash |
|||
abp generate-proxy -t js -u https://localhost:53929/ |
|||
```` |
|||
|
|||
> If you haven't installed yet, you should install the [ABP CLI](../CLI.md). Change the example URL to your application's root URL. |
|||
|
|||
This command should generate the following files under the `ClientProxies` folder: |
|||
|
|||
 |
|||
|
|||
`app-proxy.js` is the generated proxy file in this example. Here, an example proxy function in this file: |
|||
|
|||
````js |
|||
acme.bookStore.authors.author.get = function(id, ajaxParams) { |
|||
return abp.ajax($.extend(true, { |
|||
url: abp.appPath + 'api/app/author/' + id + '', |
|||
type: 'GET' |
|||
}, ajaxParams)); |
|||
}; |
|||
```` |
|||
|
|||
> `generate-proxy` command generates proxies for only the APIs you've defined in your application (assumes `app` as the module name). If you are developing a modular application, you can specify the `-m` (or `--module`) parameter to specify the module you want to generate proxies. See the *generate-proxy* section in the [ABP CLI](../CLI.md) documentation for other options. |
|||
|
|||
### Using the Proxy Functions |
|||
|
|||
To use the proxy functions, first import the `app-proxy.js` file into your page: |
|||
|
|||
````html |
|||
<abp-script src="/client-proxies/app-proxy.js"/> |
|||
```` |
|||
|
|||
> We've used the [abp-script tag helper](Bundling-Minification.md) in this example. You could use the standard `script` tag, but the `abp-script` is the recommended way of importing JavaScript files to your pages. |
|||
|
|||
Now, you can call any of the application service methods from your JavaScript code, just like calling a JavaScript function. The JavaScript function has the identical function **name**, **parameters** and the **return value** with the C# method. |
|||
|
|||
**Example: Get a single author** |
|||
|
|||
````js |
|||
acme.bookStore.authors.author |
|||
.get("7245a066-5457-4941-8aa7-3004778775f0") //Get id from somewhere! |
|||
.then(function(result){ |
|||
console.log(result); |
|||
}); |
|||
```` |
|||
|
|||
**Example: Get the authors list** |
|||
|
|||
````js |
|||
acme.bookStore.authors.author.getList({ |
|||
maxResultCount: 10 |
|||
}).then(function(result){ |
|||
console.log(result.items); |
|||
}); |
|||
```` |
|||
|
|||
**Example: Delete an author** |
|||
|
|||
```js |
|||
acme.bookStore.authors.author |
|||
.delete('7245a066-5457-4941-8aa7-3004778775f0') //Get id from somewhere! |
|||
.then(function() { |
|||
abp.notify.info('Successfully deleted!'); |
|||
}); |
|||
``` |
|||
|
|||
## Disabling Dynamic JavaScript Proxies |
|||
|
|||
When you create an application or module, the [dynamic client proxy generation](Dynamic-JavaScript-Proxies.md) approach is used by default. If you want to use the statically generated client proxies for your application, you should explicitly disable it for your application or module in the `ConfigureServices` method of your [module class](../../Module-Development-Basics.md) as like in the following example: |
|||
|
|||
````csharp |
|||
Configure<DynamicJavaScriptProxyOptions>(options => |
|||
{ |
|||
options.DisableModule("app"); |
|||
}); |
|||
```` |
|||
|
|||
`app` represents the main application in this example, which works if you are creating an application. If you are developing an application module, then use your module's name. |
|||
|
|||
## AJAX Details |
|||
|
|||
JavaScript client proxy functions use the [abp.ajax](JavaScript-API/Ajax.md) under the hood. So, you have the same benefits like **automatic error handling**. Also, you can fully control the AJAX call by providing the options. |
|||
|
|||
### The Return Value |
|||
|
|||
Every function returns a [Deferred object](https://api.jquery.com/category/deferred-object/). That means you can chain with `then` to get the result, `catch` to handle the error, `always` to perform an action once the operation completes (success or failed). |
|||
|
|||
### AJAX Options |
|||
|
|||
Every function gets an additional **last parameter** after your own parameters. The last parameter is called as `ajaxParams`. It is an object that overrides the AJAX options. |
|||
|
|||
**Example: Set `type` and `dataType` AJAX options** |
|||
|
|||
````js |
|||
acme.bookStore.authors.author |
|||
.delete('7245a066-5457-4941-8aa7-3004778775f0', { |
|||
type: 'POST', |
|||
dataType: 'xml' |
|||
}) |
|||
.then(function() { |
|||
abp.notify.info('Successfully deleted!'); |
|||
}); |
|||
```` |
|||
|
|||
See the [jQuery.ajax](https://api.jquery.com/jQuery.ajax/) documentation for all the available options. |
|||
|
|||
## See Also |
|||
|
|||
* [Dynamic JavaScript API Client Proxies](Dynamic-JavaScript-Proxies.md) |
|||
* [Auto API Controllers](../../API/Auto-API-Controllers.md) |
|||
* [Web Application Development Tutorial](../../Tutorials/Part-1.md) |
|||
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 106 KiB |
|
After Width: | Height: | Size: 7.0 KiB |
|
After Width: | Height: | Size: 6.7 KiB |
@ -0,0 +1,35 @@ |
|||
using System; |
|||
using System.IO; |
|||
using System.Linq; |
|||
using Volo.Abp.Cli.Utils; |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace Volo.Abp.Cli.ProjectModification; |
|||
|
|||
public class AngularPwaSupportAdder : ITransientDependency |
|||
{ |
|||
protected ICmdHelper CmdHelper { get; } |
|||
protected PackageJsonFileFinder PackageJsonFileFinder { get; } |
|||
|
|||
public AngularPwaSupportAdder( |
|||
ICmdHelper cmdHelper, |
|||
PackageJsonFileFinder packageJsonFileFinder) |
|||
{ |
|||
CmdHelper = cmdHelper; |
|||
PackageJsonFileFinder = packageJsonFileFinder; |
|||
} |
|||
|
|||
public virtual void AddPwaSupport(string rootDirectory) |
|||
{ |
|||
var fileList = PackageJsonFileFinder.Find(rootDirectory).Where(x => File.Exists(x.RemovePostFix("package.json") + "angular.json")).ToList(); |
|||
|
|||
fileList.ForEach(AddPwaSupportToProject); |
|||
} |
|||
|
|||
protected virtual void AddPwaSupportToProject(string filePath) |
|||
{ |
|||
var directory = Path.GetDirectoryName(filePath).EnsureEndsWith(Path.DirectorySeparatorChar); |
|||
|
|||
CmdHelper.RunCmd("ng add @angular/pwa --skip-confirmation", workingDirectory: directory); |
|||
} |
|||
} |
|||
@ -0,0 +1,39 @@ |
|||
using System.Threading.Tasks; |
|||
using Microsoft.EntityFrameworkCore; |
|||
using Microsoft.EntityFrameworkCore.Design; |
|||
using Microsoft.Extensions.Configuration; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Volo.Abp.Modularity; |
|||
using Volo.Abp.Threading; |
|||
|
|||
namespace Volo.Abp.EntityFrameworkCore.Design; |
|||
|
|||
public abstract class AbpDesignTimeDbContextBase<TModule, TContext> : IDesignTimeDbContextFactory<TContext> |
|||
where TModule : AbpModule |
|||
where TContext : DbContext |
|||
{ |
|||
public virtual TContext CreateDbContext(string[] args) |
|||
{ |
|||
return AsyncHelper.RunSync(() => CreateDbContextAsync(args)); |
|||
} |
|||
|
|||
protected virtual async Task<TContext> CreateDbContextAsync(string[] args) |
|||
{ |
|||
var application = await AbpApplicationFactory.CreateAsync<TModule>(options => |
|||
{ |
|||
options.Services.ReplaceConfiguration(BuildConfiguration()); |
|||
ConfigureServices(options.Services); |
|||
}); |
|||
|
|||
await application.InitializeAsync(); |
|||
|
|||
return application.ServiceProvider.GetRequiredService<TContext>(); |
|||
} |
|||
|
|||
protected virtual void ConfigureServices(IServiceCollection services) |
|||
{ |
|||
|
|||
} |
|||
|
|||
protected abstract IConfigurationRoot BuildConfiguration(); |
|||
} |
|||
@ -0,0 +1,58 @@ |
|||
using Shouldly; |
|||
using Volo.Abp.Testing; |
|||
using Xunit; |
|||
|
|||
namespace Volo.Abp.Uow; |
|||
|
|||
public class UnitOfWorkExtensions_Tests : AbpIntegratedTest<AbpUnitOfWorkModule> |
|||
{ |
|||
private readonly IUnitOfWorkManager _unitOfWorkManager; |
|||
|
|||
public UnitOfWorkExtensions_Tests() |
|||
{ |
|||
_unitOfWorkManager = GetRequiredService<IUnitOfWorkManager>(); |
|||
} |
|||
|
|||
[Fact] |
|||
public void AddItem() |
|||
{ |
|||
var uow = _unitOfWorkManager.Begin(); |
|||
uow.AddItem("testKey", "testValue"); |
|||
|
|||
uow.Items.ShouldContainKey("testKey"); |
|||
uow.Items.ContainsValue("testValue"); |
|||
} |
|||
|
|||
[Fact] |
|||
public void GetItemOrDefault() |
|||
{ |
|||
var uow = _unitOfWorkManager.Begin(); |
|||
uow.Items.Add("testKey", new NameValue("TestKey","TestValue")); |
|||
|
|||
uow.GetItemOrDefault<NameValue>("testKey").ShouldBeOfType<NameValue>(); |
|||
uow.GetItemOrDefault<NameValue>("testKey").Value.ShouldBe("TestValue"); |
|||
} |
|||
|
|||
[Fact] |
|||
public void GetOrAddItem() |
|||
{ |
|||
var uow = _unitOfWorkManager.Begin(); |
|||
|
|||
var item = uow.GetOrAddItem("testKey", _ => new NameValue("TestKey", "TestValue")); |
|||
|
|||
item.Name.ShouldBe("TestKey"); |
|||
item.ShouldBeOfType<NameValue>(); |
|||
item.Value.ShouldBe("TestValue"); |
|||
} |
|||
|
|||
[Fact] |
|||
public void RemoveItem() |
|||
{ |
|||
var uow = _unitOfWorkManager.Begin(); |
|||
uow.Items.Add("testKey", "testValue"); |
|||
|
|||
uow.RemoveItem("testKey"); |
|||
|
|||
uow.Items.ShouldNotContainKey("testKey"); |
|||
} |
|||
} |
|||
File diff suppressed because it is too large
@ -1,11 +1,11 @@ |
|||
{ |
|||
"version": "5.2.0", |
|||
"version": "5.2.1", |
|||
"name": "@abp/aspnetcore.components.server.basictheme", |
|||
"publishConfig": { |
|||
"access": "public" |
|||
}, |
|||
"dependencies": { |
|||
"@abp/aspnetcore.components.server.theming": "~5.2.0" |
|||
"@abp/aspnetcore.components.server.theming": "~5.2.1" |
|||
}, |
|||
"gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431" |
|||
} |
|||
|
|||
@ -1,12 +1,12 @@ |
|||
{ |
|||
"version": "5.2.0", |
|||
"version": "5.2.1", |
|||
"name": "@abp/aspnetcore.components.server.theming", |
|||
"publishConfig": { |
|||
"access": "public" |
|||
}, |
|||
"dependencies": { |
|||
"@abp/bootstrap": "~5.2.0", |
|||
"@abp/font-awesome": "~5.2.0" |
|||
"@abp/bootstrap": "~5.2.1", |
|||
"@abp/font-awesome": "~5.2.1" |
|||
}, |
|||
"gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431" |
|||
} |
|||
|
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue