@ -0,0 +1,269 @@ |
|||||
|
## ABP Application Startup |
||||
|
|
||||
|
You typically use the [ABP CLI](CLI.md)'s `abp new` command to [get started](Getting-Started.md) with one of the pre-built [startup solution templates](Startup-Templates/Index.md). When you do that, you generally don't need to know the details of how the ABP Framework is integrated with your application or how it is configured and initialized. The startup template also comes with the fundamental ABP packages and [application modules](Modules/Index) are pre-installed and configured for you. |
||||
|
|
||||
|
> It is always suggested to [get started with a startup template](Getting-Started.md) and modify it for your requirements. Read this document only if you want to understand the details or if you need to modify how the ABP Framework starts. |
||||
|
|
||||
|
While the ABP Framework has a lot of features and integrations, it is built as a lightweight and modular framework. It consists of [hundreds of NuGet and NPM packages](https://abp.io/packages), so you can only use the features you need. If you follow the [Getting Started with an Empty ASP.NET Core MVC / Razor Pages Application](Getting-Started-AspNetCore-Application.md) document, you'll see how easy it is to install the ABP Framework into an empty ASP.NET Core project from scratch. You only need to install a single NuGet package and make a few small changes. |
||||
|
|
||||
|
This document is for who wants to better understand how the ABP Framework is initialized and configured on startup. |
||||
|
|
||||
|
## Installing to a Console Application |
||||
|
|
||||
|
A .NET Console application is the minimalist .NET application. So, it is best to show the installing of the ABP Framework to a console application as a minimalist example. |
||||
|
|
||||
|
If you [create a new console application with Visual Studio](https://learn.microsoft.com/en-us/dotnet/core/tutorials/with-visual-studio) (for .NET 7.0 or later), you will see the following solution structure (I named the solution as `MyConsoleDemo`): |
||||
|
|
||||
|
 |
||||
|
|
||||
|
This example uses the [top level statements](https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/tutorials/top-level-statements), so it consists of only a single line of code. |
||||
|
|
||||
|
The first step is to install the [Volo.Abp.Core](https://www.nuget.org/packages/Volo.Abp.Core) NuGet package, which is the most core NuGet package of the ABP framework. You can install it using the [Package Manager Console](https://learn.microsoft.com/en-us/nuget/consume-packages/install-use-packages-powershell) in Visual Studio: |
||||
|
|
||||
|
````bash |
||||
|
Install-Package Volo.Abp.Core |
||||
|
```` |
||||
|
|
||||
|
Alternatively, you can use a command-line terminal in the root folder of the project (the folder containing the `MyConsoleDemo.csproj` file, for this example): |
||||
|
|
||||
|
````bash |
||||
|
dotnet add package Volo.Abp.Core |
||||
|
```` |
||||
|
|
||||
|
After adding the NuGet package, we should create a root [module class](Module-Development-Basics.md) for our application. We can create the following class in the project: |
||||
|
|
||||
|
````csharp |
||||
|
using Volo.Abp.Modularity; |
||||
|
|
||||
|
namespace MyConsoleDemo |
||||
|
{ |
||||
|
public class MyConsoleDemoModule : AbpModule |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
|
```` |
||||
|
|
||||
|
This is an empty class deriving from the `AbpModule` class. It is the main class that you will control your application's dependencies with, and implement your configuration and startup/shutdown logic. For more information, please check the [Modularity](Module-Development-Basics.md) document. |
||||
|
|
||||
|
As the second and the last step, change the `Program.cs` as shown in the following code block: |
||||
|
|
||||
|
````csharp |
||||
|
using MyConsoleDemo; |
||||
|
using Volo.Abp; |
||||
|
|
||||
|
// 1: Create the ABP application container |
||||
|
using var application = await AbpApplicationFactory.CreateAsync<MyConsoleDemoModule>(); |
||||
|
|
||||
|
// 2: Initialize/start the ABP Framework (and all the modules) |
||||
|
await application.InitializeAsync(); |
||||
|
|
||||
|
Console.WriteLine("ABP Framework has been started..."); |
||||
|
|
||||
|
// 3: Stop the ABP Framework (and all the modules) |
||||
|
await application.ShutdownAsync(); |
||||
|
```` |
||||
|
|
||||
|
That's all. Now, ABP Framework is installed, integrated, started and stopped in your application. From now, you can install [ABP packages](https://abp.io/packages) to your application whenever you need them. |
||||
|
|
||||
|
## Installing a Framework Package |
||||
|
|
||||
|
If you want to send emails from your .NET application, you can use .NET's standard [SmtpClient class](https://learn.microsoft.com/en-us/dotnet/api/system.net.mail.smtpclient). ABP also provides an `IEmailSender` service that simplifies [sending emails](Emailing.md) and configuring the email settings in a central place. If you want to use it, you should install the [Volo.Abp.Emailing](https://www.nuget.org/packages/Volo.Abp.Emailing) NuGet package to your project: |
||||
|
|
||||
|
````bash |
||||
|
dotnet add package Volo.Abp.Emailing |
||||
|
```` |
||||
|
|
||||
|
Once you add a new ABP package/module, you also need to specify the module dependency from your module class. So, change the `MyConsoleDemoModule` class as shown below: |
||||
|
|
||||
|
````csharp |
||||
|
using Volo.Abp.Emailing; |
||||
|
using Volo.Abp.Modularity; |
||||
|
|
||||
|
namespace MyConsoleDemo |
||||
|
{ |
||||
|
[DependsOn(typeof(AbpEmailingModule))] // Added the module dependency |
||||
|
public class MyConsoleDemoModule : AbpModule |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
|
```` |
||||
|
|
||||
|
I've just added a `[DependsOn]` attribute to declare that I want to use the ABP Emailing Module (`AbpEmailingModule`). Now, I can use the `IEmailSender` service in my `Program.cs`: |
||||
|
|
||||
|
````csharp |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using MyConsoleDemo; |
||||
|
using Volo.Abp; |
||||
|
using Volo.Abp.Emailing; |
||||
|
|
||||
|
using var application = await AbpApplicationFactory.CreateAsync<MyConsoleDemoModule>(); |
||||
|
await application.InitializeAsync(); |
||||
|
|
||||
|
// Sending emails using the IEmailSender service |
||||
|
var emailsender = application.ServiceProvider.GetRequiredService<IEmailSender>(); |
||||
|
await emailsender.SendAsync( |
||||
|
to: "info@acme.com", |
||||
|
subject: "Hello World", |
||||
|
body: "My message body..." |
||||
|
); |
||||
|
|
||||
|
await application.ShutdownAsync(); |
||||
|
```` |
||||
|
|
||||
|
> If you run that application, you get a runtime error indicating that the email sending settings haven't been done yet. You can check the [Email Sending document](Emailing.md) to learn how to configure it. |
||||
|
|
||||
|
That's all. Install an ABP NuGet package, add the module dependency (using the `[DependsOn]` attribute) and use any service inside the NuGet package. |
||||
|
|
||||
|
The [ABP CLI](CLI.md) already has a special command to perform the addition of an ABP NuGet and also adding the `[DependsOn]` attribute to your module class for you with a single command: |
||||
|
|
||||
|
````bash |
||||
|
abp add-package Volo.Abp.Emailing |
||||
|
```` |
||||
|
|
||||
|
We suggest you to use the `abp add-package` command instead of manually doing it. |
||||
|
|
||||
|
## AbpApplicationFactory |
||||
|
|
||||
|
`AbpApplicationFactory` is the main class that creates an ABP application container. It provides a single static `CreateAsync` (and `Create` if you can't use asynchronous programming) method with multiple overloads. Let's investigate these overloads to understand where you can use them. |
||||
|
|
||||
|
The first overload gets a generic module class parameter as we've used before in this document: |
||||
|
|
||||
|
````csharp |
||||
|
AbpApplicationFactory.CreateAsync<MyConsoleDemoModule>(); |
||||
|
```` |
||||
|
|
||||
|
The generic class parameter should be the root module class of your application. All the other modules are resolved as dependencies of that module. |
||||
|
|
||||
|
The second overload gets the module class as a `Type` parameter, instead of the generic parameter. So, the previous code block could be re-written as shown below: |
||||
|
|
||||
|
````csharp |
||||
|
AbpApplicationFactory.CreateAsync(typeof(MyConsoleDemoModule)); |
||||
|
```` |
||||
|
|
||||
|
Both overloads work exactly the same. So, you can use the second one if you don't know the module class type on development time and you (somehow) calculate it on runtime. |
||||
|
|
||||
|
If you create one of the methods above, ABP creates an internal service collection (`IServiceCollection`) and an internal service provider (`IServiceProvider`) to setup the [dependency injection](Dependency-Injection.md) system internally. Notice that we've used the `application.ServiceProvider` property in the *Installing a Framework Package* section to resolve the `IEmailSender` service from the dependency injection system. |
||||
|
|
||||
|
The next overload gets an `IServiceCollection` parameter from you to allow you to setup the dependency injection system yourself, or integrate to another framework (like ASP.NET Core) that also sets up the dependency injection system internally. |
||||
|
|
||||
|
We can change the `Program.cs` as shown below to externally manage the dependency injection setup: |
||||
|
|
||||
|
````csharp |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using MyConsoleDemo; |
||||
|
using Volo.Abp; |
||||
|
|
||||
|
// 1: Manually created the IServiceCollection |
||||
|
IServiceCollection services = new ServiceCollection(); |
||||
|
|
||||
|
// 2: Pass the IServiceCollection externally to the ABP Framework |
||||
|
using var application = await AbpApplicationFactory |
||||
|
.CreateAsync<MyConsoleDemoModule>(services); |
||||
|
|
||||
|
// 3: Manually built the IServiceProvider object |
||||
|
IServiceProvider serviceProvider = services.BuildServiceProvider(); |
||||
|
|
||||
|
// 4: Pass the IServiceProvider externally to the ABP Framework |
||||
|
await application.InitializeAsync(serviceProvider); |
||||
|
|
||||
|
Console.WriteLine("ABP Framework has been started..."); |
||||
|
|
||||
|
await application.ShutdownAsync(); |
||||
|
```` |
||||
|
|
||||
|
In this example, we've used .NET's standard dependency injection container. The `services.BuildServiceProvider()` call creates the standard container. However, ABP provides an alternative extension method, `BuildServiceProviderFromFactory()`, that properly works even if you are using another dependency injection container: |
||||
|
|
||||
|
````csharp |
||||
|
IServiceProvider serviceProvider = services.BuildServiceProviderFromFactory(); |
||||
|
```` |
||||
|
|
||||
|
> You can check the [Autofac Integration](Autofac-Integration.md) document if you want to learn how you can integrate the [Autofac](https://autofac.org/) dependency injection container with the ABP Framework. |
||||
|
|
||||
|
Finally, the `CreateAsync` method has a last overload that takes the module class name as a `Type` parameter and a `IServiceCollection` object. So, we could re-write the last `CreateAsync` method usage as in the following code block: |
||||
|
|
||||
|
````csharp |
||||
|
using var application = await AbpApplicationFactory |
||||
|
.CreateAsync(typeof(MyConsoleDemoModule), services); |
||||
|
```` |
||||
|
|
||||
|
> All of the `CreateAsync` method overloads have `Create` counterparts. If your application type can not utilize asynchronous programming (that means you can't use the `await` keyword), then you can use the `Create` method instead of the `CreateAsync` method. |
||||
|
|
||||
|
### AbpApplicationCreationOptions |
||||
|
|
||||
|
All of the `CreateAsync` overloads can get an optional `Action<AbpApplicationCreationOptions>` parameter to configure the options that are used on the application creation. See the following example: |
||||
|
|
||||
|
````csharp |
||||
|
using var application = await AbpApplicationFactory |
||||
|
.CreateAsync<MyConsoleDemoModule>(options => |
||||
|
{ |
||||
|
options.ApplicationName = "MyApp"; |
||||
|
}); |
||||
|
```` |
||||
|
|
||||
|
We've passed a lambda method to configure the `ApplicationName` option. Here's a list of all standard options: |
||||
|
|
||||
|
* `ApplicationName`: A human-readable name for the application. It is a unique value for an application. |
||||
|
* `Configuration`: Can be used to setup the [application configuration](Configuration.md) when it is not provided by the hosting system. It is not needed for ASP.NET Core and other .NET hosted applications. However, if you've used `AbpApplicationFactory` with an internal service provider, you can use this option to configure how the application configuration is built. |
||||
|
* `PlugInSources`: A list of plugin sources. See the [Plug-In Modules documentation](PlugIn-Modules) to learn how to work with plugins. |
||||
|
* `Services`: The `IServiceCollection` object that can be used to register service dependencies. You generally don't need that, because you configure your services in your [module class](Module-Development-Basics.md). However, it can be used while writing extension methods for the `AbpApplicationCreationOptions` class. |
||||
|
|
||||
|
#### The ApplicationName option |
||||
|
|
||||
|
As defined above, the `ApplicationName` option is a human-readable name for the application. It is a unique value for an application. |
||||
|
|
||||
|
`ApplicationName` is used by the ABP Framework in several places to distinguish the application. For example, the [audit logging](Audit-Logging.md) system saves the `ApplicationName` in each audit log record written by the related application, so you can understand which application has created the audit log entry. So, if your system consists of multiple applications (like a microservice solution) that are saving audit logs to a single point, you should be sure that each application has a different `ApplicationName`. |
||||
|
|
||||
|
The `ApplicationName` property's value is set automatically from the **entry assembly's name** (generally, the project name in a .NET solution) by default, which is proper for most cases, since each application typically has a unique entry assembly name. |
||||
|
|
||||
|
There are two ways to set the application name to a different value. In this first approach, you can set the `ApplicationName` property in your application's [configuration](Configuration.md). The easiest way is to add an `ApplicationName` field to your `appsettings.json` file: |
||||
|
|
||||
|
````json |
||||
|
{ |
||||
|
"ApplicationName": "Services.Ordering" |
||||
|
} |
||||
|
```` |
||||
|
|
||||
|
Alternatively, you can set `AbpApplicationCreationOptions.ApplicationName` while creating the ABP application. You can find the `AddApplication` or `AddApplicationAsync` call in your solution (typically in the `Program.cs` file), and set the `ApplicationName` option as shown below: |
||||
|
|
||||
|
````csharp |
||||
|
await builder.AddApplicationAsync<OrderingServiceHttpApiHostModule>(options => |
||||
|
{ |
||||
|
options.ApplicationName = "Services.Ordering"; |
||||
|
}); |
||||
|
```` |
||||
|
|
||||
|
#### IApplicationInfoAccessor |
||||
|
|
||||
|
If you need to access the `ApplicationName` later in your solution, you can inject the `IApplicationInfoAccessor` service and get the value from its `ApplicationName` property. |
||||
|
|
||||
|
`IApplicationInfoAccessor` also provides an `InstanceId` value, that is a random GUID value that is generated when your application starts. You can use that value to distinguish application instances from each other. |
||||
|
|
||||
|
## IAbpApplication |
||||
|
|
||||
|
`AbpApplicationFactory` returns an `IAbpApplication` object from its `CreateAsync` (or `Create`) method. `IAbpApplication` is the main container for an ABP application. It is also registered to the [dependency injection](Dependency-Injection.md) system, so you can inject `IAbpApplication` in your services to use its properties and methods. |
||||
|
|
||||
|
Here's a list of `IAbpApplication` properties you may want to know: |
||||
|
|
||||
|
* `StartupModuleType`: Gets the root module of the application that was used while creating the application container (on the `AbpApplicationFactory.CreateAsync` method). |
||||
|
* `Services`: A list of all service registrations (the `IServiceCollection` object). You can not add new services to this collection after application initialization (you can actually add, but it won't have any effect). |
||||
|
* `ServiceProvider`: A reference to the root service provider used by the application. This can not be used before initializing the application. If you need to resolve non-singleton services from that `IServiceProvider` object, always create a new service scope and dispose it after usage. Otherwise, your application will have memory leak problems. See the *Releasing/Disposing Services* section of the [dependency injection](Dependency-Injection.md) document for more information about service scopes. |
||||
|
* `Modules`: A read-only list of all the modules loaded into the current application. Alternatively, you can inject the `IModuleContainer` service if you need to access the module list in your application code. |
||||
|
|
||||
|
The `IAbpApplication` interface extends the `IApplicationInfoAccessor` interface, so you can get the `ApplicationName` and `InstanceId` values from it. However, if you only need to access these properties, inject and use the `IApplicationInfoAccessor` service instead. |
||||
|
|
||||
|
`IAbpApplication` is disposable. Always dispose of it before exiting your application. |
||||
|
|
||||
|
## .NET Generic Host & ASP.NET Core Integrations |
||||
|
|
||||
|
`AbpApplicationFactory` can create a standalone ABP application container without any external dependency. However, in most cases, you will want to integrate it with [.NET's generic host](https://learn.microsoft.com/en-us/dotnet/core/extensions/generic-host) or ASP.NET Core. For such usages, ABP provides built-in extension methods to easily create an ABP application container that is well-integrated to these systems. |
||||
|
|
||||
|
The [Getting Started with an Empty ASP.NET Core MVC / Razor Pages Application](Getting-Started-AspNetCore-Application.md) document clearly explains how you can create an ABP application container in an ASP.NET Core application. |
||||
|
|
||||
|
You can also [create a console application](Startup-Templates/Console) to see how it is integrated with .NET Generic Host. |
||||
|
|
||||
|
> Most of the times, you will directly create ABP applications using the ABP CLI's `new` command. So, you don't need to care about these integration details. |
||||
|
|
||||
|
## See Also |
||||
|
|
||||
|
* [Dependency injection](Dependency-Injection.md) |
||||
|
* [Modularity](Module-Development-Basics.md) |
||||
@ -0,0 +1,45 @@ |
|||||
|
# Deploying Distributed / Microservice Solutions |
||||
|
|
||||
|
The ABP Framework is designed to consider distributed and microservice systems, where you have multiple applications and/or services communicating internally. All of its features are compatible with distributed scenarios. This document highlights some points you should care about when you deploy your distributed or microservice solution. |
||||
|
|
||||
|
## Application Name & Instance Id |
||||
|
|
||||
|
ABP provides the `IApplicationInfoAccessor` service that provides the following properties: |
||||
|
|
||||
|
* `ApplicationName`: A human-readable name for an application. It is a unique value for an application. |
||||
|
* `InstanceId`: A random (GUID) value generated by the ABP Framework each time you start the application. |
||||
|
|
||||
|
These values are used by the ABP Framework in several places to distinguish the application and the application instance (process) in the system. For example, the [audit logging](../Audit-Logging.md) system saves the `ApplicationName` in each audit log record written by the related application, so you can understand which application has created the audit log entry. So, if your system consists of multiple applications saving audit logs to a single point, you should be sure that each application has a different `ApplicationName`. |
||||
|
|
||||
|
The `ApplicationName` property's value is set automatically from the **entry assembly's name** (generally, the project name in a .NET solution) by default, which is proper for most cases, since each application typically has a unique entry assembly name. |
||||
|
|
||||
|
There are two ways to set the application name to a different value. In this first approach, you can set the `ApplicationName` property in your application's [configuration](../Configuration.md). The easiest way is to add an `ApplicationName` field to your `appsettings.json` file: |
||||
|
|
||||
|
````json |
||||
|
{ |
||||
|
"ApplicationName": "Services.Ordering" |
||||
|
} |
||||
|
```` |
||||
|
|
||||
|
Alternatively, you can set `AbpApplicationCreationOptions.ApplicationName` while creating the ABP application. You can find the `AddApplication` or `AddApplicationAsync` call in your solution (typically in the `Program.cs` file), and set the `ApplicationName` option as shown below: |
||||
|
|
||||
|
````csharp |
||||
|
await builder.AddApplicationAsync<OrderingServiceHttpApiHostModule>(options => |
||||
|
{ |
||||
|
options.ApplicationName = "Services.Ordering"; |
||||
|
}); |
||||
|
```` |
||||
|
|
||||
|
## Using a Distributed Event Bus |
||||
|
|
||||
|
ABP's [Distributed Event Bus](../Distributed-Event-Bus.md) system provides a standard interface to communicate with other applications and services. While the name is "distributed", the default implementation is in-process. That means, your applications / services can not communicate with each other unless you explicitly configure a distributed event bus provider. |
||||
|
|
||||
|
If you are building a distributed system, then the applications should communicate through an external distributed messaging server. Please follow the [Distributed Event Bus](../Distributed-Event-Bus.md) document to learn how to install and configure your distributed event bus provider. |
||||
|
|
||||
|
> **Warning**: Even if you don't use the distributed event bus directly in your application code, the ABP Framework and some of the modules you are using may use it. So, if you are building a distributed system, always configure a distributed event bus provider. |
||||
|
|
||||
|
> **Info**: [Clustered deployment](Clustered-Environment.md) of a single application is not considered as a distributed system. So, if you only have a single application with multiple instances serving behind a load balancer, a real distributed messaging server may not be needed. |
||||
|
|
||||
|
## See Also |
||||
|
|
||||
|
* [Deploying to a Clustered Environment](Clustered-Environment.md) |
||||
@ -0,0 +1,22 @@ |
|||||
|
# Optimizing Your Application for Production Environments |
||||
|
|
||||
|
ABP Framework and the startup solution templates are configured well to get the maximum performance on production environments. However, there are still some points you need to pay attention to in order to optimize your system in production. In this document, we will mention some of these topics. |
||||
|
|
||||
|
## Caching Static Contents |
||||
|
|
||||
|
The following items are contents that can be cached in the client side (typically in the Browser) or in a CDN server: |
||||
|
|
||||
|
* **Static images** can always be cached. Here, you should be careful that if you change an image, use a different file name, or use a versioning query-string parameter, so the browser (or CDN) understands it's been changed. |
||||
|
* **CSS and JavaScript files**. ABP's [bundling & minification](../UI/AspNetCore/Bundling-Minification.md) system always uses a query-string versioning parameter and a hash value in the files names of the CSS & JavaScript files for the [MVC (Razor Pages)](../UI/AspNetCore/Overall.md) UI. So, you can safely cache these files in the client side or in a CDN server. |
||||
|
* **Application bundle files** of an [Angular UI](../UI/Angular/Quick-Start.md) application. |
||||
|
* **[Application Localization Endpoint](../API/Application-Localization.md)** can be cached per culture (it already has a `cultureName` query string parameter) if you don't use dynamic localization on the server-side. ABP Commercial's [Language Management](https://commercial.abp.io/modules/Volo.LanguageManagement) module provides dynamic localization. If you're using it, you can't cache that endpoint forever. However, you can still cache it for a while. Applying dynamic localization text changes to the application can delay for a few minutes, even for a few hours in a real life scenario. |
||||
|
|
||||
|
There may be more ways based on your solution structure and deployment environment, but these are the essential points you should consider to client-side cache in a production environment. |
||||
|
|
||||
|
## Bundling & Minification for MVC (Razor Pages) UI |
||||
|
|
||||
|
ABP's [bundling & minification](../UI/AspNetCore/Bundling-Minification.md) system automatically bundles, minifies and versions your CSS and JavaScript files in production environment. Normally, you don't need to do anything, if you haven't disabled it yourself in your application code. It is important to follow the [bundling & minification](../UI/AspNetCore/Bundling-Minification.md) document and truly use the system to get the maximum optimization. |
||||
|
|
||||
|
## Background Jobs |
||||
|
|
||||
|
ABP's [Background Jobs](../Background-Jobs.md) system provides an abstraction with a basic implementation to enqueue jobs and execute them in a background thread. ABP's Default Background Job Manager may not be enough if you are adding too many jobs to the queue and want them to be executed in parallel by multiple servers with a high performance. If you need these, you should consider to configure a dedicated background job software, like [Hangfire](https://www.hangfire.io/). ABP has a pre-built [Hangfire integration](../Background-Jobs-Hangfire.md), so you can switch to Hangfire without changing your application code. |
||||
@ -0,0 +1,131 @@ |
|||||
|
# Entity Cache |
||||
|
|
||||
|
ABP Framework provides an entity caching system that works on top of the [distributed caching](Caching.md) system. It does the following operations on behalf of you: |
||||
|
|
||||
|
* Gets the entity from the database (by using the [repositories](Repositories.md)) in its first call and then gets it from the cache in subsequent calls. |
||||
|
* Automatically invalidates the cached entity if the entity is updated or deleted. Thus, it will be retrieved from the database in the next call and will be re-cached. |
||||
|
|
||||
|
## Caching Entity Objects |
||||
|
|
||||
|
`IEntityCache<TEntityCacheItem, TKey>` is a simple service provided by the ABP Framework for caching entities. Assume that you have a `Product` entity as shown below: |
||||
|
|
||||
|
```csharp |
||||
|
public class Product : AggregateRoot<Guid> |
||||
|
{ |
||||
|
public string Name { get; set; } |
||||
|
public string Description { get; set; } |
||||
|
public float Price { get; set; } |
||||
|
public int StockCount { get; set; } |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
If you want to cache this entity, you should first configure the [dependency injection](Dependency-Injection.md) system to register the `IEntityCache` service in the `ConfigureServices` method of your [module class](Module-Development-Basics.md): |
||||
|
|
||||
|
```csharp |
||||
|
context.Services.AddEntityCache<Product, Guid>(); |
||||
|
``` |
||||
|
|
||||
|
Now you can inject the `IEntityCache<Product, Guid>` service wherever you need: |
||||
|
|
||||
|
```csharp |
||||
|
public class ProductAppService : ApplicationService, IProductAppService |
||||
|
{ |
||||
|
private readonly IEntityCache<Product, Guid> _productCache; |
||||
|
|
||||
|
public ProductAppService(IEntityCache<Product, Guid> productCache) |
||||
|
{ |
||||
|
_productCache = productCache; |
||||
|
} |
||||
|
|
||||
|
public async Task<ProductDto> GetAsync(Guid id) |
||||
|
{ |
||||
|
var product = await _productCache.GetAsync(id); |
||||
|
return ObjectMapper.Map<Product, ProductDto>(product); |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
> Note that we've used the `ObjectMapper` service to map from `Product` to `ProductDto`. You should configure that [object mapping](Object-To-Object-Mapping.md) to make that example service properly works. |
||||
|
|
||||
|
That's all. The cache name (in the distributed cache server) will be full name (with namespace) of the `Product` class. You can use the `[CacheName]` attribute to change it. Please refer to the [caching document](Caching.md) for details. |
||||
|
|
||||
|
## Using a Cache Item Class |
||||
|
|
||||
|
In the previous section, we've directly cached the `Product` entity. In that case, the `Product` class must be serializable to JSON (and deserializable from JSON). Sometimes that might not be possible or you may want to use another class to store the cache data. For example, we may want to use the `ProductDto` class instead of the `Product` class for the cached object if the `Product` entity. |
||||
|
|
||||
|
Assume that we've created a `ProductDto` class as shown below: |
||||
|
|
||||
|
```csharp |
||||
|
public class ProductDto : EntityDto<Guid> |
||||
|
{ |
||||
|
public string Name { get; set; } |
||||
|
public string Description { get; set; } |
||||
|
public float Price { get; set; } |
||||
|
public int StockCount { get; set; } |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
Now, we can register the entity cache services to [dependency injection](Dependency-Injection.md) in the `ConfigureServices` method of your [module class](Module-Development-Basics.md) with three generic parameters, as shown below: |
||||
|
|
||||
|
```csharp |
||||
|
context.Services.AddEntityCache<Product, ProductDto, Guid>(); |
||||
|
``` |
||||
|
|
||||
|
Since the entity cache system will perform the [object mapping](Object-To-Object-Mapping.md) (from `Product` to `ProductDto`), we should configure the object map. Here, an example configuration with [AutoMapper](https://automapper.org/): |
||||
|
|
||||
|
```csharp |
||||
|
public class MyMapperProfile : Profile |
||||
|
{ |
||||
|
public MyMapperProfile() |
||||
|
{ |
||||
|
CreateMap<Product, ProductDto>(); |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
Now, you can inject the `IEntityCache<ProductDto, Guid>` service wherever you want: |
||||
|
|
||||
|
```csharp |
||||
|
public class ProductAppService : ApplicationService, IProductAppService |
||||
|
{ |
||||
|
private readonly IEntityCache<ProductDto, Guid> _productCache; |
||||
|
|
||||
|
public ProductAppService(IEntityCache<ProductDto, Guid> productCache) |
||||
|
{ |
||||
|
_productCache = productCache; |
||||
|
} |
||||
|
|
||||
|
public async Task<ProductDto> GetAsync(Guid id) |
||||
|
{ |
||||
|
return await _productCache.GetAsync(id); |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
Notice that the `_productCache.GetAsync` method already returns a `ProductDto` object, so we could directly return it from out application service. |
||||
|
|
||||
|
## Configuration |
||||
|
|
||||
|
All of the `context.Services.AddEntityCache()` methods get an optional `DistributedCacheEntryOptions` parameter where you can easily configure the caching options: |
||||
|
|
||||
|
```csharp |
||||
|
context.Services.AddEntityCache<Product, ProductDto, Guid>( |
||||
|
new DistributedCacheEntryOptions |
||||
|
{ |
||||
|
SlidingExpiration = TimeSpan.FromMinutes(30) |
||||
|
} |
||||
|
); |
||||
|
``` |
||||
|
|
||||
|
> The default cache duration is **2 minutes** with the `AbsoluteExpirationRelativeToNow` configuration. |
||||
|
|
||||
|
## Additional Notes |
||||
|
|
||||
|
* Entity classes should be serializable/deserializable to/from JSON to be cached (because it's serialized to JSON when saving in the [Distributed Cache](Caching.md)). If your entity class is not serializable, you can consider using a cache-item/DTO class instead, as explained before. |
||||
|
* Entity Caching System is designed as **read-only**. You should use the standard [repository](Repositories.md) methods to manipulate the entity if you need. If you need to manipulate (update) the entity, do not get it from the entity cache. Instead, read it from the repository, change it and update using the repository. |
||||
|
|
||||
|
## See Also |
||||
|
|
||||
|
* [Distributed caching](Caching.md) |
||||
|
* [Entities](Entities.md) |
||||
|
* [Repositories](Repositories.md) |
||||
|
After Width: | Height: | Size: 47 KiB |
@ -1,28 +1,24 @@ |
|||||
using System.Threading.Tasks; |
using System.Net; |
||||
|
using System.Threading.Tasks; |
||||
using Microsoft.AspNetCore.Http; |
using Microsoft.AspNetCore.Http; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
using Volo.CmsKit.Pages; |
using Volo.CmsKit.Pages; |
||||
using Volo.CmsKit.Public.Pages; |
using Volo.CmsKit.Public.Pages; |
||||
|
|
||||
namespace Volo.CmsKit.Public.Web; |
namespace Volo.CmsKit.Public.Web; |
||||
|
|
||||
public class DefaultHomePageMiddleware |
public class DefaultHomePageMiddleware : IMiddleware, ITransientDependency |
||||
{ |
{ |
||||
private readonly RequestDelegate _next; |
public async Task InvokeAsync(HttpContext context, RequestDelegate next) |
||||
private readonly IPagePublicAppService _pagePublicAppService; |
|
||||
|
|
||||
public DefaultHomePageMiddleware(RequestDelegate next, IPagePublicAppService pagePublicAppService) |
|
||||
{ |
|
||||
_next = next; |
|
||||
_pagePublicAppService = pagePublicAppService; |
|
||||
} |
|
||||
public async Task InvokeAsync(HttpContext httpContext) |
|
||||
{ |
{ |
||||
|
var _pagePublicAppService = context.RequestServices.GetRequiredService<IPagePublicAppService>(); |
||||
var page = await _pagePublicAppService.FindDefaultHomePageAsync(); |
var page = await _pagePublicAppService.FindDefaultHomePageAsync(); |
||||
if (page is not null && httpContext.Request.Path.Value == "/") |
if (page is not null && context.Request.Path.Value == "/") |
||||
{ |
{ |
||||
httpContext.Request.Path = $"{PageConsts.UrlPrefix}{page.Slug}"; |
context.Request.Path = $"{PageConsts.UrlPrefix}{page.Slug}"; |
||||
} |
} |
||||
|
|
||||
await _next(httpContext); |
await next(context); |
||||
} |
} |
||||
} |
} |
||||
@ -0,0 +1,7 @@ |
|||||
|
export enum eServiceType { |
||||
|
All = 'all', |
||||
|
Application = 'application', |
||||
|
Integration = 'integration', |
||||
|
} |
||||
|
|
||||
|
export const defaultEServiceType = eServiceType.Application; |
||||
@ -0,0 +1,2 @@ |
|||||
|
#!/usr/bin/env sh |
||||
|
cd npm/ng-packs && npm run lint-staged |
||||
@ -0,0 +1,82 @@ |
|||||
|
using MyCompanyName.MyProjectName.Blazor.Menus; |
||||
|
using MyCompanyName.MyProjectName.Shared.Localization; |
||||
|
using MyCompanyName.MyProjectName.Shared.MultiTenancy; |
||||
|
using Volo.Abp.Account.Localization; |
||||
|
using Volo.Abp.Authorization.Permissions; |
||||
|
using Volo.Abp.Identity.Blazor; |
||||
|
using Volo.Abp.SettingManagement.Blazor.Menus; |
||||
|
using Volo.Abp.TenantManagement.Blazor.Navigation; |
||||
|
using Volo.Abp.UI.Navigation; |
||||
|
|
||||
|
namespace MyCompanyName.MyProjectName.Client.Menus; |
||||
|
|
||||
|
public class MyProjectNameMenuContributor : IMenuContributor |
||||
|
{ |
||||
|
private readonly IConfiguration _configuration; |
||||
|
|
||||
|
public MyProjectNameMenuContributor(IConfiguration configuration) |
||||
|
{ |
||||
|
_configuration = configuration; |
||||
|
} |
||||
|
|
||||
|
public async Task ConfigureMenuAsync(MenuConfigurationContext context) |
||||
|
{ |
||||
|
if (context.Menu.Name == StandardMenus.Main) |
||||
|
{ |
||||
|
await ConfigureMainMenuAsync(context); |
||||
|
} |
||||
|
else if (context.Menu.Name == StandardMenus.User) |
||||
|
{ |
||||
|
await ConfigureUserMenuAsync(context); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private Task ConfigureMainMenuAsync(MenuConfigurationContext context) |
||||
|
{ |
||||
|
var l = context.GetLocalizer<MyProjectNameResource>(); |
||||
|
|
||||
|
context.Menu.Items.Insert( |
||||
|
0, |
||||
|
new ApplicationMenuItem( |
||||
|
MyProjectNameMenus.Home, |
||||
|
l["Menu:Home"], |
||||
|
"/", |
||||
|
icon: "fas fa-home", |
||||
|
order: 0 |
||||
|
) |
||||
|
); |
||||
|
|
||||
|
var administration = context.Menu.GetAdministration(); |
||||
|
|
||||
|
if (MultiTenancyConsts.IsEnabled) |
||||
|
{ |
||||
|
administration.SetSubItemOrder(TenantManagementMenuNames.GroupName, 1); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
administration.TryRemoveMenuItem(TenantManagementMenuNames.GroupName); |
||||
|
} |
||||
|
|
||||
|
administration.SetSubItemOrder(IdentityMenuNames.GroupName, 2); |
||||
|
administration.SetSubItemOrder(SettingManagementMenus.GroupName, 3); |
||||
|
|
||||
|
return Task.CompletedTask; |
||||
|
} |
||||
|
|
||||
|
private Task ConfigureUserMenuAsync(MenuConfigurationContext context) |
||||
|
{ |
||||
|
var accountStringLocalizer = context.GetLocalizer<AccountResource>(); |
||||
|
|
||||
|
var authServerUrl = _configuration["AuthServer:Authority"] ?? ""; |
||||
|
|
||||
|
context.Menu.AddItem(new ApplicationMenuItem( |
||||
|
"Account.Manage", |
||||
|
accountStringLocalizer["MyAccount"], |
||||
|
$"{authServerUrl.EnsureEndsWith('/')}Account/Manage?returnUrl={_configuration["App:SelfUrl"]}", |
||||
|
icon: "fa fa-cog", |
||||
|
order: 1000, |
||||
|
null).RequireAuthenticated()); |
||||
|
|
||||
|
return Task.CompletedTask; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,10 @@ |
|||||
|
namespace MyCompanyName.MyProjectName.Blazor.Menus; |
||||
|
|
||||
|
public class MyProjectNameMenus |
||||
|
{ |
||||
|
private const string Prefix = "MyProjectName"; |
||||
|
public const string Home = Prefix + ".Home"; |
||||
|
|
||||
|
//Add your menu items here...
|
||||
|
|
||||
|
} |
||||
@ -0,0 +1,47 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly"> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>net7.0</TargetFramework> |
||||
|
<Nullable>enable</Nullable> |
||||
|
<ImplicitUsings>enable</ImplicitUsings> |
||||
|
<BlazorWebAssemblyLoadAllGlobalizationData>true</BlazorWebAssemblyLoadAllGlobalizationData> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Blazorise.Bootstrap5" Version="1.1.4.1" /> |
||||
|
<PackageReference Include="Blazorise.Icons.FontAwesome" Version="1.1.4.1" /> |
||||
|
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="7.0.0" /> |
||||
|
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="7.0.0" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<!-- <TEMPLATE-REMOVE> --> |
||||
|
<ProjectReference Include="..\..\..\..\..\framework\src\Volo.Abp.AspNetCore.Components.WebAssembly.Theming\Volo.Abp.AspNetCore.Components.WebAssembly.Theming.csproj" /> |
||||
|
<ProjectReference Include="..\..\..\..\..\framework\src\Volo.Abp.Http.Client.IdentityModel.WebAssembly\Volo.Abp.Http.Client.IdentityModel.WebAssembly.csproj" /> |
||||
|
<ProjectReference Include="..\..\..\..\..\framework\src\Volo.Abp.AspNetCore.Components.Web.Theming\Volo.Abp.AspNetCore.Components.Web.Theming.csproj" /> |
||||
|
<!-- </TEMPLATE-REMOVE> --> |
||||
|
<PackageReference Include="Volo.Abp.AspNetCore.Components.WebAssembly.LeptonXLiteTheme" Version="2.0.0-*" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\..\..\..\..\framework\src\Volo.Abp.Autofac.WebAssembly\Volo.Abp.Autofac.WebAssembly.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\..\..\..\..\modules\identity\src\Volo.Abp.Identity.Blazor.WebAssembly\Volo.Abp.Identity.Blazor.WebAssembly.csproj" /> |
||||
|
<ProjectReference Include="..\..\..\..\..\modules\tenant-management\src\Volo.Abp.TenantManagement.Blazor.WebAssembly\Volo.Abp.TenantManagement.Blazor.WebAssembly.csproj" /> |
||||
|
<ProjectReference Include="..\..\..\..\..\modules\setting-management\src\Volo.Abp.SettingManagement.Blazor.WebAssembly\Volo.Abp.SettingManagement.Blazor.WebAssembly.csproj" /> |
||||
|
<ProjectReference Include="..\..\..\..\..\modules\account\src\Volo.Abp.Account.HttpApi.Client\Volo.Abp.Account.HttpApi.Client.csproj" /> |
||||
|
<ProjectReference Include="..\..\..\..\..\modules\identity\src\Volo.Abp.Identity.HttpApi.Client\Volo.Abp.Identity.HttpApi.Client.csproj" /> |
||||
|
<ProjectReference Include="..\..\..\..\..\modules\permission-management\src\Volo.Abp.PermissionManagement.HttpApi.Client\Volo.Abp.PermissionManagement.HttpApi.Client.csproj" /> |
||||
|
<ProjectReference Include="..\..\..\..\..\modules\tenant-management\src\Volo.Abp.TenantManagement.HttpApi.Client\Volo.Abp.TenantManagement.HttpApi.Client.csproj" /> |
||||
|
<ProjectReference Include="..\..\..\..\..\modules\feature-management\src\Volo.Abp.FeatureManagement.HttpApi.Client\Volo.Abp.FeatureManagement.HttpApi.Client.csproj" /> |
||||
|
<ProjectReference Include="..\..\..\..\..\modules\setting-management\src\Volo.Abp.SettingManagement.HttpApi.Client\Volo.Abp.SettingManagement.HttpApi.Client.csproj" /> |
||||
|
<ProjectReference Include="..\..\..\..\..\modules\openiddict\src\Volo.Abp.OpenIddict.Domain.Shared\Volo.Abp.OpenIddict.Domain.Shared.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\Shared\MyCompanyName.MyProjectName.Blazor.WebAssembly.Shared.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,11 @@ |
|||||
|
using AutoMapper; |
||||
|
|
||||
|
namespace MyCompanyName.MyProjectName.Client; |
||||
|
|
||||
|
public class MyProjectNameBlazorAutoMapperProfile : Profile |
||||
|
{ |
||||
|
public MyProjectNameBlazorAutoMapperProfile() |
||||
|
{ |
||||
|
//Define your AutoMapper configuration here for the Blazor project.
|
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,142 @@ |
|||||
|
using Blazorise.Bootstrap5; |
||||
|
using Blazorise.Icons.FontAwesome; |
||||
|
using Microsoft.AspNetCore.Components.WebAssembly.Hosting; |
||||
|
using MyCompanyName.MyProjectName.Client.Menus; |
||||
|
using MyCompanyName.MyProjectName.Shared; |
||||
|
using OpenIddict.Abstractions; |
||||
|
using Volo.Abp.Account; |
||||
|
using Volo.Abp.AspNetCore.Components.Web.LeptonXLiteTheme.Themes.LeptonXLite; |
||||
|
using Volo.Abp.AspNetCore.Components.Web.Theming.Routing; |
||||
|
using Volo.Abp.AspNetCore.Components.WebAssembly.LeptonXLiteTheme; |
||||
|
using Volo.Abp.Autofac.WebAssembly; |
||||
|
using Volo.Abp.AutoMapper; |
||||
|
using Volo.Abp.FeatureManagement; |
||||
|
using Volo.Abp.Identity; |
||||
|
using Volo.Abp.Identity.Blazor.WebAssembly; |
||||
|
using Volo.Abp.Modularity; |
||||
|
using Volo.Abp.OpenIddict; |
||||
|
using Volo.Abp.PermissionManagement; |
||||
|
using Volo.Abp.SettingManagement; |
||||
|
using Volo.Abp.SettingManagement.Blazor.WebAssembly; |
||||
|
using Volo.Abp.TenantManagement; |
||||
|
using Volo.Abp.TenantManagement.Blazor.WebAssembly; |
||||
|
using Volo.Abp.UI.Navigation; |
||||
|
|
||||
|
namespace MyCompanyName.MyProjectName.Client; |
||||
|
|
||||
|
[DependsOn( |
||||
|
typeof(MyProjectNameSharedModule), |
||||
|
|
||||
|
// ABP Framework packages
|
||||
|
typeof(AbpAutofacWebAssemblyModule), |
||||
|
typeof(AbpAspNetCoreComponentsWebAssemblyLeptonXLiteThemeModule), |
||||
|
|
||||
|
// Account module packages
|
||||
|
typeof(AbpAccountHttpApiClientModule), |
||||
|
|
||||
|
// Identity module packages
|
||||
|
typeof(AbpIdentityHttpApiClientModule), |
||||
|
typeof(AbpIdentityBlazorWebAssemblyModule), |
||||
|
typeof(AbpOpenIddictDomainSharedModule), |
||||
|
|
||||
|
// Permission Management module packages
|
||||
|
typeof(AbpPermissionManagementHttpApiClientModule), |
||||
|
|
||||
|
// Tenant Management module packages
|
||||
|
typeof(AbpTenantManagementHttpApiClientModule), |
||||
|
typeof(AbpTenantManagementBlazorWebAssemblyModule), |
||||
|
|
||||
|
// Feature Management module packages
|
||||
|
typeof(AbpFeatureManagementHttpApiClientModule), |
||||
|
|
||||
|
// Setting Management module packages
|
||||
|
typeof(AbpSettingManagementHttpApiClientModule), |
||||
|
typeof(AbpSettingManagementBlazorWebAssemblyModule) |
||||
|
)] |
||||
|
public class MyProjectNameBlazorModule : AbpModule |
||||
|
{ |
||||
|
public const string RemoteServiceName = "Default"; |
||||
|
|
||||
|
public override void ConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
var environment = context.Services.GetSingletonInstance<IWebAssemblyHostEnvironment>(); |
||||
|
var builder = context.Services.GetSingletonInstance<WebAssemblyHostBuilder>(); |
||||
|
|
||||
|
ConfigureAuthentication(builder); |
||||
|
ConfigureHttpClient(context, environment); |
||||
|
ConfigureBlazorise(context); |
||||
|
ConfigureRouter(context); |
||||
|
ConfigureUI(builder); |
||||
|
ConfigureMenu(context); |
||||
|
ConfigureAutoMapper(context); |
||||
|
ConfigureHttpClientProxies(context); |
||||
|
} |
||||
|
|
||||
|
private void ConfigureRouter(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
Configure<AbpRouterOptions>(options => |
||||
|
{ |
||||
|
options.AppAssembly = typeof(MyProjectNameBlazorModule).Assembly; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
private void ConfigureMenu(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
Configure<AbpNavigationOptions>(options => |
||||
|
{ |
||||
|
options.MenuContributors.Add(new MyProjectNameMenuContributor(context.Services.GetConfiguration())); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
private void ConfigureBlazorise(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
context.Services |
||||
|
.AddBootstrap5Providers() |
||||
|
.AddFontAwesomeIcons(); |
||||
|
} |
||||
|
|
||||
|
private void ConfigureHttpClientProxies(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
context.Services.AddHttpClientProxies( |
||||
|
typeof(MyProjectNameSharedModule).Assembly, |
||||
|
RemoteServiceName |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
private static void ConfigureAuthentication(WebAssemblyHostBuilder builder) |
||||
|
{ |
||||
|
builder.Services.AddOidcAuthentication(options => |
||||
|
{ |
||||
|
builder.Configuration.Bind("AuthServer", options.ProviderOptions); |
||||
|
options.UserOptions.NameClaim = OpenIddictConstants.Claims.Name; |
||||
|
options.UserOptions.RoleClaim = OpenIddictConstants.Claims.Role; |
||||
|
|
||||
|
options.ProviderOptions.DefaultScopes.Add("MyProjectName"); |
||||
|
options.ProviderOptions.DefaultScopes.Add("roles"); |
||||
|
options.ProviderOptions.DefaultScopes.Add("email"); |
||||
|
options.ProviderOptions.DefaultScopes.Add("phone"); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
private static void ConfigureUI(WebAssemblyHostBuilder builder) |
||||
|
{ |
||||
|
builder.RootComponents.Add<App>("#ApplicationContainer"); |
||||
|
|
||||
|
} |
||||
|
|
||||
|
private static void ConfigureHttpClient(ServiceConfigurationContext context, IWebAssemblyHostEnvironment environment) |
||||
|
{ |
||||
|
context.Services.AddTransient(sp => new HttpClient |
||||
|
{ |
||||
|
BaseAddress = new Uri(environment.BaseAddress) |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
private void ConfigureAutoMapper(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
Configure<AbpAutoMapperOptions>(options => |
||||
|
{ |
||||
|
options.AddMaps<MyProjectNameBlazorModule>(); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,10 @@ |
|||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.Ui.Branding; |
||||
|
|
||||
|
namespace MyCompanyName.MyProjectName.Client; |
||||
|
|
||||
|
[Dependency(ReplaceServices = true)] |
||||
|
public class MyProjectNameBrandingProvider : DefaultBrandingProvider |
||||
|
{ |
||||
|
public override string AppName => "MyProjectName"; |
||||
|
} |
||||
@ -0,0 +1,19 @@ |
|||||
|
using Volo.Abp.Bundling; |
||||
|
|
||||
|
namespace MyCompanyName.MyProjectName.Client; |
||||
|
|
||||
|
/* Add your global styles/scripts here. |
||||
|
* See https://docs.abp.io/en/abp/latest/UI/Blazor/Global-Scripts-Styles to learn how to use it
|
||||
|
*/ |
||||
|
public class MyProjectNameBundleContributor : IBundleContributor |
||||
|
{ |
||||
|
public void AddScripts(BundleContext context) |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
|
||||
|
public void AddStyles(BundleContext context) |
||||
|
{ |
||||
|
context.Add("main.css", true); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,12 @@ |
|||||
|
using MyCompanyName.MyProjectName.Shared.Localization; |
||||
|
using Volo.Abp.AspNetCore.Components; |
||||
|
|
||||
|
namespace MyCompanyName.MyProjectName.Client; |
||||
|
|
||||
|
public abstract class MyProjectNameComponentBase : AbpComponentBase |
||||
|
{ |
||||
|
protected MyProjectNameComponentBase() |
||||
|
{ |
||||
|
LocalizationResource = typeof(MyProjectNameResource); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,39 @@ |
|||||
|
@page "/" |
||||
|
@inherits MyProjectNameComponentBase |
||||
|
<div class="container"> |
||||
|
<div class="card"> |
||||
|
<div class="card-header"> |
||||
|
<h5 class="card-title"> |
||||
|
@L["Welcome_Title"] |
||||
|
</h5> |
||||
|
</div> |
||||
|
<div class="card-body"> |
||||
|
@L["Welcome_Text"] |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<div class="card mt-5"> |
||||
|
<div class="card-body"> |
||||
|
<div class="row"> |
||||
|
<div class="col-md-auto text-center"> |
||||
|
<img src="https://abp.io/assets/png/mastering-abp-framework.webp" style="max-width: 400px;" class="w-100 mb-5 my-md-3"> |
||||
|
</div> |
||||
|
<div class="col-md d-flex align-items-center"> |
||||
|
<div class="pe-0 pe-md-4"> |
||||
|
<small class="text-uppercase text-muted">THE OFFICIAL GUIDE</small> |
||||
|
<h2 class="mb-4">Mastering ABP Framework</h2> |
||||
|
<p class="mb-4">Written by the creator of the ABP Framework, this book will help you gain a complete understanding of the framework and modern web application development techniques.</p> |
||||
|
<div class="mb-4"> |
||||
|
<a href="https://www.amazon.com/gp/product/B097Z2DM8Q/ref=dbs_a_def_rwt_hsch_vapi_tkin_p1_i0" class="btn btn-success mb-1"> |
||||
|
Buy on Amazon US |
||||
|
</a> |
||||
|
<a href="https://www.packtpub.com/product/mastering-abp-framework/9781801079242" class="btn btn-primary mb-1"> |
||||
|
Buy on PACKT |
||||
|
</a> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
@ -0,0 +1,6 @@ |
|||||
|
namespace MyCompanyName.MyProjectName.Client.Pages; |
||||
|
|
||||
|
public partial class Index |
||||
|
{ |
||||
|
|
||||
|
} |
||||
@ -0,0 +1 @@ |
|||||
|
/* Write here your styles for the Index page */ |
||||
@ -0,0 +1,22 @@ |
|||||
|
using Microsoft.AspNetCore.Components.WebAssembly.Hosting; |
||||
|
|
||||
|
namespace MyCompanyName.MyProjectName.Client; |
||||
|
|
||||
|
public class Program |
||||
|
{ |
||||
|
public async static Task Main(string[] args) |
||||
|
{ |
||||
|
var builder = WebAssemblyHostBuilder.CreateDefault(args); |
||||
|
|
||||
|
var application = await builder.AddApplicationAsync<MyProjectNameBlazorModule>(options => |
||||
|
{ |
||||
|
options.UseAutofac(); |
||||
|
}); |
||||
|
|
||||
|
var host = builder.Build(); |
||||
|
|
||||
|
await application.InitializeApplicationAsync(host.Services); |
||||
|
|
||||
|
await host.RunAsync(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,30 @@ |
|||||
|
{ |
||||
|
"iisSettings": { |
||||
|
"windowsAuthentication": false, |
||||
|
"anonymousAuthentication": true, |
||||
|
"iisExpress": { |
||||
|
"applicationUrl": "https://localhost:44300", |
||||
|
"sslPort": 44300 |
||||
|
} |
||||
|
}, |
||||
|
"profiles": { |
||||
|
"https": { |
||||
|
"commandName": "Project", |
||||
|
"dotnetRunMessages": true, |
||||
|
"launchBrowser": true, |
||||
|
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", |
||||
|
"applicationUrl": "https://localhost:44300", |
||||
|
"environmentVariables": { |
||||
|
"ASPNETCORE_ENVIRONMENT": "Development" |
||||
|
} |
||||
|
}, |
||||
|
"IIS Express": { |
||||
|
"commandName": "IISExpress", |
||||
|
"launchBrowser": true, |
||||
|
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", |
||||
|
"environmentVariables": { |
||||
|
"ASPNETCORE_ENVIRONMENT": "Development" |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,14 @@ |
|||||
|
@using System.Net.Http |
||||
|
@using Microsoft.AspNetCore.Authorization |
||||
|
@using Microsoft.AspNetCore.Components.Authorization |
||||
|
@using Microsoft.AspNetCore.Components.Forms |
||||
|
@using Microsoft.AspNetCore.Components.Routing |
||||
|
@using Microsoft.AspNetCore.Components.Web |
||||
|
@using Microsoft.AspNetCore.Components.WebAssembly.Http |
||||
|
@using Microsoft.JSInterop |
||||
|
@using Volo.Abp.AspNetCore.Components.Web |
||||
|
@using MyCompanyName.MyProjectName.Client |
||||
|
@using Blazorise |
||||
|
@using Blazorise.DataGrid |
||||
|
@using Volo.Abp.BlazoriseUI |
||||
|
@using Volo.Abp.BlazoriseUI.Components |
||||
@ -0,0 +1,24 @@ |
|||||
|
{ |
||||
|
"App": { |
||||
|
"SelfUrl": "https://localhost:44300" |
||||
|
}, |
||||
|
"AuthServer": { |
||||
|
"Authority": "https://localhost:44300", |
||||
|
"ClientId": "MyProjectName_Blazor", |
||||
|
"ResponseType": "code" |
||||
|
}, |
||||
|
"RemoteServices": { |
||||
|
"Default": { |
||||
|
"BaseUrl": "https://localhost:44300" |
||||
|
} |
||||
|
}, |
||||
|
"AbpCli": { |
||||
|
"Bundle": { |
||||
|
"Mode": "BundleAndMinify", /* Options: None, Bundle, BundleAndMinify */ |
||||
|
"Name": "global", |
||||
|
"Parameters": { |
||||
|
|
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 6.2 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 46 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 38 KiB |
@ -0,0 +1,35 @@ |
|||||
|
<!DOCTYPE html> |
||||
|
<html> |
||||
|
|
||||
|
<head> |
||||
|
<meta charset="utf-8" /> |
||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" /> |
||||
|
<title>MyCompanyName.MyProjectName.Blazor</title> |
||||
|
<base href="/" /> |
||||
|
|
||||
|
<!--ABP:Styles--> |
||||
|
<link href="global.css?_v=638058527301625844" rel="stylesheet"/> |
||||
|
<link href="main.css" rel="stylesheet"/> |
||||
|
<!--/ABP:Styles--> |
||||
|
<link href="MyCompanyName.MyProjectName.Blazor.WebAssembly.Client.styles.css" rel="stylesheet"/> |
||||
|
|
||||
|
<!-- <TEMPLATE-REMOVE IF-NOT='PWA'> --> |
||||
|
<link href="manifest.json" rel="manifest" /> |
||||
|
<link rel="apple-touch-icon" sizes="512x512" href="icon-512.png" /> |
||||
|
<link rel="apple-touch-icon" sizes="192x192" href="icon-192.png" /> |
||||
|
<!-- </TEMPLATE-REMOVE> --> |
||||
|
</head> |
||||
|
|
||||
|
<body class="abp-application-layout bg-light"> |
||||
|
<div id="ApplicationContainer"> |
||||
|
<div class="spinner"> |
||||
|
<div class="double-bounce1"></div> |
||||
|
<div class="double-bounce2"></div> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<!--ABP:Scripts--> |
||||
|
<script src="global.js?_v=638058527305084060"></script> |
||||
|
<!--/ABP:Scripts--> |
||||
|
</body> |
||||
|
</html> |
||||
@ -0,0 +1,57 @@ |
|||||
|
/* Global styles for the MyProjectName application */ |
||||
|
|
||||
|
/* <TEMPLATE-REMOVE IF-NOT='LEPTONX'> */ |
||||
|
:root .lpx-brand-logo { |
||||
|
--lpx-logo: url('/images/logo/leptonx/logo-light.png'); |
||||
|
--lpx-logo-icon: url('/images/logo/leptonx/logo-light-thumbnail.png'); |
||||
|
} |
||||
|
/* </TEMPLATE-REMOVE> */ |
||||
|
|
||||
|
.spinner { |
||||
|
width: 40px; |
||||
|
height: 40px; |
||||
|
display: block; |
||||
|
position: fixed; |
||||
|
top: calc( 50% - ( 40px / 2) ); |
||||
|
right: calc( 50% - ( 40px / 2) ); |
||||
|
} |
||||
|
|
||||
|
.double-bounce1, .double-bounce2 { |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
border-radius: 50%; |
||||
|
background-color: #333; |
||||
|
opacity: 0.6; |
||||
|
position: absolute; |
||||
|
top: 0; |
||||
|
left: 0; |
||||
|
-webkit-animation: sk-bounce 2.0s infinite ease-in-out; |
||||
|
animation: sk-bounce 2.0s infinite ease-in-out; |
||||
|
} |
||||
|
|
||||
|
.double-bounce2 { |
||||
|
-webkit-animation-delay: -1.0s; |
||||
|
animation-delay: -1.0s; |
||||
|
} |
||||
|
|
||||
|
@-webkit-keyframes sk-bounce { |
||||
|
0%, 100% { |
||||
|
-webkit-transform: scale(0.0) |
||||
|
} |
||||
|
|
||||
|
50% { |
||||
|
-webkit-transform: scale(1.0) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@keyframes sk-bounce { |
||||
|
0%, 100% { |
||||
|
transform: scale(0.0); |
||||
|
-webkit-transform: scale(0.0); |
||||
|
} |
||||
|
|
||||
|
50% { |
||||
|
transform: scale(1.0); |
||||
|
-webkit-transform: scale(1.0); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,21 @@ |
|||||
|
{ |
||||
|
"name": "MyProjectName", |
||||
|
"short_name": "MyCompanyName.MyProjectName", |
||||
|
"start_url": "./", |
||||
|
"display": "standalone", |
||||
|
"background_color": "#ffffff", |
||||
|
"theme_color": "#03173d", |
||||
|
"prefer_related_applications": false, |
||||
|
"icons": [ |
||||
|
{ |
||||
|
"src": "icon-512.png", |
||||
|
"type": "image/png", |
||||
|
"sizes": "512x512" |
||||
|
}, |
||||
|
{ |
||||
|
"src": "icon-192.png", |
||||
|
"type": "image/png", |
||||
|
"sizes": "192x192" |
||||
|
} |
||||
|
] |
||||
|
} |
||||
@ -0,0 +1,22 @@ |
|||||
|
using Volo.Abp.Data; |
||||
|
using Volo.Abp.MongoDB; |
||||
|
|
||||
|
namespace MyCompanyName.MyProjectName.Server.Data; |
||||
|
|
||||
|
[ConnectionStringName("Default")] |
||||
|
public class MyProjectNameDbContext : AbpMongoDbContext |
||||
|
{ |
||||
|
/* Add mongo collections here. Example: |
||||
|
* public IMongoCollection<Question> Questions => Collection<Question>(); |
||||
|
*/ |
||||
|
|
||||
|
protected override void CreateModel(IMongoModelBuilder modelBuilder) |
||||
|
{ |
||||
|
base.CreateModel(modelBuilder); |
||||
|
|
||||
|
//builder.Entity<YourEntity>(b =>
|
||||
|
//{
|
||||
|
// //...
|
||||
|
//});
|
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,88 @@ |
|||||
|
using Microsoft.Extensions.Logging.Abstractions; |
||||
|
using Volo.Abp.Data; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.Identity; |
||||
|
using Volo.Abp.MultiTenancy; |
||||
|
using Volo.Abp.TenantManagement; |
||||
|
|
||||
|
namespace MyCompanyName.MyProjectName.Server.Data; |
||||
|
|
||||
|
public class MyProjectNameDbMigrationService : ITransientDependency |
||||
|
{ |
||||
|
public ILogger<MyProjectNameDbMigrationService> Logger { get; set; } |
||||
|
|
||||
|
private readonly IDataSeeder _dataSeeder; |
||||
|
private readonly MyProjectNameMongoDbSchemaMigrator _dbSchemaMigrator; |
||||
|
private readonly ITenantRepository _tenantRepository; |
||||
|
private readonly ICurrentTenant _currentTenant; |
||||
|
|
||||
|
public MyProjectNameDbMigrationService( |
||||
|
IDataSeeder dataSeeder, |
||||
|
MyProjectNameMongoDbSchemaMigrator dbSchemaMigrator, |
||||
|
ITenantRepository tenantRepository, |
||||
|
ICurrentTenant currentTenant) |
||||
|
{ |
||||
|
_dataSeeder = dataSeeder; |
||||
|
_dbSchemaMigrator = dbSchemaMigrator; |
||||
|
_tenantRepository = tenantRepository; |
||||
|
_currentTenant = currentTenant; |
||||
|
|
||||
|
Logger = NullLogger<MyProjectNameDbMigrationService>.Instance; |
||||
|
} |
||||
|
|
||||
|
public async Task MigrateAsync() |
||||
|
{ |
||||
|
Logger.LogInformation("Started database migrations..."); |
||||
|
|
||||
|
await MigrateDatabaseSchemaAsync(); |
||||
|
await SeedDataAsync(); |
||||
|
|
||||
|
Logger.LogInformation($"Successfully completed host database migrations."); |
||||
|
|
||||
|
var tenants = await _tenantRepository.GetListAsync(includeDetails: true); |
||||
|
|
||||
|
var migratedDatabaseSchemas = new HashSet<string>(); |
||||
|
foreach (var tenant in tenants) |
||||
|
{ |
||||
|
using (_currentTenant.Change(tenant.Id)) |
||||
|
{ |
||||
|
if (tenant.ConnectionStrings.Any()) |
||||
|
{ |
||||
|
var tenantConnectionStrings = tenant.ConnectionStrings |
||||
|
.Select(x => x.Value) |
||||
|
.ToList(); |
||||
|
|
||||
|
if (!migratedDatabaseSchemas.IsSupersetOf(tenantConnectionStrings)) |
||||
|
{ |
||||
|
await MigrateDatabaseSchemaAsync(tenant); |
||||
|
|
||||
|
migratedDatabaseSchemas.AddIfNotContains(tenantConnectionStrings); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
await SeedDataAsync(tenant); |
||||
|
} |
||||
|
|
||||
|
Logger.LogInformation($"Successfully completed {tenant.Name} tenant database migrations."); |
||||
|
} |
||||
|
|
||||
|
Logger.LogInformation("Successfully completed all database migrations."); |
||||
|
Logger.LogInformation("You can safely end this process..."); |
||||
|
} |
||||
|
|
||||
|
private async Task MigrateDatabaseSchemaAsync(Tenant? tenant = null) |
||||
|
{ |
||||
|
Logger.LogInformation($"Migrating schema for {(tenant == null ? "host" : tenant.Name + " tenant")} database..."); |
||||
|
await _dbSchemaMigrator.MigrateAsync(); |
||||
|
} |
||||
|
|
||||
|
private async Task SeedDataAsync(Tenant? tenant = null) |
||||
|
{ |
||||
|
Logger.LogInformation($"Executing {(tenant == null ? "host" : tenant.Name + " tenant")} database seed..."); |
||||
|
|
||||
|
await _dataSeeder.SeedAsync(new DataSeedContext(tenant?.Id) |
||||
|
.WithProperty(IdentityDataSeedContributor.AdminEmailPropertyName, IdentityDataSeedContributor.AdminEmailDefaultValue) |
||||
|
.WithProperty(IdentityDataSeedContributor.AdminPasswordPropertyName, IdentityDataSeedContributor.AdminPasswordDefaultValue) |
||||
|
); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,39 @@ |
|||||
|
using MongoDB.Driver; |
||||
|
using Volo.Abp.Data; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.MongoDB; |
||||
|
|
||||
|
namespace MyCompanyName.MyProjectName.Server.Data; |
||||
|
|
||||
|
public class MyProjectNameMongoDbSchemaMigrator : ITransientDependency |
||||
|
{ |
||||
|
private readonly IServiceProvider _serviceProvider; |
||||
|
|
||||
|
public MyProjectNameMongoDbSchemaMigrator(IServiceProvider serviceProvider) |
||||
|
{ |
||||
|
_serviceProvider = serviceProvider; |
||||
|
} |
||||
|
|
||||
|
public async Task MigrateAsync() |
||||
|
{ |
||||
|
var dbContexts = _serviceProvider.GetServices<IAbpMongoDbContext>(); |
||||
|
var connectionStringResolver = _serviceProvider.GetRequiredService<IConnectionStringResolver>(); |
||||
|
|
||||
|
foreach (var dbContext in dbContexts) |
||||
|
{ |
||||
|
var connectionString = |
||||
|
await connectionStringResolver.ResolveAsync( |
||||
|
ConnectionStringNameAttribute.GetConnStringName(dbContext.GetType())); |
||||
|
var mongoUrl = new MongoUrl(connectionString); |
||||
|
var databaseName = mongoUrl.DatabaseName; |
||||
|
var client = new MongoClient(mongoUrl); |
||||
|
|
||||
|
if (databaseName.IsNullOrWhiteSpace()) |
||||
|
{ |
||||
|
databaseName = ConnectionStringNameAttribute.GetConnStringName(dbContext.GetType()); |
||||
|
} |
||||
|
|
||||
|
(dbContext as AbpMongoDbContext)?.InitializeCollections(client.GetDatabase(databaseName)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,308 @@ |
|||||
|
using JetBrains.Annotations; |
||||
|
using Microsoft.Extensions.Localization; |
||||
|
using OpenIddict.Abstractions; |
||||
|
using Volo.Abp; |
||||
|
using Volo.Abp.Authorization.Permissions; |
||||
|
using Volo.Abp.Data; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.OpenIddict.Applications; |
||||
|
using Volo.Abp.PermissionManagement; |
||||
|
using Volo.Abp.Uow; |
||||
|
|
||||
|
namespace MyCompanyName.MyProjectName.Server.Data; |
||||
|
|
||||
|
/* Creates initial data that is needed to property run the application |
||||
|
* and make client-to-server communication possible. |
||||
|
*/ |
||||
|
public class OpenIddictDataSeedContributor : IDataSeedContributor, ITransientDependency |
||||
|
{ |
||||
|
private readonly IConfiguration _configuration; |
||||
|
private readonly IOpenIddictApplicationManager _applicationManager; |
||||
|
private readonly IOpenIddictScopeManager _scopeManager; |
||||
|
private readonly IPermissionDataSeeder _permissionDataSeeder; |
||||
|
private readonly IStringLocalizer<OpenIddictResponse> L; |
||||
|
|
||||
|
public OpenIddictDataSeedContributor( |
||||
|
IConfiguration configuration, |
||||
|
IOpenIddictApplicationManager applicationManager, |
||||
|
IOpenIddictScopeManager scopeManager, |
||||
|
IPermissionDataSeeder permissionDataSeeder, |
||||
|
IStringLocalizer<OpenIddictResponse> l) |
||||
|
{ |
||||
|
_configuration = configuration; |
||||
|
_applicationManager = applicationManager; |
||||
|
_scopeManager = scopeManager; |
||||
|
_permissionDataSeeder = permissionDataSeeder; |
||||
|
L = l; |
||||
|
} |
||||
|
|
||||
|
[UnitOfWork] |
||||
|
public virtual async Task SeedAsync(DataSeedContext context) |
||||
|
{ |
||||
|
await CreateScopesAsync(); |
||||
|
await CreateApplicationsAsync(); |
||||
|
} |
||||
|
|
||||
|
private async Task CreateScopesAsync() |
||||
|
{ |
||||
|
if (await _scopeManager.FindByNameAsync("MyProjectName") == null) |
||||
|
{ |
||||
|
await _scopeManager.CreateAsync(new OpenIddictScopeDescriptor |
||||
|
{ |
||||
|
Name = "MyProjectName", |
||||
|
DisplayName = "MyProjectName API", |
||||
|
Resources = |
||||
|
{ |
||||
|
"MyProjectName" |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private async Task CreateApplicationsAsync() |
||||
|
{ |
||||
|
var commonScopes = new List<string> |
||||
|
{ |
||||
|
OpenIddictConstants.Permissions.Scopes.Address, |
||||
|
OpenIddictConstants.Permissions.Scopes.Email, |
||||
|
OpenIddictConstants.Permissions.Scopes.Phone, |
||||
|
OpenIddictConstants.Permissions.Scopes.Profile, |
||||
|
OpenIddictConstants.Permissions.Scopes.Roles, |
||||
|
"MyProjectName" |
||||
|
}; |
||||
|
|
||||
|
var configurationSection = _configuration.GetSection("OpenIddict:Applications"); |
||||
|
|
||||
|
//Blazor Client
|
||||
|
var blazorClientId = configurationSection["MyProjectName_Blazor:ClientId"]; |
||||
|
if (!blazorClientId.IsNullOrWhiteSpace()) |
||||
|
{ |
||||
|
var blazorRootUrl = configurationSection["MyProjectName_Blazor:RootUrl"]?.TrimEnd('/'); |
||||
|
await CreateApplicationAsync( |
||||
|
name: blazorClientId!, |
||||
|
type: OpenIddictConstants.ClientTypes.Public, |
||||
|
consentType: OpenIddictConstants.ConsentTypes.Implicit, |
||||
|
displayName: "Blazor Application", |
||||
|
secret: null, |
||||
|
grantTypes: new List<string> |
||||
|
{ |
||||
|
OpenIddictConstants.GrantTypes.AuthorizationCode |
||||
|
}, |
||||
|
scopes: commonScopes, |
||||
|
redirectUri: $"{blazorRootUrl}/authentication/login-callback", |
||||
|
clientUri: blazorRootUrl, |
||||
|
postLogoutRedirectUri: $"{blazorRootUrl}/authentication/logout-callback" |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
// Swagger Client
|
||||
|
var swaggerClientId = configurationSection["MyProjectName_Swagger:ClientId"]; |
||||
|
if (!swaggerClientId.IsNullOrWhiteSpace()) |
||||
|
{ |
||||
|
var swaggerRootUrl = configurationSection["MyProjectName_Swagger:RootUrl"]?.TrimEnd('/'); |
||||
|
|
||||
|
await CreateApplicationAsync( |
||||
|
name: swaggerClientId!, |
||||
|
type: OpenIddictConstants.ClientTypes.Public, |
||||
|
consentType: OpenIddictConstants.ConsentTypes.Implicit, |
||||
|
displayName: "Swagger Application", |
||||
|
secret: null, |
||||
|
grantTypes: new List<string> |
||||
|
{ |
||||
|
OpenIddictConstants.GrantTypes.AuthorizationCode, |
||||
|
}, |
||||
|
scopes: commonScopes, |
||||
|
redirectUri: $"{swaggerRootUrl}/swagger/oauth2-redirect.html" |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private async Task CreateApplicationAsync( |
||||
|
[NotNull] string name, |
||||
|
[NotNull] string type, |
||||
|
[NotNull] string consentType, |
||||
|
string displayName, |
||||
|
string? secret, |
||||
|
List<string> grantTypes, |
||||
|
List<string> scopes, |
||||
|
string? clientUri = null, |
||||
|
string? redirectUri = null, |
||||
|
string? postLogoutRedirectUri = null, |
||||
|
List<string>? permissions = null) |
||||
|
{ |
||||
|
if (!string.IsNullOrEmpty(secret) && string.Equals(type, OpenIddictConstants.ClientTypes.Public, StringComparison.OrdinalIgnoreCase)) |
||||
|
{ |
||||
|
throw new BusinessException(L["NoClientSecretCanBeSetForPublicApplications"]); |
||||
|
} |
||||
|
|
||||
|
if (string.IsNullOrEmpty(secret) && string.Equals(type, OpenIddictConstants.ClientTypes.Confidential, StringComparison.OrdinalIgnoreCase)) |
||||
|
{ |
||||
|
throw new BusinessException(L["TheClientSecretIsRequiredForConfidentialApplications"]); |
||||
|
} |
||||
|
|
||||
|
if (!string.IsNullOrEmpty(name) && await _applicationManager.FindByClientIdAsync(name) != null) |
||||
|
{ |
||||
|
return; |
||||
|
//throw new BusinessException(L["TheClientIdentifierIsAlreadyTakenByAnotherApplication"]);
|
||||
|
} |
||||
|
|
||||
|
var client = await _applicationManager.FindByClientIdAsync(name); |
||||
|
if (client == null) |
||||
|
{ |
||||
|
var application = new AbpApplicationDescriptor |
||||
|
{ |
||||
|
ClientId = name, |
||||
|
Type = type, |
||||
|
ClientSecret = secret, |
||||
|
ConsentType = consentType, |
||||
|
DisplayName = displayName, |
||||
|
ClientUri = clientUri |
||||
|
}; |
||||
|
|
||||
|
Check.NotNullOrEmpty(grantTypes, nameof(grantTypes)); |
||||
|
Check.NotNullOrEmpty(scopes, nameof(scopes)); |
||||
|
|
||||
|
if (new [] { OpenIddictConstants.GrantTypes.AuthorizationCode, OpenIddictConstants.GrantTypes.Implicit }.All(grantTypes.Contains)) |
||||
|
{ |
||||
|
application.Permissions.Add(OpenIddictConstants.Permissions.ResponseTypes.CodeIdToken); |
||||
|
|
||||
|
if (string.Equals(type, OpenIddictConstants.ClientTypes.Public, StringComparison.OrdinalIgnoreCase)) |
||||
|
{ |
||||
|
application.Permissions.Add(OpenIddictConstants.Permissions.ResponseTypes.CodeIdTokenToken); |
||||
|
application.Permissions.Add(OpenIddictConstants.Permissions.ResponseTypes.CodeToken); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (!redirectUri.IsNullOrWhiteSpace() || !postLogoutRedirectUri.IsNullOrWhiteSpace()) |
||||
|
{ |
||||
|
application.Permissions.Add(OpenIddictConstants.Permissions.Endpoints.Logout); |
||||
|
} |
||||
|
|
||||
|
foreach (var grantType in grantTypes) |
||||
|
{ |
||||
|
if (grantType == OpenIddictConstants.GrantTypes.AuthorizationCode) |
||||
|
{ |
||||
|
application.Permissions.Add(OpenIddictConstants.Permissions.GrantTypes.AuthorizationCode); |
||||
|
application.Permissions.Add(OpenIddictConstants.Permissions.ResponseTypes.Code); |
||||
|
} |
||||
|
|
||||
|
if (grantType == OpenIddictConstants.GrantTypes.AuthorizationCode || grantType == OpenIddictConstants.GrantTypes.Implicit) |
||||
|
{ |
||||
|
application.Permissions.Add(OpenIddictConstants.Permissions.Endpoints.Authorization); |
||||
|
} |
||||
|
|
||||
|
if (grantType == OpenIddictConstants.GrantTypes.AuthorizationCode || |
||||
|
grantType == OpenIddictConstants.GrantTypes.ClientCredentials || |
||||
|
grantType == OpenIddictConstants.GrantTypes.Password || |
||||
|
grantType == OpenIddictConstants.GrantTypes.RefreshToken || |
||||
|
grantType == OpenIddictConstants.GrantTypes.DeviceCode) |
||||
|
{ |
||||
|
application.Permissions.Add(OpenIddictConstants.Permissions.Endpoints.Token); |
||||
|
application.Permissions.Add(OpenIddictConstants.Permissions.Endpoints.Revocation); |
||||
|
application.Permissions.Add(OpenIddictConstants.Permissions.Endpoints.Introspection); |
||||
|
} |
||||
|
|
||||
|
if (grantType == OpenIddictConstants.GrantTypes.ClientCredentials) |
||||
|
{ |
||||
|
application.Permissions.Add(OpenIddictConstants.Permissions.GrantTypes.ClientCredentials); |
||||
|
} |
||||
|
|
||||
|
if (grantType == OpenIddictConstants.GrantTypes.Implicit) |
||||
|
{ |
||||
|
application.Permissions.Add(OpenIddictConstants.Permissions.GrantTypes.Implicit); |
||||
|
} |
||||
|
|
||||
|
if (grantType == OpenIddictConstants.GrantTypes.Password) |
||||
|
{ |
||||
|
application.Permissions.Add(OpenIddictConstants.Permissions.GrantTypes.Password); |
||||
|
} |
||||
|
|
||||
|
if (grantType == OpenIddictConstants.GrantTypes.RefreshToken) |
||||
|
{ |
||||
|
application.Permissions.Add(OpenIddictConstants.Permissions.GrantTypes.RefreshToken); |
||||
|
} |
||||
|
|
||||
|
if (grantType == OpenIddictConstants.GrantTypes.DeviceCode) |
||||
|
{ |
||||
|
application.Permissions.Add(OpenIddictConstants.Permissions.GrantTypes.DeviceCode); |
||||
|
application.Permissions.Add(OpenIddictConstants.Permissions.Endpoints.Device); |
||||
|
} |
||||
|
|
||||
|
if (grantType == OpenIddictConstants.GrantTypes.Implicit) |
||||
|
{ |
||||
|
application.Permissions.Add(OpenIddictConstants.Permissions.ResponseTypes.IdToken); |
||||
|
if (string.Equals(type, OpenIddictConstants.ClientTypes.Public, StringComparison.OrdinalIgnoreCase)) |
||||
|
{ |
||||
|
application.Permissions.Add(OpenIddictConstants.Permissions.ResponseTypes.IdTokenToken); |
||||
|
application.Permissions.Add(OpenIddictConstants.Permissions.ResponseTypes.Token); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
var buildInScopes = new [] |
||||
|
{ |
||||
|
OpenIddictConstants.Permissions.Scopes.Address, |
||||
|
OpenIddictConstants.Permissions.Scopes.Email, |
||||
|
OpenIddictConstants.Permissions.Scopes.Phone, |
||||
|
OpenIddictConstants.Permissions.Scopes.Profile, |
||||
|
OpenIddictConstants.Permissions.Scopes.Roles |
||||
|
}; |
||||
|
|
||||
|
foreach (var scope in scopes) |
||||
|
{ |
||||
|
if (buildInScopes.Contains(scope)) |
||||
|
{ |
||||
|
application.Permissions.Add(scope); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
application.Permissions.Add(OpenIddictConstants.Permissions.Prefixes.Scope + scope); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (redirectUri != null) |
||||
|
{ |
||||
|
if (!redirectUri.IsNullOrEmpty()) |
||||
|
{ |
||||
|
if (!Uri.TryCreate(redirectUri, UriKind.Absolute, out var uri) || !uri.IsWellFormedOriginalString()) |
||||
|
{ |
||||
|
throw new BusinessException(L["InvalidRedirectUri", redirectUri]); |
||||
|
} |
||||
|
|
||||
|
if (application.RedirectUris.All(x => x != uri)) |
||||
|
{ |
||||
|
application.RedirectUris.Add(uri); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (postLogoutRedirectUri != null) |
||||
|
{ |
||||
|
if (!postLogoutRedirectUri.IsNullOrEmpty()) |
||||
|
{ |
||||
|
if (!Uri.TryCreate(postLogoutRedirectUri, UriKind.Absolute, out var uri) || !uri.IsWellFormedOriginalString()) |
||||
|
{ |
||||
|
throw new BusinessException(L["InvalidPostLogoutRedirectUri", postLogoutRedirectUri]); |
||||
|
} |
||||
|
|
||||
|
if (application.PostLogoutRedirectUris.All(x => x != uri)) |
||||
|
{ |
||||
|
application.PostLogoutRedirectUris.Add(uri); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (permissions != null) |
||||
|
{ |
||||
|
await _permissionDataSeeder.SeedAsync( |
||||
|
ClientPermissionValueProvider.ProviderName, |
||||
|
name, |
||||
|
permissions, |
||||
|
null |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
await _applicationManager.CreateAsync(application); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,99 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk.Web"> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>net7.0</TargetFramework> |
||||
|
<Nullable>enable</Nullable> |
||||
|
<ImplicitUsings>enable</ImplicitUsings> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="7.0.0" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Serilog.AspNetCore" Version="5.0.0" /> |
||||
|
<PackageReference Include="Serilog.Sinks.Async" Version="1.5.0" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\..\..\..\..\framework\src\Volo.Abp.AspNetCore.Mvc\Volo.Abp.AspNetCore.Mvc.csproj" /> |
||||
|
<ProjectReference Include="..\..\..\..\..\framework\src\Volo.Abp.Autofac\Volo.Abp.Autofac.csproj" /> |
||||
|
<ProjectReference Include="..\..\..\..\..\framework\src\Volo.Abp.AutoMapper\Volo.Abp.AutoMapper.csproj" /> |
||||
|
<ProjectReference Include="..\..\..\..\..\framework\src\Volo.Abp.Swashbuckle\Volo.Abp.Swashbuckle.csproj" /> |
||||
|
<ProjectReference Include="..\..\..\..\..\framework\src\Volo.Abp.AspNetCore.Authentication.JwtBearer\Volo.Abp.AspNetCore.Authentication.JwtBearer.csproj" /> |
||||
|
<ProjectReference Include="..\..\..\..\..\framework\src\Volo.Abp.AspNetCore.Serilog\Volo.Abp.AspNetCore.Serilog.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\..\..\..\..\modules\account\src\Volo.Abp.Account.Application\Volo.Abp.Account.Application.csproj" /> |
||||
|
<ProjectReference Include="..\..\..\..\..\modules\account\src\Volo.Abp.Account.HttpApi\Volo.Abp.Account.HttpApi.csproj" /> |
||||
|
<ProjectReference Include="..\..\..\..\..\modules\account\src\Volo.Abp.Account.Web.OpenIddict\Volo.Abp.Account.Web.OpenIddict.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\..\..\..\..\modules\identity\src\Volo.Abp.PermissionManagement.Domain.Identity\Volo.Abp.PermissionManagement.Domain.Identity.csproj" /> |
||||
|
<ProjectReference Include="..\..\..\..\..\modules\identity\src\Volo.Abp.Identity.Application\Volo.Abp.Identity.Application.csproj" /> |
||||
|
<ProjectReference Include="..\..\..\..\..\modules\identity\src\Volo.Abp.Identity.HttpApi\Volo.Abp.Identity.HttpApi.csproj" /> |
||||
|
<ProjectReference Include="..\..\..\..\..\modules\identity\src\Volo.Abp.Identity.MongoDB\Volo.Abp.Identity.MongoDB.csproj" /> |
||||
|
<ProjectReference Include="..\..\..\..\..\modules\openiddict\src\Volo.Abp.OpenIddict.MongoDB\Volo.Abp.OpenIddict.MongoDB.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\..\..\..\..\modules\openiddict\src\Volo.Abp.PermissionManagement.Domain.OpenIddict\Volo.Abp.PermissionManagement.Domain.OpenIddict.csproj" /> |
||||
|
<ProjectReference Include="..\..\..\..\..\modules\permission-management\src\Volo.Abp.PermissionManagement.Application\Volo.Abp.PermissionManagement.Application.csproj" /> |
||||
|
<ProjectReference Include="..\..\..\..\..\modules\permission-management\src\Volo.Abp.PermissionManagement.MongoDB\Volo.Abp.PermissionManagement.MongoDB.csproj" /> |
||||
|
<ProjectReference Include="..\..\..\..\..\modules\permission-management\src\Volo.Abp.PermissionManagement.HttpApi\Volo.Abp.PermissionManagement.HttpApi.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\..\..\..\..\modules\tenant-management\src\Volo.Abp.TenantManagement.Application\Volo.Abp.TenantManagement.Application.csproj" /> |
||||
|
<ProjectReference Include="..\..\..\..\..\modules\tenant-management\src\Volo.Abp.TenantManagement.MongoDB\Volo.Abp.TenantManagement.MongoDB.csproj" /> |
||||
|
<ProjectReference Include="..\..\..\..\..\modules\tenant-management\src\Volo.Abp.TenantManagement.HttpApi\Volo.Abp.TenantManagement.HttpApi.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\..\..\..\..\modules\feature-management\src\Volo.Abp.FeatureManagement.Application\Volo.Abp.FeatureManagement.Application.csproj" /> |
||||
|
<ProjectReference Include="..\..\..\..\..\modules\feature-management\src\Volo.Abp.FeatureManagement.MongoDB\Volo.Abp.FeatureManagement.MongoDB.csproj" /> |
||||
|
<ProjectReference Include="..\..\..\..\..\modules\feature-management\src\Volo.Abp.FeatureManagement.HttpApi\Volo.Abp.FeatureManagement.HttpApi.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\..\..\..\..\modules\setting-management\src\Volo.Abp.SettingManagement.Application\Volo.Abp.SettingManagement.Application.csproj" /> |
||||
|
<ProjectReference Include="..\..\..\..\..\modules\setting-management\src\Volo.Abp.SettingManagement.MongoDB\Volo.Abp.SettingManagement.MongoDB.csproj" /> |
||||
|
<ProjectReference Include="..\..\..\..\..\modules\setting-management\src\Volo.Abp.SettingManagement.HttpApi\Volo.Abp.SettingManagement.HttpApi.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\..\..\..\..\modules\audit-logging\src\Volo.Abp.AuditLogging.MongoDB\Volo.Abp.AuditLogging.MongoDB.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<!-- <TEMPLATE-REMOVE> --> |
||||
|
<ProjectReference Include="..\..\..\..\..\framework\src\Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy\Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy.csproj" /> |
||||
|
<ProjectReference Include="..\..\..\..\..\framework\src\Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared\Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.csproj" /> |
||||
|
<ProjectReference Include="..\..\..\..\..\framework\src\Volo.Abp.AutoMapper\Volo.Abp.AutoMapper.csproj" /> |
||||
|
<!-- </TEMPLATE-REMOVE> --> |
||||
|
<PackageReference Include="Volo.Abp.AspNetCore.Mvc.UI.Theme.LeptonXLite" Version="2.0.0-*" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" Version="7.0.0" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\Client\MyCompanyName.MyProjectName.Blazor.WebAssembly.Client.csproj" /> |
||||
|
<ProjectReference Include="..\Shared\MyCompanyName.MyProjectName.Blazor.WebAssembly.Shared.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<Compile Remove="Logs\**" /> |
||||
|
<Content Remove="Logs\**" /> |
||||
|
<EmbeddedResource Remove="Logs\**" /> |
||||
|
<None Remove="Logs\**" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<Content Remove="$(UserProfile)\.nuget\packages\*\*\contentFiles\any\*\*.abppkg*.json" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,318 @@ |
|||||
|
using Microsoft.AspNetCore.Cors; |
||||
|
using Microsoft.AspNetCore.DataProtection; |
||||
|
using Microsoft.AspNetCore.Extensions.DependencyInjection; |
||||
|
using Microsoft.Extensions.DependencyInjection.Extensions; |
||||
|
using Microsoft.OpenApi.Models; |
||||
|
using MyCompanyName.MyProjectName.Server.Data; |
||||
|
using MyCompanyName.MyProjectName.Shared.Localization; |
||||
|
using MyCompanyName.MyProjectName.Shared; |
||||
|
using MyCompanyName.MyProjectName.Shared.MultiTenancy; |
||||
|
using OpenIddict.Validation.AspNetCore; |
||||
|
using Volo.Abp; |
||||
|
using Volo.Abp.Account; |
||||
|
using Volo.Abp.Account.Web; |
||||
|
using Volo.Abp.AspNetCore.MultiTenancy; |
||||
|
using Volo.Abp.AspNetCore.Mvc; |
||||
|
using Volo.Abp.AspNetCore.Mvc.Localization; |
||||
|
using Volo.Abp.AspNetCore.Mvc.UI.Bundling; |
||||
|
using Volo.Abp.AspNetCore.Mvc.UI.Theme.LeptonXLite; |
||||
|
using Volo.Abp.AspNetCore.Mvc.UI.Theme.LeptonXLite.Bundling; |
||||
|
using Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared; |
||||
|
using Volo.Abp.AspNetCore.Serilog; |
||||
|
using Volo.Abp.AuditLogging.MongoDB; |
||||
|
using Volo.Abp.Autofac; |
||||
|
using Volo.Abp.AutoMapper; |
||||
|
using Volo.Abp.Emailing; |
||||
|
using Volo.Abp.FeatureManagement; |
||||
|
using Volo.Abp.FeatureManagement.MongoDB; |
||||
|
using Volo.Abp.Identity; |
||||
|
using Volo.Abp.Identity.MongoDB; |
||||
|
using Volo.Abp.Modularity; |
||||
|
using Volo.Abp.MultiTenancy; |
||||
|
using Volo.Abp.OpenIddict.MongoDB; |
||||
|
using Volo.Abp.PermissionManagement; |
||||
|
using Volo.Abp.PermissionManagement.MongoDB; |
||||
|
using Volo.Abp.PermissionManagement.HttpApi; |
||||
|
using Volo.Abp.PermissionManagement.Identity; |
||||
|
using Volo.Abp.PermissionManagement.OpenIddict; |
||||
|
using Volo.Abp.SettingManagement; |
||||
|
using Volo.Abp.SettingManagement.MongoDB; |
||||
|
using Volo.Abp.Swashbuckle; |
||||
|
using Volo.Abp.TenantManagement; |
||||
|
using Volo.Abp.TenantManagement.MongoDB; |
||||
|
using Volo.Abp.UI.Navigation.Urls; |
||||
|
using Volo.Abp.Uow; |
||||
|
using Volo.Abp.VirtualFileSystem; |
||||
|
|
||||
|
namespace MyCompanyName.MyProjectName.Server; |
||||
|
|
||||
|
[DependsOn( |
||||
|
|
||||
|
typeof(MyProjectNameSharedModule), |
||||
|
|
||||
|
// ABP Framework packages
|
||||
|
typeof(AbpAspNetCoreMvcModule), |
||||
|
typeof(AbpAspNetCoreMultiTenancyModule), |
||||
|
typeof(AbpAutofacModule), |
||||
|
typeof(AbpAutoMapperModule), |
||||
|
typeof(AbpAspNetCoreMvcUiLeptonXLiteThemeModule), |
||||
|
typeof(AbpSwashbuckleModule), |
||||
|
typeof(AbpAspNetCoreSerilogModule), |
||||
|
|
||||
|
// Account module packages
|
||||
|
typeof(AbpAccountApplicationModule), |
||||
|
typeof(AbpAccountHttpApiModule), |
||||
|
typeof(AbpAccountWebOpenIddictModule), |
||||
|
|
||||
|
// Identity module packages
|
||||
|
typeof(AbpPermissionManagementDomainIdentityModule), |
||||
|
typeof(AbpPermissionManagementDomainOpenIddictModule), |
||||
|
typeof(AbpIdentityApplicationModule), |
||||
|
typeof(AbpIdentityHttpApiModule), |
||||
|
typeof(AbpIdentityMongoDbModule), |
||||
|
typeof(AbpOpenIddictMongoDbModule), |
||||
|
|
||||
|
// Audit logging module packages
|
||||
|
typeof(AbpAuditLoggingMongoDbModule), |
||||
|
|
||||
|
// Permission Management module packages
|
||||
|
typeof(AbpPermissionManagementApplicationModule), |
||||
|
typeof(AbpPermissionManagementHttpApiModule), |
||||
|
typeof(AbpPermissionManagementMongoDbModule), |
||||
|
|
||||
|
// Tenant Management module packages
|
||||
|
typeof(AbpTenantManagementApplicationModule), |
||||
|
typeof(AbpTenantManagementHttpApiModule), |
||||
|
typeof(AbpTenantManagementMongoDbModule), |
||||
|
|
||||
|
// Feature Management module packages
|
||||
|
typeof(AbpFeatureManagementApplicationModule), |
||||
|
typeof(AbpFeatureManagementMongoDbModule), |
||||
|
typeof(AbpFeatureManagementHttpApiModule), |
||||
|
|
||||
|
// Setting Management module packages
|
||||
|
typeof(AbpSettingManagementApplicationModule), |
||||
|
typeof(AbpSettingManagementMongoDbModule), |
||||
|
typeof(AbpSettingManagementHttpApiModule) |
||||
|
)] |
||||
|
public class MyProjectNameServerModule : AbpModule |
||||
|
{ |
||||
|
public override void PreConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
context.Services.PreConfigure<AbpMvcDataAnnotationsLocalizationOptions>(options => |
||||
|
{ |
||||
|
options.AddAssemblyResource( |
||||
|
typeof(MyProjectNameResource) |
||||
|
); |
||||
|
}); |
||||
|
|
||||
|
PreConfigure<OpenIddictBuilder>(builder => |
||||
|
{ |
||||
|
builder.AddValidation(options => |
||||
|
{ |
||||
|
options.AddAudiences("MyProjectName"); |
||||
|
options.UseLocalServer(); |
||||
|
options.UseAspNetCore(); |
||||
|
}); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public override void ConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
var hostingEnvironment = context.Services.GetHostingEnvironment(); |
||||
|
var configuration = context.Services.GetConfiguration(); |
||||
|
|
||||
|
if (hostingEnvironment.IsDevelopment()) |
||||
|
{ |
||||
|
context.Services.Replace(ServiceDescriptor.Singleton<IEmailSender, NullEmailSender>()); |
||||
|
} |
||||
|
|
||||
|
ConfigureAuthentication(context); |
||||
|
ConfigureBundles(); |
||||
|
ConfigureMultiTenancy(); |
||||
|
ConfigureUrls(configuration); |
||||
|
ConfigureAutoMapper(context); |
||||
|
ConfigureSwagger(context.Services, configuration); |
||||
|
ConfigureAutoApiControllers(); |
||||
|
ConfigureVirtualFiles(hostingEnvironment); |
||||
|
ConfigureCors(context, configuration); |
||||
|
ConfigureDataProtection(context); |
||||
|
ConfigureMongoDB(context); |
||||
|
} |
||||
|
|
||||
|
private void ConfigureAuthentication(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
context.Services.ForwardIdentityAuthenticationForBearer(OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme); |
||||
|
} |
||||
|
|
||||
|
private void ConfigureBundles() |
||||
|
{ |
||||
|
Configure<AbpBundlingOptions>(options => |
||||
|
{ |
||||
|
options.StyleBundles.Configure( |
||||
|
LeptonXLiteThemeBundles.Styles.Global, |
||||
|
bundle => { bundle.AddFiles("/global-styles.css"); } |
||||
|
); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
private void ConfigureMultiTenancy() |
||||
|
{ |
||||
|
Configure<AbpMultiTenancyOptions>(options => |
||||
|
{ |
||||
|
options.IsEnabled = MultiTenancyConsts.IsEnabled; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
private void ConfigureUrls(IConfiguration configuration) |
||||
|
{ |
||||
|
Configure<AppUrlOptions>(options => |
||||
|
{ |
||||
|
options.Applications["MVC"].RootUrl = configuration["App:SelfUrl"]; |
||||
|
options.RedirectAllowedUrls.AddRange(configuration["App:RedirectAllowedUrls"]?.Split(',') ?? Array.Empty<string>()); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
private void ConfigureVirtualFiles(IWebHostEnvironment hostingEnvironment) |
||||
|
{ |
||||
|
Configure<AbpVirtualFileSystemOptions>(options => |
||||
|
{ |
||||
|
options.FileSets.AddEmbedded<MyProjectNameServerModule>(); |
||||
|
if (hostingEnvironment.IsDevelopment()) |
||||
|
{ |
||||
|
/* Using physical files in development, so we don't need to recompile on changes */ |
||||
|
options.FileSets.ReplaceEmbeddedByPhysical<MyProjectNameServerModule>(hostingEnvironment.ContentRootPath); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
private void ConfigureAutoApiControllers() |
||||
|
{ |
||||
|
Configure<AbpAspNetCoreMvcOptions>(options => |
||||
|
{ |
||||
|
options.ConventionalControllers.Create(typeof(MyProjectNameServerModule).Assembly); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
private void ConfigureSwagger(IServiceCollection services, IConfiguration configuration) |
||||
|
{ |
||||
|
services.AddAbpSwaggerGenWithOAuth( |
||||
|
configuration["AuthServer:Authority"], |
||||
|
new Dictionary<string, string> |
||||
|
{ |
||||
|
{"MyProjectName", "MyProjectName API"} |
||||
|
}, |
||||
|
options => |
||||
|
{ |
||||
|
options.SwaggerDoc("v1", new OpenApiInfo { Title = "MyProjectName API", Version = "v1" }); |
||||
|
options.DocInclusionPredicate((docName, description) => true); |
||||
|
options.CustomSchemaIds(type => type.FullName); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
private void ConfigureAutoMapper(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
context.Services.AddAutoMapperObjectMapper<MyProjectNameServerModule>(); |
||||
|
Configure<AbpAutoMapperOptions>(options => |
||||
|
{ |
||||
|
/* Uncomment `validate: true` if you want to enable the Configuration Validation feature. |
||||
|
* See AutoMapper's documentation to learn what it is: |
||||
|
* https://docs.automapper.org/en/stable/Configuration-validation.html
|
||||
|
*/ |
||||
|
options.AddMaps<MyProjectNameServerModule>(/* validate: true */); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
private void ConfigureCors(ServiceConfigurationContext context, IConfiguration configuration) |
||||
|
{ |
||||
|
context.Services.AddCors(options => |
||||
|
{ |
||||
|
options.AddDefaultPolicy(builder => |
||||
|
{ |
||||
|
builder |
||||
|
.WithOrigins( |
||||
|
configuration["App:CorsOrigins"]? |
||||
|
.Split(",", StringSplitOptions.RemoveEmptyEntries) |
||||
|
.Select(o => o.RemovePostFix("/")) |
||||
|
.ToArray() ?? Array.Empty<string>() |
||||
|
) |
||||
|
.WithAbpExposedHeaders() |
||||
|
.SetIsOriginAllowedToAllowWildcardSubdomains() |
||||
|
.AllowAnyHeader() |
||||
|
.AllowAnyMethod() |
||||
|
.AllowCredentials(); |
||||
|
}); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
private void ConfigureDataProtection(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
context.Services.AddDataProtection().SetApplicationName("MyProjectName"); |
||||
|
} |
||||
|
|
||||
|
private void ConfigureMongoDB(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
context.Services.AddMongoDbContext<MyProjectNameDbContext>(options => |
||||
|
{ |
||||
|
options.AddDefaultRepositories(); |
||||
|
}); |
||||
|
|
||||
|
Configure<AbpUnitOfWorkDefaultOptions>(options => |
||||
|
{ |
||||
|
options.TransactionBehavior = UnitOfWorkTransactionBehavior.Disabled; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public override void OnApplicationInitialization(ApplicationInitializationContext context) |
||||
|
{ |
||||
|
var app = context.GetApplicationBuilder(); |
||||
|
var env = context.GetEnvironment(); |
||||
|
|
||||
|
if (env.IsDevelopment()) |
||||
|
{ |
||||
|
app.UseDeveloperExceptionPage(); |
||||
|
} |
||||
|
|
||||
|
app.UseAbpRequestLocalization(); |
||||
|
|
||||
|
if (!env.IsDevelopment()) |
||||
|
{ |
||||
|
app.UseErrorPage(); |
||||
|
} |
||||
|
|
||||
|
app.UseCorrelationId(); |
||||
|
app.UseBlazorFrameworkFiles(); |
||||
|
app.UseStaticFiles(); |
||||
|
app.UseRouting(); |
||||
|
app.UseCors(); |
||||
|
app.UseAuthentication(); |
||||
|
app.UseAbpOpenIddictValidation(); |
||||
|
|
||||
|
if (MultiTenancyConsts.IsEnabled) |
||||
|
{ |
||||
|
app.UseMultiTenancy(); |
||||
|
} |
||||
|
|
||||
|
app.UseUnitOfWork(); |
||||
|
app.UseAuthorization(); |
||||
|
|
||||
|
app.UseSwagger(); |
||||
|
app.UseAbpSwaggerUI(options => |
||||
|
{ |
||||
|
options.SwaggerEndpoint("/swagger/v1/swagger.json", "MyProjectName API"); |
||||
|
|
||||
|
var configuration = context.GetConfiguration(); |
||||
|
options.OAuthClientId(configuration["AuthServer:SwaggerClientId"]); |
||||
|
options.OAuthScopes("MyProjectName"); |
||||
|
}); |
||||
|
|
||||
|
app.UseAuditing(); |
||||
|
app.UseAbpSerilogEnrichers(); |
||||
|
app.UseConfiguredEndpoints(); |
||||
|
|
||||
|
if (app is WebApplication webApp) |
||||
|
{ |
||||
|
webApp.MapFallbackToFile("index.html"); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,11 @@ |
|||||
|
using AutoMapper; |
||||
|
|
||||
|
namespace MyCompanyName.MyProjectName.Server.ObjectMapping; |
||||
|
|
||||
|
public class MyProjectNameAutoMapperProfile : Profile |
||||
|
{ |
||||
|
public MyProjectNameAutoMapperProfile() |
||||
|
{ |
||||
|
/* Create your AutoMapper object mappings here */ |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,81 @@ |
|||||
|
using MyCompanyName.MyProjectName.Server; |
||||
|
using MyCompanyName.MyProjectName.Server.Data; |
||||
|
using Serilog; |
||||
|
using Serilog.Events; |
||||
|
using Volo.Abp.Data; |
||||
|
|
||||
|
public class Program |
||||
|
{ |
||||
|
public async static Task<int> Main(string[] args) |
||||
|
{ |
||||
|
//<TEMPLATE-REMOVE IF-NOT='dbms:PostgreSQL'>
|
||||
|
// https://www.npgsql.org/efcore/release-notes/6.0.html#opting-out-of-the-new-timestamp-mapping-logic
|
||||
|
AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true); |
||||
|
|
||||
|
//</TEMPLATE-REMOVE>
|
||||
|
var loggerConfiguration = new LoggerConfiguration() |
||||
|
#if DEBUG
|
||||
|
.MinimumLevel.Debug() |
||||
|
#else
|
||||
|
.MinimumLevel.Information() |
||||
|
#endif
|
||||
|
.MinimumLevel.Override("Microsoft", LogEventLevel.Information) |
||||
|
.MinimumLevel.Override("Microsoft.EntityFrameworkCore", LogEventLevel.Warning) |
||||
|
.Enrich.FromLogContext() |
||||
|
.WriteTo.Async(c => c.File("Logs/logs.txt")) |
||||
|
.WriteTo.Async(c => c.Console()); |
||||
|
|
||||
|
if (IsMigrateDatabase(args)) |
||||
|
{ |
||||
|
loggerConfiguration.MinimumLevel.Override("Volo.Abp", LogEventLevel.Warning); |
||||
|
loggerConfiguration.MinimumLevel.Override("Microsoft", LogEventLevel.Warning); |
||||
|
} |
||||
|
|
||||
|
Log.Logger = loggerConfiguration.CreateLogger(); |
||||
|
|
||||
|
try |
||||
|
{ |
||||
|
|
||||
|
var builder = WebApplication.CreateBuilder(args); |
||||
|
builder.Host.AddAppSettingsSecretsJson() |
||||
|
.UseAutofac() |
||||
|
.UseSerilog(); |
||||
|
await builder.AddApplicationAsync<MyProjectNameServerModule>(); |
||||
|
if (IsMigrateDatabase(args)) |
||||
|
{ |
||||
|
builder.Services.AddDataMigrationEnvironment(); |
||||
|
} |
||||
|
var app = builder.Build(); |
||||
|
await app.InitializeApplicationAsync(); |
||||
|
|
||||
|
if (IsMigrateDatabase(args)) |
||||
|
{ |
||||
|
await app.Services.GetRequiredService<MyProjectNameDbMigrationService>().MigrateAsync(); |
||||
|
return 0; |
||||
|
} |
||||
|
|
||||
|
Log.Information("Starting MyCompanyName.MyProjectName."); |
||||
|
await app.RunAsync(); |
||||
|
return 0; |
||||
|
} |
||||
|
catch (Exception ex) |
||||
|
{ |
||||
|
if (ex is HostAbortedException) |
||||
|
{ |
||||
|
throw; |
||||
|
} |
||||
|
|
||||
|
Log.Fatal(ex, "MyCompanyName.MyProjectName terminated unexpectedly!"); |
||||
|
return 1; |
||||
|
} |
||||
|
finally |
||||
|
{ |
||||
|
Log.CloseAndFlush(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static bool IsMigrateDatabase(string[] args) |
||||
|
{ |
||||
|
return args.Any(x => x.Contains("--migrate-database", StringComparison.OrdinalIgnoreCase)); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,30 @@ |
|||||
|
{ |
||||
|
"iisSettings": { |
||||
|
"windowsAuthentication": false, |
||||
|
"anonymousAuthentication": true, |
||||
|
"iisExpress": { |
||||
|
"applicationUrl": "https://localhost:44300", |
||||
|
"sslPort": 44300 |
||||
|
} |
||||
|
}, |
||||
|
"profiles": { |
||||
|
"https": { |
||||
|
"commandName": "Project", |
||||
|
"dotnetRunMessages": true, |
||||
|
"launchBrowser": true, |
||||
|
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", |
||||
|
"applicationUrl": "https://localhost:44300", |
||||
|
"environmentVariables": { |
||||
|
"ASPNETCORE_ENVIRONMENT": "Development" |
||||
|
} |
||||
|
}, |
||||
|
"IIS Express": { |
||||
|
"commandName": "IISExpress", |
||||
|
"launchBrowser": true, |
||||
|
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", |
||||
|
"environmentVariables": { |
||||
|
"ASPNETCORE_ENVIRONMENT": "Development" |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||