mirror of https://github.com/abpframework/abp
commit
836f687cc2
@ -0,0 +1,33 @@
|
||||
# Application Localization Endpoint
|
||||
|
||||
ABP Framework provides a pre-built and standard endpoint that returns all the [localization](../Localization.md) resources and texts defined in the server.
|
||||
|
||||
> If you have started with ABP's startup solution templates and using one of the official UI options, then all these are set up for you and you don't need to know these details. However, if you are building a UI application from scratch, you may want to know this endpoint.
|
||||
|
||||
## HTTP API
|
||||
|
||||
`/api/abp/application-localization` is the main URL of the HTTP API that returns the localization data as a JSON string. I accepts the following query string parameters:
|
||||
|
||||
* `cultureName` (required): A culture code to get the localization data, like `en` or `en-US`.
|
||||
* `onlyDynamics` (optional, default: `false`): Can be set to `true` to only get the dynamically defined localization resources and texts. If your client-side application shares the same localization resources with the server (like ABP's Blazor and MVC UIs), you can set `onlyDynamics` to `true`.
|
||||
|
||||
**Example request:**
|
||||
|
||||
````
|
||||
/api/abp/application-localization?cultureName=en
|
||||
````
|
||||
|
||||
## Script
|
||||
|
||||
For [ASP.NET Core MVC (Razor Pages)](../UI/AspNetCore/Overall.md) applications, the same localization data is also available on the JavaScript side. `/Abp/ApplicationLocalizationScript` is the URL of the script that is auto-generated based on the HTTP API above.
|
||||
|
||||
**Example request:**
|
||||
|
||||
````
|
||||
/Abp/ApplicationLocalizationScript?cultureName=en
|
||||
````
|
||||
|
||||
See the [JavaScript API document](../UI/AspNetCore/JavaScript-API/Index.md) for the ASP.NET Core UI.
|
||||
|
||||
Other UI types provide services native to the related platform. For example, see the [Angular UI localization documentation](../UI/Angular/Localization.md) to learn how to use the localization values exposes by this endpoint.
|
||||
|
||||
|
After Width: | Height: | Size: 9.9 KiB |
@ -0,0 +1,134 @@
|
||||
# JSON Columns in Entity Framework Core 7
|
||||
|
||||
In this article, we will see how to use the new **JSON Columns** features that came with EF Core 7 in an ABP based application (with examples).
|
||||
|
||||
## JSON Columns
|
||||
|
||||
Most relational databases support columns that contain JSON documents. The JSON in these columns can be drilled into with queries. This allows, for example, filtering and sorting by the elements of the documents, as well as projection of elements out of the documents into results. JSON columns allow relational databases to take on some of the characteristics of document databases, creating a useful hybrid between these two database management approaches.
|
||||
|
||||
EF7 contains provider-agnostic support for JSON columns, with an implementation for SQL Server. This support allows the mapping of aggregates built from .NET types to JSON documents. Normal LINQ queries can be used on the aggregates, and these will be translated to the appropriate query constructs needed to drill into the JSON. EF7 also supports updating and saving changes to JSON documents.
|
||||
|
||||
> You can find more information about JSON columns in EF Core's [documentation](https://docs.microsoft.com/en-us/ef/core/what-is-new/ef-core-7.0/whatsnew#json-columns).
|
||||
|
||||
### Mapping JSON Columns
|
||||
|
||||
In EF Core, aggregate types can be defined using `OwnsOne` and `OwnsMany` methods. `OwnsOne` can be used to map a single aggregate and the `OwnsMany` method can be used to map a collection of aggregates.
|
||||
|
||||
With EF 7, we have a new extension method for mapping property to a JSON Column: `ToJson`. We can use this method to mark a property as a JSON Column. The property can be of any type that can be serialized to JSON.
|
||||
|
||||
The following example shows how to map a JSON column to an aggregate type:
|
||||
|
||||
```csharp
|
||||
public class ContactDetails
|
||||
{
|
||||
public Address Address { get; set; }
|
||||
public string? Phone { get; set; }
|
||||
}
|
||||
|
||||
public class Address
|
||||
{
|
||||
public Address(string street, string city, string postcode, string country)
|
||||
{
|
||||
Street = street;
|
||||
City = city;
|
||||
Postcode = postcode;
|
||||
Country = country;
|
||||
}
|
||||
|
||||
public string Street { get; set; }
|
||||
public string City { get; set; }
|
||||
public string Postcode { get; set; }
|
||||
public string Country { get; set; }
|
||||
}
|
||||
|
||||
public class Person : AggregateRoot<int>
|
||||
{
|
||||
public string Name { get; set; } = null!;
|
||||
public ContactDetails ContactDetails { get; set; } = null!;
|
||||
}
|
||||
```
|
||||
|
||||
* Above, we have defined an aggregate type `ContactDetails` that contains an `Address` and a `Phone` number. The aggregate type is configured in `OnModelCreating` using `OwnsOne` and `ToJson` methods below.
|
||||
* The `Address` property is mapped to a JSON column using `ToJson`, and the `Phone` property is mapped to a regular column. This requires just one call to **ToJson()** when configuring the aggregate type:
|
||||
|
||||
```csharp
|
||||
|
||||
public class MyDbContext : AbpDbContext<MyDbContext>
|
||||
{
|
||||
public DbSet<Person> Persons { get; set; }
|
||||
|
||||
public MyDbContext(DbContextOptions<MyDbContext> options)
|
||||
: base(options)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder builder)
|
||||
{
|
||||
base.OnModelCreating(builder);
|
||||
|
||||
builder.Entity<Person>(b =>
|
||||
{
|
||||
b.ToTable(MyProjectConsts.DbTablePrefix + "Persons", MyProjectConsts.DbSchema);
|
||||
b.ConfigureByConvention();
|
||||
b.OwnsOne(x=>x.ContactDetails, c =>
|
||||
{
|
||||
c.ToJson(); //mark as JSON Column
|
||||
c.OwnsOne(cd => cd.Address);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Querying JSON Columns
|
||||
|
||||
Queries into JSON columns work just the same as querying into any other aggregate type in EF Core. That's it, just use the LINQ! Here are some examples:
|
||||
|
||||
```csharp
|
||||
var persons = await (await GetDbSetAsync()).ToListAsync();
|
||||
|
||||
var contacts = await (await GetDbSetAsync()).Select(person => new
|
||||
{
|
||||
person,
|
||||
person.ContactDetails.Phone, //query over JSON column
|
||||
Addresses = person.ContactDetails.Address //query over JSON column
|
||||
}).ToListAsync();
|
||||
|
||||
var addresses = await (await GetDbSetAsync()).Select(person => new
|
||||
{
|
||||
person,
|
||||
Addresses = person.ContactDetails.Address //query over JSON column
|
||||
}).ToListAsync();
|
||||
```
|
||||
|
||||
### Updating JSON Columns
|
||||
|
||||
You can update JSON columns the same as updating any record by using the `UpdateAsync` method. The following example shows how to update a JSON column:
|
||||
|
||||
```csharp
|
||||
var person = await (await GetDbSetAsync()).FirstAsync();
|
||||
|
||||
person.ContactDetails.Phone = "123456789";
|
||||
person.ContactDetails.Address = new Address("Street", "City", "Postcode", "Country");
|
||||
await UpdateAsync(person, true);
|
||||
```
|
||||
|
||||
### JSON Column in a Database
|
||||
|
||||
After you've configured the database relations, created a new migration and applied it to database you will have a database table like below:
|
||||
|
||||

|
||||
|
||||
As you can see, thanks to JSON Columns feature the **ContactDetails** row has JSON content and we can use it in a query or update it from our application with the LINQ JSON query support that mentioned above.
|
||||
|
||||
### Conclusion
|
||||
|
||||
In this article, I've briefly introduced the JSON Columns feature that was shipped with EF Core 7. It's pretty straightforward to use JSON Columns in an ABP based application. You can see the examples above and give it a try!
|
||||
|
||||
### The Source Code
|
||||
* You can find the full source code of the example application [here](https://github.com/abpframework/abp-samples/tree/master/EfCoreJSONColumnDemo).
|
||||
|
||||
### References
|
||||
|
||||
* [https://docs.microsoft.com/en-us/ef/core/what-is-new/ef-core-7.0/whatsnew#json-columns](https://docs.microsoft.com/en-us/ef/core/what-is-new/ef-core-7.0/whatsnew#json-columns)
|
||||
* [https://docs.microsoft.com/en-us/ef/core/modeling/owned-entities](https://docs.microsoft.com/en-us/ef/core/modeling/owned-entities)
|
||||
@ -0,0 +1,73 @@
|
||||
# gRPC - Health Checks
|
||||
|
||||
In this article we will show how to use gRPC health checks with the ABP Framework.
|
||||
|
||||
## Health Checks
|
||||
|
||||
ASP.NET Core 7 supports gRPC health checks. Health Checks allow us to determine the overall health and availability of our application infrastructure. They are exposed as HTTP endpoints and can be configured to provide information for various monitoring scenarios, such as the response time and memory usage of our application, or whether our application can communicate with our database provider.
|
||||
|
||||
### gRPC Health Checks
|
||||
|
||||
The [gRPC health checking protocol](https://github.com/grpc/grpc/blob/master/doc/health-checking.md) is a standard for reporting the health of gRPC server apps. An app exposes health checks as a gRPC service. They are typically used with an external monitoring service to check the status of an app.
|
||||
|
||||
### Grpc.AspNetCore.HealthChecks
|
||||
|
||||
ASP.NET Core supports the gRPC health checking protocol with the [Grpc.AspNetCore.HealthChecks](https://www.nuget.org/packages/Grpc.AspNetCore.HealthChecks) package. Results from .NET health checks are reported to callers.
|
||||
|
||||
## Using gRPC Health Checks with the ABP Framework
|
||||
|
||||
In this article, I'm assuming you've used gRPC with ABP before. If you are still having problems with this, it may be good for you to review this article.
|
||||
https://community.abp.io/posts/using-grpc-with-the-abp-framework-2dgaxzw3
|
||||
|
||||
### Set up gRPC Health Checks
|
||||
|
||||
In this solution, `*.HttpApi.Host` is the project that configures and runs the server-side application. So, we will make changes in that project.
|
||||
|
||||
* Add the `Grpc.AspNetCore.HealthChecks` package to your project.
|
||||
|
||||
```bash
|
||||
dotnet add package Grpc.AspNetCore.HealthChecks
|
||||
```
|
||||
|
||||
* `AddGrpcHealthChecks` to register services that enable health checks.
|
||||
|
||||
```csharp
|
||||
public override void ConfigureServices(ServiceConfigurationContext context)
|
||||
{
|
||||
// Other configurations...
|
||||
|
||||
context.Services.AddGrpcHealthChecks()
|
||||
.AddCheck("SampleHealthCheck", () => HealthCheckResult.Healthy());
|
||||
}
|
||||
```
|
||||
* `MapGrpcHealthChecksService` to add a health check service endpoint.
|
||||
|
||||
```csharp
|
||||
public override void OnApplicationInitialization(ApplicationInitializationContext context)
|
||||
{
|
||||
// Other middlewares...
|
||||
|
||||
app.UseConfiguredEndpoints(builder =>
|
||||
{
|
||||
builder.MapGrpcHealthChecksService();
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### Calling Health Checks From a Client
|
||||
|
||||
Now that our server is configured for gRPC health checks, we can test it by creating a basic console client.
|
||||
|
||||
```csharp
|
||||
var channel = GrpcChannel.ForAddress("https://localhost:44357");
|
||||
var client = new Health.HealthClient(channel);
|
||||
|
||||
var response = await client.CheckAsync(new HealthCheckRequest());
|
||||
var status = response.Status;
|
||||
|
||||
Console.WriteLine($"Health Status: {status}");
|
||||
```
|
||||
|
||||
## References
|
||||
|
||||
- https://learn.microsoft.com/en-us/aspnet/core/release-notes/aspnetcore-7.0?view=aspnetcore-7.0#grpc-health-checks-in-aspnet-core
|
||||
@ -0,0 +1,381 @@
|
||||
# Injecting Service Dependencies to Entities with Entity Framework Core 7.0
|
||||
|
||||
[Dependency injection](https://docs.abp.io/en/abp/latest/Dependency-Injection) is a widely-used pattern of obtaining references to other services from our classes. It is a built-in feature when you develop ASP.NET Core applications. In this article, I will explain why we may need to have references to other services in an entity class and how we can implement Entity Framework Core's new `IMaterializationInterceptor` interface to provide these services to the entities using the standard dependency injection system.
|
||||
|
||||
> You can find the source code of the example application [here](https://github.com/abpframework/abp-samples/tree/master/EfCoreEntityDependencyInjectionDemo).
|
||||
|
||||
## The Problem
|
||||
|
||||
While developing applications based on [Domain-Driven Design](https://docs.abp.io/en/abp/latest/Domain-Driven-Design) (DDD) patterns, we typically write our business code inside [application services](https://docs.abp.io/en/abp/latest/Application-Services), [domain services](https://docs.abp.io/en/abp/latest/Domain-Services) and [entities](https://docs.abp.io/en/abp/latest/Entities). Since the application and domain service instances are created by the dependency injection system, they can inject services into their constructors.
|
||||
|
||||
Here, an example domain service that injects a repository into its constructor:
|
||||
|
||||
````csharp
|
||||
public class ProductManager : DomainService
|
||||
{
|
||||
private readonly IRepository<Product, Guid> _productRepository;
|
||||
|
||||
public ProductManager(IRepository<Product, Guid> productRepository)
|
||||
{
|
||||
_productRepository = productRepository;
|
||||
}
|
||||
|
||||
//...
|
||||
}
|
||||
````
|
||||
|
||||
`ProductManager` can then use the `_productRepository` object in its methods to perform its business logic. In the following example, `ChangeCodeAsync` method is used to change a product's code (the `ProductCode` property) by ensuring uniqueness of product codes in the system:
|
||||
|
||||
````csharp
|
||||
public class ProductManager : DomainService
|
||||
{
|
||||
private readonly IRepository<Product, Guid> _productRepository;
|
||||
|
||||
public ProductManager(IRepository<Product, Guid> productRepository)
|
||||
{
|
||||
_productRepository = productRepository;
|
||||
}
|
||||
|
||||
public async Task ChangeCodeAsync(Product product, string newProductCode)
|
||||
{
|
||||
Check.NotNull(product, nameof(product));
|
||||
Check.NotNullOrWhiteSpace(newProductCode, nameof(newProductCode));
|
||||
|
||||
if (product.ProductCode == newProductCode)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (await _productRepository.AnyAsync(x => x.ProductCode == newProductCode))
|
||||
{
|
||||
throw new ApplicationException(
|
||||
"Product code is already used: " + newProductCode);
|
||||
}
|
||||
|
||||
product.ProductCode = newProductCode;
|
||||
}
|
||||
}
|
||||
````
|
||||
|
||||
Here, the `ProductManager` forces the rule "product code must be unique". Let's see the `Product` entity class too:
|
||||
|
||||
````csharp
|
||||
public class Product : AuditedAggregateRoot<Guid>
|
||||
{
|
||||
public string ProductCode { get; internal set; }
|
||||
|
||||
public string Name { get; private set; }
|
||||
|
||||
private Product()
|
||||
{
|
||||
/* This constructor is used by EF Core while
|
||||
getting the Product from database */
|
||||
}
|
||||
|
||||
/* Primary constructor that should be used in the application code */
|
||||
public Product(string productCode, string name)
|
||||
{
|
||||
ProductCode = Check.NotNullOrWhiteSpace(productCode, nameof(productCode));
|
||||
Name = Check.NotNullOrWhiteSpace(name, nameof(name));
|
||||
}
|
||||
}
|
||||
````
|
||||
|
||||
You see that the `ProductCode` property's setter is `internal`, which makes possible to set it from the `ProductManager` class as shown before.
|
||||
|
||||
This design has a problem: We had to make the `ProductCode` setter `internal`. Now, any developer may forget to use the `ProductManager.ChangeCodeAsync` method, and can directly set the `ProductCode` on the entity. So, we can't completely force the "product code must be unique" rule.
|
||||
|
||||
It would be better to move the `ChangeCodeAsync` method into the `Product` class and make the `ProductCode` property's setter `private`:
|
||||
|
||||
````csharp
|
||||
public class Product : AuditedAggregateRoot<Guid>
|
||||
{
|
||||
public string ProductCode { get; private set; }
|
||||
|
||||
public string Name { get; private set; }
|
||||
|
||||
// ...
|
||||
|
||||
public async Task ChangeCodeAsync(string newProductCode)
|
||||
{
|
||||
Check.NotNullOrWhiteSpace(newProductCode, nameof(newProductCode));
|
||||
|
||||
if (newProductCode == ProductCode)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
/* ??? HOW TO INJECT THE PRODUCT REPOSITORY HERE ??? */
|
||||
if (await _productRepository.AnyAsync(x => x.ProductCode == newProductCode))
|
||||
{
|
||||
throw new ApplicationException("Product code is already used: " + newProductCode);
|
||||
}
|
||||
|
||||
ProductCode = newProductCode;
|
||||
}
|
||||
}
|
||||
````
|
||||
|
||||
With that design, there is no way to set the `ProductCode` without applying the rule "product code must be unique". Great! But we have a problem: An entity class can not inject dependencies into its constructor, because an entity is not created using the dependency injection system. There are two common points of creating an entity:
|
||||
|
||||
* We can create an entity in our application code, using the standard `new` keyword, like `var product = new Product(...);`.
|
||||
* Entity Framework (and any other ORM / database provider) creates entities after getting them from the database. They typically use the empty (default) constructor of the entity to create it, then sets the properties coming from the database query.
|
||||
|
||||
So, how we can use the product repository in the `Product.ChangeCodeAsync` method? If we forget the dependency injection system, we would think to add the repository as a parameter to the `ChangeCodeAsync` method and delegate the responsibility of obtaining the service reference to the caller of that method:
|
||||
|
||||
````csharp
|
||||
public async Task ChangeCodeAsync(
|
||||
IRepository<Product, Guid> productRepository, string newProductCode)
|
||||
{
|
||||
Check.NotNull(productRepository, nameof(productRepository));
|
||||
Check.NotNullOrWhiteSpace(newProductCode, nameof(newProductCode));
|
||||
|
||||
if (newProductCode == ProductCode)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (await productRepository.AnyAsync(x => x.ProductCode == newProductCode))
|
||||
{
|
||||
throw new ApplicationException(
|
||||
"Product code is already used: " + newProductCode);
|
||||
}
|
||||
|
||||
ProductCode = newProductCode;
|
||||
}
|
||||
````
|
||||
|
||||
However, that design would make hard to use the `ChangeCodeAsync` method, and also exposes its internal dependencies to outside. If we need another dependency in the `ChangeCodeAsync` method later, we should add another parameter, which will effect all the application code that uses the `ChangeCodeAsync` method. I think that's not reasonable. The next section offers a better and a more generic solution to the problem.
|
||||
|
||||
## The Solution
|
||||
|
||||
First of all, we can introduce an interface that should be implemented by the entity classes which needs to use services in their methods:
|
||||
|
||||
````csharp
|
||||
public interface IInjectServiceProvider
|
||||
{
|
||||
ICachedServiceProvider ServiceProvider { get; set; }
|
||||
}
|
||||
````
|
||||
|
||||
`ICachedServiceProvider` is a service that is provided by the ABP Framework. It extends the standard `IServiceProvider`, but caches the resolved services. Basically, it internally resolves a service only a single time, even if you resolve the service from it multiple times. The `ICachedServiceProvider` service itself is a scoped service, means it is created only once in a scope. We can use it to optimize the service resolution, however, the standard `IServiceProvider` would work as expected.
|
||||
|
||||
Next, we can implement the `IInjectServiceProvider` for our `Product` entity:
|
||||
|
||||
````csharp
|
||||
public class Product : AuditedAggregateRoot<Guid>, IInjectServiceProvider
|
||||
{
|
||||
public ICachedServiceProvider ServiceProvider { get; set; }
|
||||
|
||||
//...
|
||||
}
|
||||
````
|
||||
|
||||
I will explain how to set the `ServiceProvider` property later, but first see how to use it in our `Product.ChangeCodeAsync` method. Here, the final `Product` class:
|
||||
|
||||
````csharp
|
||||
public class Product : AuditedAggregateRoot<Guid>, IInjectServiceProvider
|
||||
{
|
||||
public string ProductCode { get; internal set; }
|
||||
|
||||
public string Name { get; private set; }
|
||||
|
||||
public ICachedServiceProvider ServiceProvider { get; set; }
|
||||
|
||||
private Product()
|
||||
{
|
||||
/* This constructor is used by EF Core while
|
||||
getting the Product from database */
|
||||
}
|
||||
|
||||
/* Primary constructor that should be used in the application code */
|
||||
public Product(string productCode, string name)
|
||||
{
|
||||
ProductCode = Check.NotNullOrWhiteSpace(productCode, nameof(productCode));
|
||||
Name = Check.NotNullOrWhiteSpace(name, nameof(name));
|
||||
}
|
||||
|
||||
public async Task ChangeCodeAsync(string newProductCode)
|
||||
{
|
||||
Check.NotNullOrWhiteSpace(newProductCode, nameof(newProductCode));
|
||||
|
||||
if (newProductCode == ProductCode)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var productRepository = ServiceProvider
|
||||
.GetRequiredService<IRepository<Product, Guid>>();
|
||||
|
||||
if (await productRepository.AnyAsync(x => x.ProductCode == newProductCode))
|
||||
{
|
||||
throw new ApplicationException("Product code is already used: " + newProductCode);
|
||||
}
|
||||
|
||||
ProductCode = newProductCode;
|
||||
}
|
||||
}
|
||||
````
|
||||
|
||||
The `ChangeCodeAsync` method gets the product repository from the `ServiceProvider` and uses it to check if there is another product with the given `newProductCode` value.
|
||||
|
||||
Now, let's explain how to set the `ServiceProvider` value...
|
||||
|
||||
### Entity Framework Core Configuration
|
||||
|
||||
Entity Framework 7.0 introduces the `IMaterializationInterceptor` interceptor that allows us to manipulate an entity object just after the entity object is created as a result of database query.
|
||||
|
||||
We can write the following interceptor that sets the `ServiceProvider` property of an entity, if it implements the `IInjectServiceProvider` interface:
|
||||
|
||||
````csharp
|
||||
public class ServiceProviderInterceptor : IMaterializationInterceptor
|
||||
{
|
||||
public object InitializedInstance(
|
||||
MaterializationInterceptionData materializationData,
|
||||
object instance)
|
||||
{
|
||||
if (instance is IInjectServiceProvider entity)
|
||||
{
|
||||
entity.ServiceProvider = materializationData
|
||||
.Context
|
||||
.GetService<ICachedServiceProvider>();
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
````
|
||||
|
||||
> Lifetime of the resolved services are tied to the lifetime of the related `DbContext` instance. So, you don't need to care if the resolved dependencies are disposed. ABP's [unit of work](https://docs.abp.io/en/abp/latest/Unit-Of-Work) system already disposes the `DbContext` instance when the unit of work is completed.
|
||||
|
||||
Once we defined such an interceptor, we should configure our `DbContext` class to use it. You can do it by overriding the `OnConfiguring` method in your `DbContext` class:
|
||||
|
||||
````csharp
|
||||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
||||
{
|
||||
base.OnConfiguring(optionsBuilder);
|
||||
optionsBuilder.AddInterceptors(new ServiceProviderInterceptor());
|
||||
}
|
||||
````
|
||||
|
||||
Finally, you should ignore the `ServiceProvider` property in your entity mapping configuration in your `DbContext` (because we don't want to map it to a database table field):
|
||||
|
||||
````csharp
|
||||
protected override void OnModelCreating(ModelBuilder builder)
|
||||
{
|
||||
base.OnModelCreating(builder);
|
||||
// ...
|
||||
builder.Entity<Product>(b =>
|
||||
{
|
||||
// ...
|
||||
/* We should ignore the ServiceProvider on mapping! */
|
||||
b.Ignore(x => x.ServiceProvider);
|
||||
});
|
||||
}
|
||||
````
|
||||
|
||||
That's all. From now, EF Core will set the `ServiceProvider` property for you.
|
||||
|
||||
### Manually Creating Entities
|
||||
|
||||
While EF Core seamlessly set the `ServiceProvider` property while getting entities from database, you should still set it manually while creating new entities yourself.
|
||||
|
||||
**Example: Set `ServiceProvider` property while creating a new Product entity:**
|
||||
|
||||
````csharp
|
||||
public async Task CreateAsync(CreateProductDto input)
|
||||
{
|
||||
var product = new Product(input.ProductCode, input.Name)
|
||||
{
|
||||
ServiceProvider = _cachedServiceProvider
|
||||
};
|
||||
|
||||
await _productRepository.InsertAsync(product);
|
||||
}
|
||||
````
|
||||
|
||||
Here, you may think that it is not necessary to set the `ServiceProvider`, because we haven't used the `ChangeCodeAsync` method. You are definitely right; It is not needed in this example, because it is clear to see the entity object is not used between the entity creation and saving it to the database. However, if you call a method of the entity, or pass it to another service before inserting into the database, you may not know if the `ServiceProvider` will be needed. So, you should carefully use it.
|
||||
|
||||
Basically, I've introduced the problem and the solution. In the next section, I will explain some limitations of that design and some of my other thoughts.
|
||||
|
||||
## Discussions
|
||||
|
||||
In this section, I will first discuss a slightly different way of obtaining services. Then I will explain limitations and problems of injecting services into entities.
|
||||
|
||||
### Why injected a service provider, but not the services?
|
||||
|
||||
As an obvious question, you may ask why we've property-injected a service provider object, then resolved the services manually. Can't we directly property-inject our dependencies?
|
||||
|
||||
**Example: Property-inject the `IRepository<Product, Guid>` service:**
|
||||
|
||||
````csharp
|
||||
public class Product : AuditedAggregateRoot<Guid>
|
||||
{
|
||||
// ...
|
||||
|
||||
public IRepository<Product, Guid> ProductRepository { get; set; }
|
||||
|
||||
public async Task ChangeCodeAsync(string newProductCode)
|
||||
{
|
||||
Check.NotNullOrWhiteSpace(newProductCode, nameof(newProductCode));
|
||||
|
||||
if (newProductCode == ProductCode)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (await ProductRepository.AnyAsync(x => x.ProductCode == newProductCode))
|
||||
{
|
||||
throw new ApplicationException("Product code is already used: " + newProductCode);
|
||||
}
|
||||
|
||||
ProductCode = newProductCode;
|
||||
}
|
||||
}
|
||||
````
|
||||
|
||||
Now, we don't need to implement the `IInjectServiceProvider` interface and manually resolve the `IRepository<Product, Guid>` object from the `ServiceProvider`. You see that the `ChangeCodeAsync` method is much simpler now.
|
||||
|
||||
So, how to set `ProductRepository`? For the EF Core interceptor part, you can somehow get all public properties of the entity via reflection. Then, for each property, check if such a service does exist, and set it from the dependency injection system if available. Surely, that will be less performant, but will work if you can truly implement. On the other hand, it would be extra hard to set all the dependencies of the entity while manually creating it using the `new` keyword. So, personally I wouldn't recommend that approach.
|
||||
|
||||
### Limitations
|
||||
|
||||
One important limitation is that you can not use the services inside your entity's constructor code. Ideally, the constructor of the `Product` class should check if the product code is already used before. See the following constructor:
|
||||
|
||||
````csharp
|
||||
public Product(string productCode, string name)
|
||||
{
|
||||
ProductCode = Check.NotNullOrWhiteSpace(productCode, nameof(productCode));
|
||||
Name = Check.NotNullOrWhiteSpace(name, nameof(name));
|
||||
|
||||
/* Can not check if product code is already used by another product? */
|
||||
}
|
||||
````
|
||||
|
||||
It is not possible to use the product repository here, because;
|
||||
|
||||
1. The services are property-injected. That means they will be set after the object creation has completed.
|
||||
2. Even if the service is available, it won't be truly possible to call async code in a constructor. You know constructors can not be async in C#, but the repository and other service methods are generally designed as async.
|
||||
|
||||
So, if you want to force the "product code must be unique" rule, you should create an async domain service method (like `ProductManager.CreateAsync(...)`) and always use it to create products (you can make the `Product` class constructor `internal` to not allow to use it in the application layer).
|
||||
|
||||
### Design Problems
|
||||
|
||||
Beside the technical limitations, coupling your entities to external services is generally considered as a bad design. It makes your entities over-complicated, hard to test, and generally leads to take too much responsibility over the time.
|
||||
|
||||
## Conclusion
|
||||
|
||||
In this article, I tried to investigate all aspects of injecting services into entity classes. I explained how to use Entity Framework 7.0 `IMaterializationInterceptor` to implement property-injection pattern while getting entities from database.
|
||||
|
||||
Injecting services into entities seems a certain way of forcing some business rules in your entities. However, because of the current technical limitations, design issues and usage difficulties, I don't suggest to depend on services in your entities. Instead, create domain services when you need to implement a business rule that depends on external services and entities.
|
||||
|
||||
## The Source Code
|
||||
|
||||
* You can find the full source code of the example application [here](https://github.com/abpframework/abp-samples/tree/master/EfCoreEntityDependencyInjectionDemo).
|
||||
* You can see [this pull request](https://github.com/abpframework/abp-samples/pull/207/files) for the changes I've done after creating the application.
|
||||
|
||||
## See Also
|
||||
|
||||
* [What's new in EF Core 7.0](https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-7.0/whatsnew)
|
||||
* [ABP Framework: Dependency Injection](https://docs.abp.io/en/abp/latest/Dependency-Injection)
|
||||
* [ABP Framework: Domain Driven Design](https://docs.abp.io/en/abp/latest/Domain-Driven-Design)
|
||||
@ -0,0 +1,88 @@
|
||||
# Model building conventions in Entity Framework Core 7.0
|
||||
|
||||
In this article, I will show you one of the new features of EF Core 7 named "Model building conventions".
|
||||
|
||||
Entity Framework Core uses a metadata model to describe how entity types are mapped to the database. Before EF Core 7.0, it was not possible to remove or replace existing conventions or add new conventions. With EF Core 7.0, this is now possible. To read more about it, you can visit its [documentation](https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-7.0/whatsnew#model-building-conventions).
|
||||
|
||||
EF Core uses many built-in conventions. You can see the full list of the conventions on `IConvetion` Interface API [documentation](https://learn.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.metadata.conventions.iconvention?view=efcore-7.0).
|
||||
|
||||
If you want to add, remove or replace a convention, you need to override `ConfigureConventions` method of your DbContext as shown below;
|
||||
|
||||
```csharp
|
||||
protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
|
||||
{
|
||||
configurationBuilder.Conventions.Add(_ => new MyCustomConvention());
|
||||
}
|
||||
```
|
||||
|
||||
## Allowed Operations
|
||||
|
||||
### Removing an existing convention
|
||||
|
||||
Existing conventions provided by EF Core are well thought and useful, but sometimes some of them might not be a good candidate for your application. In such cases, you can remove an existing convention as shown below;
|
||||
|
||||
```csharp
|
||||
protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
|
||||
{
|
||||
configurationBuilder.Conventions.Remove(typeof(ForeignKeyIndexConvention));
|
||||
}
|
||||
```
|
||||
|
||||
### Adding a new convention
|
||||
|
||||
Just like removing a convention, we can add a completely new convention as well. You can define many different conventions here. For example, you can specify a standard precision for all decimal fields in your entities.
|
||||
|
||||
```csharp
|
||||
public class DecimalPrecisionConvention : IModelFinalizingConvention
|
||||
{
|
||||
public void ProcessModelFinalizing(IConventionModelBuilder modelBuilder, IConventionContext<IConventionModelBuilder> context)
|
||||
{
|
||||
foreach (var property in modelBuilder.Metadata.GetEntityTypes()
|
||||
.SelectMany(
|
||||
entityType => entityType.GetDeclaredProperties()
|
||||
.Where(
|
||||
property => property.ClrType == typeof(decimal))))
|
||||
{
|
||||
property.Builder.HasPrecision(2);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Note that, conventions are executed in the order they are added. So you need to be careful in which order they are added.
|
||||
|
||||
### Replacing an existing convention
|
||||
|
||||
Sometimes, a default convention might work slightly different than what your app expects. In such cases, you can create your own implementation by inheriting from that convention and replace the default one. For example, you can create a convention as shown below;
|
||||
|
||||
```csharp
|
||||
public class MyCustomConvention : ADefaultEfCoreConvention
|
||||
{
|
||||
public MyCustomConvention(ProviderConventionSetBuilderDependencies dependencies)
|
||||
: base(dependencies)
|
||||
{
|
||||
}
|
||||
// override the methods you want to change.
|
||||
}
|
||||
```
|
||||
|
||||
Then, you can replace the default one with your implementation as shown below;
|
||||
|
||||
```csharp
|
||||
protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
|
||||
{
|
||||
configurationBuilder.Conventions.Replace<ADefaultEfCoreConvention>(
|
||||
serviceProvider => new MyCustomConvention(
|
||||
serviceProvider.GetRequiredService<ProviderConventionSetBuilderDependencies>()));
|
||||
}
|
||||
```
|
||||
|
||||
As a final note, conventions never override configuration marked as **DataAnnotation** or **Explicit**. This means that, even if there is a convention, if the property has a `DataAnnotation` attribute or configuration in `OnModelCreating`, convetion will not be used. Here are the configuration types EF Core uses;
|
||||
|
||||
* **Explicit:** The model element was explicitly configured in OnModelCreating
|
||||
* **DataAnnotation:** The model element was configured using a mapping attribute (aka data annotation) on the CLR type
|
||||
* **Convention:** The model element was configured by a model building convention
|
||||
|
||||
## Using in ABP-based solution
|
||||
|
||||
Since ABP uses EF Core, you can use this feature in ABP as well.
|
||||
@ -0,0 +1,71 @@
|
||||
# Bulk Operations with Entity Framework Core 7.0
|
||||
Entity Framework tracks all the entity changes and applies those changes to the database one by one when the `SaveChanges()` method is called. There was no way to execute bulk operations in Entity Framework Core without a dependency.
|
||||
|
||||
As you know the [Entity Framework Extensions](https://entityframework-extensions.net/bulk-savechanges) library was doing it but it was not free.
|
||||
|
||||
There was no other solution until now. [Bulk Operations](https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-7.0/whatsnew#executeupdate-and-executedelete-bulk-updates) are now available in Entity Framework Core with .NET 7.
|
||||
|
||||
With .NET 7, there are two new methods such as `ExecuteUpdate` and `ExecuteDelete` available to execute bulk operations. It's a similar usage with the Entity Framework Core Extensions library if you're familiar with it.
|
||||
|
||||
You can visit the microsoft example [here](https://docs.microsoft.com/en-us/ef/core/what-is-new/ef-core-7.0/whatsnew#executeupdate-and-executedelete-bulk-updates) about how to use it.
|
||||
|
||||
|
||||
It can be easily used with the DbContext.
|
||||
|
||||
```csharp
|
||||
await context.Tags.Where(t => t.Text.Contains(".NET")).ExecuteDeleteAsync();
|
||||
```
|
||||
|
||||
## Using with ABP Framework
|
||||
ABP Framework provides an abstraction over database operations and implements generic repository pattern. So, DbContext can't be accessed outside of [repositories](https://docs.abp.io/en/abp/latest/Repositories).
|
||||
|
||||
You can use the `ExecuteUpdate` and `ExecuteDelete` methods inside a repository.
|
||||
|
||||
```csharp
|
||||
public class BookEntityFrameworkCoreRepository : EfCoreRepository<BookStoreDbContext, Book, Guid>, IBookRepository
|
||||
{
|
||||
public BookEntityFrameworkCoreRepository(IDbContextProvider<BookStoreDbContext> dbContextProvider) : base(dbContextProvider)
|
||||
{
|
||||
}
|
||||
|
||||
public async Task UpdateListingAsync()
|
||||
{
|
||||
var dbSet = await GetDbSetAsync();
|
||||
|
||||
await dbSet
|
||||
.Where(x => x.IsListed && x.PublishedOn.Year <= 2022)
|
||||
.ExecuteUpdateAsync(s => s.SetProperty(x => x.IsListed, x => false));
|
||||
}
|
||||
|
||||
public async Task DeleteOldBooksAsync()
|
||||
{
|
||||
var dbSet = await GetDbSetAsync();
|
||||
|
||||
await dbSet
|
||||
.Where(x => x.PublishedOn.Year <= 2000)
|
||||
.ExecuteDeleteAsync();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
There is no need to take an action for bulk inserting. You can use the `InsertManyAsync` method of the repository instead of creating a new method for it if you don't have custom logic. It'll use a new bulk inserting feature automatically since it's available in EF Core 7.0.
|
||||
|
||||
```csharp
|
||||
public class MyDomainService : DomainService
|
||||
{
|
||||
protected IRepository<Book, Guid> BookRepository { get; }
|
||||
|
||||
public MyDomainService(IRepository<Book, Guid> bookRepository)
|
||||
{
|
||||
BookRepository = bookRepository;
|
||||
}
|
||||
|
||||
public async Task CreateBooksAsync(List<Book> books)
|
||||
{
|
||||
// It'll use bulk inserting automatically.
|
||||
await BookRepository.InsertManyAsync(books);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> If you use `ExecuteDeleteAsync` or `ExecuteUpdateAsync`, then ABP's soft delete and auditing features can not work. Because these ABP features work with EF Core's change tracking system and these new methods doesn't work with the change tracking system. So, use them carefully.
|
||||
@ -0,0 +1,208 @@
|
||||
# Value generation for DDD guarded types with Entity Framework Core 7.0
|
||||
|
||||
In domain-driven design (DDD), *guarded keys* can improve the type safety of key properties. This is achieved by wrapping the key type in another type which is specific to the use of the key. In this article, I will explain the cases why you may need to use guarded types and discuss the advantages and limitations when implementing to an ABP application.
|
||||
|
||||
> You can find the source code of the example application [here](https://github.com/abpframework/abp-samples/tree/master/EfCoreGuardedTypeDemo).
|
||||
|
||||
## The Problem
|
||||
|
||||
While developing an applications, there are many cases where we manually assign foreign keys that can be in guid type or integer type, etc. This manual assignment mistakes can cause miss-match of unique identifiers, such as **assigning a product ID to a category**, that can be hard to detect in the future.
|
||||
|
||||
Here is a very simplified sample of wrong assignment when trying to update a product category:
|
||||
|
||||
````csharp
|
||||
public class ProductAppService : MyProductStoreAppService, IProductAppService
|
||||
{
|
||||
private readonly IRepository<Product, Guid> _productRepository;
|
||||
|
||||
public ProductAppService(IRepository<Product, Guid> productRepository)
|
||||
{
|
||||
_productRepository = productRepository;
|
||||
}
|
||||
|
||||
public async Task UpdateProductCategoryAsync(Guid productId, Guid categoryId)
|
||||
{
|
||||
var productToUpdate = await _productRepository.GetAsync(productId);
|
||||
productToUpdate.CategoryId = productId; // Wrong assignment that causes error only at run-time
|
||||
|
||||
await _productRepository.UpdateAsync(productToUpdate);
|
||||
}
|
||||
}
|
||||
````
|
||||
|
||||
While the sample demonstrates a very simple mistake, it is easier to come across similar mistakes when the business logic gets more complex especially when you are using methods with **multiple foreign key arguments**. The next section offers using guarded types to prevent these kind of problems as a solution to the problem.
|
||||
|
||||
## The Solution
|
||||
|
||||
Strongly-typed IDs (*guarded keys*) is a DDD approach to address this problem. One of the main problems with .NET users was handling the persisting these objects. With EFCore7, key properties can be guarded with type safety seamlessly.
|
||||
|
||||
To use guarded keys, update your aggregate root or entity unique identifier with a complex type to overcome *primitive obsession*:
|
||||
|
||||
````csharp
|
||||
public readonly struct CategoryId
|
||||
{
|
||||
public CategoryId(Guid value) => Value = value;
|
||||
public Guid Value { get; }
|
||||
}
|
||||
|
||||
public readonly struct ProductId
|
||||
{
|
||||
public ProductId(Guid value) => Value = value;
|
||||
public Guid Value { get; }
|
||||
}
|
||||
````
|
||||
|
||||
You can now use these keys for your aggregate roots or entities:
|
||||
|
||||
```csharp
|
||||
public class Product : AggregateRoot<ProductId>
|
||||
{
|
||||
public ProductId Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
public CategoryId CategoryId { get; set; }
|
||||
|
||||
private Product() { }
|
||||
|
||||
public Product(ProductId id, string name) : base(id)
|
||||
{
|
||||
Name = Check.NotNullOrEmpty(name, nameof(name));
|
||||
}
|
||||
}
|
||||
|
||||
public class Category : AggregateRoot<CategoryId>
|
||||
{
|
||||
public CategoryId Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
public List<Product> Products { get; } = new();
|
||||
|
||||
private Category() { }
|
||||
|
||||
public Category(CategoryId id, string name) : base(id)
|
||||
{
|
||||
Name = Check.NotNullOrEmpty(name, nameof(name));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`ProductId` and `CategoryId` guarded key types shown in the sample use `Guid` key values, which means Guid values will be used in the mapped database tables. This is achieved by defining [value converters](https://learn.microsoft.com/en-us/ef/core/modeling/value-conversions) for the types.
|
||||
|
||||
Override the `ConfigureConventions` method of your DbContext to use the value converters:
|
||||
|
||||
````csharp
|
||||
protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
|
||||
{
|
||||
configurationBuilder.Properties<ProductId>().HaveConversion<ProductIdConverter>();
|
||||
configurationBuilder.Properties<CategoryId>().HaveConversion<CategoryIdConverter>();
|
||||
}
|
||||
|
||||
private class ProductIdConverter : ValueConverter<ProductId, Guid>
|
||||
{
|
||||
public ProductIdConverter()
|
||||
: base(v => v.Value, v => new(v))
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private class CategoryIdConverter : ValueConverter<CategoryId, Guid>
|
||||
{
|
||||
public CategoryIdConverter()
|
||||
: base(v => v.Value, v => new(v))
|
||||
{
|
||||
}
|
||||
}
|
||||
````
|
||||
|
||||
> The code here uses `struct` types. This means they have appropriate value-type semantics for use as keys. If `class` types are used instead, then they need to either override equality semantics or also specify a [value comparer](https://learn.microsoft.com/en-us/ef/core/modeling/value-comparers).
|
||||
|
||||
Now, you can use generic (or custom) repositories of ABP using the guarded type as the key for the repository:
|
||||
|
||||
```csharp
|
||||
public class ProductStoreDataSeedContributor : IDataSeedContributor, ITransientDependency
|
||||
{
|
||||
private readonly IRepository<Category, CategoryId> _categoryRepository;
|
||||
private readonly IRepository<Product, ProductId> _productRepository;
|
||||
|
||||
public ProductStoreDataSeedContributor(
|
||||
IRepository<Category, CategoryId> categoryRepository,
|
||||
IRepository<Product, ProductId> productRepository
|
||||
)
|
||||
{
|
||||
_categoryRepository = categoryRepository;
|
||||
_productRepository = productRepository;
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
You can also use `integer` as guarded type for your key properties and use [Sequence-based key generation for SQL Server](https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-7.0/whatsnew#sequence-based-key-generation-for-sql-server) for value generation.
|
||||
|
||||
## Discussions
|
||||
|
||||
In this section, I will discuss the use cases of guarded types and limitations when implementing to an ABP application.
|
||||
|
||||
### Do I need to use guarded types?
|
||||
|
||||
If you are already following the best practices of [Domain-Driven Design](https://docs.abp.io/en/abp/latest/Domain-Driven-Design), you are aware that **updates** and **creations** of an aggregate are done **over** the aggregate root itself as a whole unit. And entity state changes of an aggregate root should be done using the [domain services](https://docs.abp.io/en/abp/latest/Domain-Services). Domain services should already validate the entity.
|
||||
|
||||
**Example: Using domain service to update product:**
|
||||
|
||||
````csharp
|
||||
public class ProductManager : DomainService
|
||||
{
|
||||
private readonly IRepository<Product, Guid> _productRepository;
|
||||
|
||||
public ProductManager(IRepository<Product, Guid> productRepository)
|
||||
{
|
||||
_productRepository = productRepository;
|
||||
}
|
||||
|
||||
public Task<Product> AssignCategory(Product product, Category category)
|
||||
{
|
||||
// ...
|
||||
|
||||
product.CategoryId = category.Id;
|
||||
|
||||
//..
|
||||
}
|
||||
}
|
||||
````
|
||||
|
||||
In this sample, domain service validates that both **product** and the **category** entities, passed by the application layer, are valid objects since they are not key properties. However, manual assignment is already in place and more complex the domain logic, higher to miss out mistakes. At the end, it will depend on your tolerance level for developer errors comparing to the time you want to spend time on additional code base for guarded types.
|
||||
|
||||
### Limitations
|
||||
|
||||
One important limitation is automatic value generation when using `Guid` as guarded type for your key properties. The basic repository can not generate the unique identifier automatically by the time this article is written:
|
||||
|
||||
```csharp
|
||||
public readonly struct ProductId
|
||||
{
|
||||
public ProductId(Guid value) => Value = value;
|
||||
public Guid Value { get; }
|
||||
}
|
||||
```
|
||||
|
||||
you need to generate the unique identifier **manually**:
|
||||
|
||||
````csharp
|
||||
var newProduct = await _productRepository.InsertAsync(
|
||||
new Product(new ProductId(_guidGenerator.Create()), "New product")
|
||||
);
|
||||
````
|
||||
|
||||
## Conclusion
|
||||
|
||||
In this article, I tried to explain DDD guarded types for key properties and value generation for these properties using Entity Framework 7.0 and ABP.
|
||||
|
||||
Using strongly-typed key properties reduce the chance of unnoticed errors. Admittedly it increases the code complexity by adding new types to your solution with extra coding, especially if you are using classes as keys. Guarded types provide improved safety for your code base at the expense of additional code complexity as in many DDD concepts and patterns.
|
||||
|
||||
If you have a large team working on a large scale solution containing complex business logics where key assignments are abundant or if you are using methods with multiple foreign key arguments, I personally suggest using guarded types.
|
||||
|
||||
## The Source Code
|
||||
|
||||
* You can find the full source code of the example application [here](https://github.com/abpframework/abp-samples/tree/master/EfCoreGuardedTypeDemo).
|
||||
|
||||
## See Also
|
||||
|
||||
* [What's new in EF Core 7.0](https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-7.0/whatsnew)
|
||||
* [ABP Framework: Domain Driven Design](https://docs.abp.io/en/abp/latest/Domain-Driven-Design)
|
||||
|
After Width: | Height: | Size: 625 KiB |
@ -0,0 +1,100 @@
|
||||
# Configuring for Production
|
||||
|
||||
ABP Framework has a lot of options to configure and fine-tune its features. They are all explained in their own documents. Default values for these options are pretty well for most of the deployment environments. However, you may need to care about some options based on how you've structured your deployment environment. In this document, we will highlight these kind of options. So, it is highly recommended to read this document in order to not have unexpected behaviors in your system in production.
|
||||
|
||||
## Distributed Cache Prefix
|
||||
|
||||
ABP's [distributed cache infrastructure](../Caching.md) provides an option to set a key prefix for all of your data that is saved into your distributed cache provider. The default value of this option is not set (it is `null`). If you are using a distributed cache server that is shared by different applications, then you can set a prefix value to isolate an application's cache data from others.
|
||||
|
||||
````csharp
|
||||
Configure<AbpDistributedCacheOptions>(options =>
|
||||
{
|
||||
options.KeyPrefix = "MyCrmApp";
|
||||
});
|
||||
````
|
||||
|
||||
That's all. ABP, then will add this prefix to all of your cache keys in your application as along as you use ABP's `IDistributedCache<TCacheItem>` or `IDistributedCache<TCacheItem,TKey>` services. See the [Caching documentation](../Caching.md) if you are new to distributed caching.
|
||||
|
||||
> **Warning**: If you use ASP.NET Core's standard `IDistributedCache` service, it's your responsibility to add the key prefix (you can get the value by injecting `IOptions<AbpDistributedCacheOptions>`). ABP can not do it.
|
||||
|
||||
> **Warning**: Even if you have never used distributed caching in your own codebase, ABP still uses it for some features. So, you should always configure this prefix if your caching server is shared among multiple systems.
|
||||
|
||||
> **Warning**: If you are building a microservice system, then you will have multiple applications that share the same distributed cache server. In such systems, all applications (or services) should normally use the same cache prefix, because you want all the applications to use the same cache data to have consistency between them.
|
||||
|
||||
> **Warning**: Some of ABP's startup templates are pre-configured to set a prefix value for the distributed cache. So, please check your application code if it is already configured.
|
||||
|
||||
## Distributed Lock Prefix
|
||||
|
||||
ABP's [distributed locking infrastructure](../Distributed-Locking.md) provides an option to set a prefix for all the keys you are using in the distributed lock server. The default value of this option is not set (it is `null`). If you are using a distributed lock server that is shared by different applications, then you can set a prefix value to isolate an application's lock from others.
|
||||
|
||||
````csharp
|
||||
Configure<AbpDistributedLockOptions>(options =>
|
||||
{
|
||||
options.KeyPrefix = "MyCrmApp";
|
||||
});
|
||||
````
|
||||
|
||||
That's all. ABP, then will add this prefix to all of your keys in your application. See the [Distributed Locking documentation](../Distributed-Locking.md) if you are new to distributed locking.
|
||||
|
||||
> **Warning**: Even if you have never used distributed locking in your own codebase, ABP still uses it for some features. So, you should always configure this prefix if your distributed lock server is shared among multiple systems.
|
||||
|
||||
> **Warning**: If you are building a microservice system, then you will have multiple applications that share the same distributed locking server. In such systems, all applications (or services) should normally use the same lock prefix, because you want to globally lock your resources in your system.
|
||||
|
||||
> **Warning**: Some of ABP's startup templates are pre-configured to set a prefix value for distributed locking. So, please check your application code if it is already configured.
|
||||
|
||||
## Email Sender
|
||||
|
||||
ABP's [Email Sending](../Emailing.md) system abstracts sending emails from your application and module code and allows you to configure the email provider and settings in a single place.
|
||||
|
||||
Email service is configured to write email contents to the standard [application log](../Logging.md) in development environment. You should configure the email settings to be able to send emails to users in your production environment.
|
||||
|
||||
Please see the [Email Sending](../Emailing.md) document to learn how to configure its settings to really send emails.
|
||||
|
||||
> **Warning**: If you don't configure the email settings, you will get errors while trying to send emails. For example, the [Account module](../Modules/Account.md)'s *Password Reset* feature sends email to the users to reset their passwords if they forget it.
|
||||
|
||||
## SMS Sender
|
||||
|
||||
ABP's [SMS Sending abstraction](https://docs.abp.io/en/abp/latest/SMS-Sending) provides a unified interface to send SMS to users. However, its implementation is left to you. Because, typically a paid SMS service is used to send SMS, and ABP doesn't depend on a specific SMS provider.
|
||||
|
||||
So, if you are using the `ISmsSender` service, you must implement it yourself, as shown in the following code block:
|
||||
|
||||
````csharp
|
||||
public class MySmsSender : ISmsSender, ITransientDependency
|
||||
{
|
||||
public async Task SendAsync(SmsMessage smsMessage)
|
||||
{
|
||||
// TODO: Send it using your provider...
|
||||
}
|
||||
}
|
||||
````
|
||||
|
||||
> [ABP Commercial](https://commercial.abp.io/) provides a [Twilio SMS Module](https://docs.abp.io/en/commercial/latest/modules/twilio-sms) as a pre-built integration with the popular [Twilio](https://www.twilio.com/) platform.
|
||||
|
||||
## BLOB Provider
|
||||
|
||||
If you use ABP's [BLOB Storing](https://docs.abp.io/en/abp/latest/Blob-Storing) infrastructure, you should care about the BLOB provider in your production environment. For example, if you use the [File System](../Blob-Storing-File-System.md) provider and your application is running in a Docker container, you should configure a volume mapping for the BLOB storage path. Otherwise, your data will be lost when the container is restarted. Also, the File System is not a good provider for production if you have a [clustered deployment](Clustered-Environment.md) or a microservice system.
|
||||
|
||||
Check the [BLOB Storing](../Blob-Storing.md) document to see all the available BLOB storage providers.
|
||||
|
||||
> **Warning**: Even if you don't directly use the BLOB Storage system, a module you are depending on may use it. For example, ABP Commercial's [File Management](https://docs.abp.io/en/commercial/latest/modules/file-management) module stores file contents, and the [Account](https://docs.abp.io/en/commercial/latest/modules/account) module stores user profile pictures in the BLOB Storage system. So, be careful with the BLOB Storing configuration in production. Note that ABP Commercial uses the [Database Provider](../Blob-Storing-Database.md) as a pre-configured BLOB storage provider, which works in production without any problem, but you may still want to use another provider.
|
||||
|
||||
## String Encryption
|
||||
|
||||
ABP's [`IStringEncryptionService` Service](../String-Encryption.md) simply encrypts and decrypts given strings based on a password phrase. You should configure the `AbpStringEncryptionOptions` options for the production with a strong password and keep it as a secret. You can also configure the other properties of those options class. See the following example:
|
||||
|
||||
````csharp
|
||||
Configure<AbpStringEncryptionOptions>(options =>
|
||||
{
|
||||
options.DefaultPassPhrase = "gs5nTT042HAL4it1";
|
||||
});
|
||||
````
|
||||
|
||||
Note that ABP CLI automatically sets the password to a random value on a new project creation. However, it is stored in the `appsettings.json` file and is generally added to your source control. It is suggested to use [User Secrets](https://learn.microsoft.com/en-us/aspnet/core/security/app-secrets) or [Environment Variables](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration) to set that value.
|
||||
|
||||
## The Swagger UI
|
||||
|
||||
ABP's startup solution templates come with [Swagger UI](https://swagger.io/) pre-installed. Swagger is a pretty standard and useful tool to discover and test your HTTP APIs on a built-in UI that is embedded into your application or service. It is typically used in development environment, but you may want to enable it on staging or production environments too.
|
||||
|
||||
While you will always secure your HTTP APIs with other techniques (like the [Authorization](../Authorization.md) system), allowing malicious software and people to easily discover your HTTP API endpoint details can be considered as a security problem for some systems. So, be careful while taking the decision of enabling or disabling Swagger for the production environment.
|
||||
|
||||
> You may also want to see the [ABP Swagger integration](../API/Swagger-Integration.md) document.
|
||||
@ -0,0 +1,14 @@
|
||||
using System;
|
||||
|
||||
namespace Volo.Abp.DependencyInjection;
|
||||
|
||||
public interface ICachedServiceProviderBase : IServiceProvider
|
||||
{
|
||||
T GetService<T>(T defaultValue);
|
||||
|
||||
object GetService(Type serviceType, object defaultValue);
|
||||
|
||||
T GetService<T>(Func<IServiceProvider, object> factory);
|
||||
|
||||
object GetService(Type serviceType, Func<IServiceProvider, object> factory);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue