|
After Width: | Height: | Size: 53 KiB |
|
After Width: | Height: | Size: 53 KiB |
|
After Width: | Height: | Size: 43 KiB |
@ -0,0 +1,170 @@ |
|||||
|
# Common Errors in JWT Bearer Authentication |
||||
|
|
||||
|
When implementing JWT Bearer authentication in an ABP(tiered) application, you might occasionally encounter errors starting with `IDX`. These errors are related to JWT Bearer Token validation and this article will help you understand and resolve them. |
||||
|
|
||||
|
## Enable JWT Bearer authentication |
||||
|
|
||||
|
Your API project usually contains the following code, which enables JWT Bearer authentication and makes it as the default authentication scheme. |
||||
|
|
||||
|
We simply configure the JWT's `Authority` and `Audience` properties, and it will work fine. |
||||
|
|
||||
|
```csharp |
||||
|
context.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) |
||||
|
.AddJwtBearer(options => |
||||
|
{ |
||||
|
options.Authority = "https://localhost:44301/"; //configuration["AuthServer:Authority"]; |
||||
|
options.Audience = "MyProjectName"; |
||||
|
}); |
||||
|
``` |
||||
|
|
||||
|
> `AddJwtBearer` and `AddAbpJwtBearer` will do the same thing, but `AddAbpJwtBearer` is recommended. |
||||
|
|
||||
|
## JWT authentication process |
||||
|
|
||||
|
Let's take a look at how the above code works. |
||||
|
|
||||
|
A JWT Token usually consists of three parts: `Header`, `Payload`, and `Signature`. |
||||
|
|
||||
|
- `Header`: Contains the type and signing algorithm of the token |
||||
|
- `Payload`: Contains the claims of the token, including `sub`, `aud`, `exp`, `iat`, `iss`, `jti`, `preferred_username`, `given_name`, `role`, `email`, etc. |
||||
|
- `Signature`: The cryptographic signature of the token used to verify its authenticity |
||||
|
|
||||
|
Here is an example of a JWT Token issued by `AuthServer(OpenIddict)`: |
||||
|
|
||||
|
The `Header` part: |
||||
|
|
||||
|
 |
||||
|
|
||||
|
The `Payload` part: |
||||
|
|
||||
|
 |
||||
|
|
||||
|
### TokenValidationParameters |
||||
|
|
||||
|
In the `JwtBearerOptions`, there is a `TokenValidationParameters` property, which is used to validate the JWT Token. |
||||
|
|
||||
|
The default implementation for JWT Token validation is `JsonWebTokenHandler`, which comes from the [Microsoft.IdentityModel.JsonWebTokens](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/) package. |
||||
|
|
||||
|
We didn't set the `TokenValidationParameters` property in the code above, so the default values below will be used: |
||||
|
|
||||
|
```csharp |
||||
|
//... |
||||
|
TokenValidationParameters.ValidateAudience = true |
||||
|
TokenValidationParameters.ValidAudience = "MyProjectName" |
||||
|
TokenValidationParameters.ValidAudiences = null |
||||
|
|
||||
|
TokenValidationParameters.ValidateIssuer = true |
||||
|
TokenValidationParameters.ValidIssuer = null |
||||
|
TokenValidationParameters.ValidIssuers = null |
||||
|
//... |
||||
|
``` |
||||
|
|
||||
|
### JWT Bearer Token Validation Process |
||||
|
|
||||
|
During JWT Bearer authentication, API website will get the token from the HTTP request and validate it. |
||||
|
|
||||
|
The `JsonWebTokenHandler` will get the `OpenID Connect` metadata from the `AuthServer`, it will be used in the validation process, the current metadata request address is: https://localhost:44301/.well-known/openid-configuration , it is a fixed address calculated from the `Authority` property. |
||||
|
|
||||
|
First, the token's Signature is verified using the public key obtained from `OpenID Connect` metadata(https://localhost:44301/.well-known/jwks). |
||||
|
|
||||
|
Then, the payload is validated. The payload is a JSON object containing essential information such as the `token type`, `expiration time`, `issuer`, and `audience` etc. |
||||
|
|
||||
|
Most of the validation problems we may encounter are payload validation failures, for example: |
||||
|
|
||||
|
#### Lifetime |
||||
|
|
||||
|
If the token in your request has expired, the validation will fail. You will see the exception information like `IDX10230` in the log. |
||||
|
|
||||
|
#### Audience |
||||
|
|
||||
|
The `ValidAudience` of `TokenValidationParameters` is `MyProjectName`, the `aud` in the payload of the token is also `MyProjectName`, if the token does not contain `aud` or the `aud` does not match, the validation will fail. You may see the exception information like `IDX10206`, `IDX10277` or `IDX10208`. |
||||
|
|
||||
|
> If the `ValidateAudience` of `TokenValidationParameters` is `false`, then the `aud` will not be validated. |
||||
|
|
||||
|
#### Issuer |
||||
|
|
||||
|
The default value of `TokenValidationParameters.ValidateIssuer` is `true`, it requires the token's payload to contain the `issuer` field, and it must match one of `TokenValidationParameters.ValidIssuer` or `TokenValidationParameters.ValidIssuers`. |
||||
|
|
||||
|
> The default value of `ValidIssuer` or `ValidIssuers` is `null`, it will use the `issuer` from the `OpenID Connect` metadata as the default value. |
||||
|
|
||||
|
1. If the token's payload does not contain the `issuer` field, you may see the error `IDX10211`. |
||||
|
2. If the API website cannot get the `OpenID Connect` metadata from AuthServer website, the validation will fail. You may see the error `IDX10204`, the full exception message is: `IDX10204: Unable to validate issuer. validationParameters.ValidIssuer is null or whitespace AND validationParameters.ValidIssuers is null or empty.` |
||||
|
3. If the `issuer` does not match, the validation will fail. You may see the error `IDX10205` in the log. |
||||
|
|
||||
|
> If the `ValidateIssuer` of `TokenValidationParameters` is `false`, then the `issuer` will not be validated. |
||||
|
|
||||
|
> Please note that `OpenIddict` will use the current HTTP request information as the value of `issuer`. If the AuthServer website is deployed behind a reverse proxy or similar deployment configurations, the `issuer` in the token may not be the value you expect. In this case, please specify it manually. |
||||
|
|
||||
|
```csharp |
||||
|
PreConfigure<OpenIddictServerBuilder>(serverBuilder => |
||||
|
{ |
||||
|
serverBuilder.SetIssuer("https://localhost:44301/"); |
||||
|
}); |
||||
|
``` |
||||
|
|
||||
|
## Troubleshooting |
||||
|
|
||||
|
To troubleshoot any `IDX` errors during JWT authentication, you can enable detailed logging by configuring the `identitymodel` logs as follows: |
||||
|
|
||||
|
```csharp |
||||
|
using System.Diagnostics.Tracing; |
||||
|
using Microsoft.IdentityModel.Logging; |
||||
|
|
||||
|
public class Program |
||||
|
{ |
||||
|
public async static Task<int> Main(string[] args) |
||||
|
{ |
||||
|
IdentityModelEventSource.ShowPII = true; |
||||
|
IdentityModelEventSource.Logger.LogLevel = EventLevel.Verbose; |
||||
|
var wilsonTextLogger = newTextWriterEventListener("Logs/identitymodel.txt"); |
||||
|
wilsonTextLogger.EnableEvents(IdentityModelEventSource.Logger, EventLevel.Verbose); |
||||
|
|
||||
|
//... |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
Additionally, you can enable `OpenIddict`'s `Verbose` logs for more detailed debugging information: |
||||
|
|
||||
|
```csharp |
||||
|
var loggerConfiguration = new LoggerConfiguration() |
||||
|
.MinimumLevel.Debug() |
||||
|
.MinimumLevel.Override("Microsoft.EntityFrameworkCore", LogEventLevel.Warning) |
||||
|
.MinimumLevel.Override("OpenIddict", LogEventLevel.Verbose) |
||||
|
.Enrich.FromLogContext() |
||||
|
.WriteTo.Async(c => c.File("Logs/logs.txt")) |
||||
|
``` |
||||
|
|
||||
|
## Summary |
||||
|
|
||||
|
For JWT authentication, you need to pay attention to the following key points: |
||||
|
|
||||
|
1. Ensure your API website can communicate with the AuthServer properly |
||||
|
2. Verify that the `aud` claim in your token matches the expected audience |
||||
|
3. Confirm that the `issuer` claim in your token is valid and matches the configuration |
||||
|
|
||||
|
You can customize the `JwtBearerOptions`'s `TokenValidationParameters` to modify the validation rules to meet your actual needs. |
||||
|
|
||||
|
For example, if your `issuer` needs to support multiple subdomains, you can use the [Owl.TokenWildcardIssuerValidator](https://github.com/maliming/Owl.TokenWildcardIssuerValidator) library to customize the validation. |
||||
|
|
||||
|
```csharp |
||||
|
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) |
||||
|
.AddJwtBearer(options => |
||||
|
{ |
||||
|
options.Authority = "https://abp.io"; |
||||
|
options.Audience = "abp_io"; |
||||
|
|
||||
|
options.TokenValidationParameters.IssuerValidator = TokenWildcardIssuerValidator.IssuerValidator; |
||||
|
options.TokenValidationParameters.ValidIssuers = new[] |
||||
|
{ |
||||
|
"https://{0}.abp.io" |
||||
|
}; |
||||
|
}); |
||||
|
``` |
||||
|
|
||||
|
## References |
||||
|
|
||||
|
- [Configure JWT bearer authentication in ASP.NET Core]([https://learn.microsoft.com/en-us/aspnet/core/security/authentication/jwt-auth?view=aspnetcore-8.0](https://learn.microsoft.com/en-us/aspnet/core/security/authentication/configure-jwt-bearer-authentication)) |
||||
|
- [OpenIddict](https://github.com/openiddict/openiddict-core) |
||||
|
- [IdentityModel](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet) |
||||
|
- [Owl.TokenWildcardIssuerValidator](https://github.com/maliming/Owl.TokenWildcardIssuerValidator) |
||||
|
After Width: | Height: | Size: 68 KiB |
|
After Width: | Height: | Size: 228 KiB |
@ -0,0 +1,262 @@ |
|||||
|
# Using Microsoft AI Extensions Library and OpenAI to Summarize User Comments |
||||
|
|
||||
|
Either you are building an e-commerce application or a simple blog, **user comments** (about your products or blog posts) **can grow rapidly**, making it harder for users to get the gist of discussions at a glance. AI is a pretty good tool to solve the problem. By using AI, you can **summarize all the user comments** and show a single paragraph to your users, so they can easily understand the overall thought of users about the product or the blog post. |
||||
|
|
||||
|
In this tutorial, we’ll walk through a real-life implementation of using AI to summarize multiple user comments in an application. I will implement the solution based on ABP's **[CMS Kit](https://abp.io/docs/latest/modules/cms-kit)** library, as it already features a **[commenting system](https://abp.io/docs/latest/modules/cms-kit/comments)** and a [demo application](https://cms-kit-demo.abpdemo.com/) that displays user comments on **[gallery images](https://cms-kit-demo.abpdemo.com/image-gallery)** (it has not a comment summary feature yet, we will implement it in this tutorial). |
||||
|
|
||||
|
## A Screenshot |
||||
|
|
||||
|
Here, an example screenshot from the application with the comment summary feature: |
||||
|
|
||||
|
 |
||||
|
|
||||
|
## Cloning the Repository |
||||
|
|
||||
|
If you want to follow the development, you can clone the [CMS Kit Demo repository](https://github.com/abpframework/cms-kit-demo) to your computer and make it running by following the instructions on the [README file](https://github.com/abpframework/cms-kit-demo?tab=readme-ov-file#cms-kit-demo). |
||||
|
|
||||
|
I suggest to you to play a little with [the application](https://cms-kit-demo.abpdemo.com/) (create a new user for yourself, add some comments to the images in the gallery), so you understand how it works. |
||||
|
|
||||
|
## Preparing the Solution for AI |
||||
|
|
||||
|
I will use [Microsoft AI Extensions Library](https://learn.microsoft.com/en-us/dotnet/ai/ai-extensions) to use the AI features. It is an abstraction library that can work with multiple AI models and tools. I will use an OpenAI model in the demo. |
||||
|
|
||||
|
The first step is to add the [Microsoft.Extensions.AI.OpenAI](http://nuget.org/packages/Microsoft.Extensions.AI.OpenAI) NuGet package to the project: |
||||
|
|
||||
|
````bash |
||||
|
dotnet add package Microsoft.Extensions.AI.OpenAI --prerelease |
||||
|
```` |
||||
|
|
||||
|
>The Microsoft AI Extensions Library was in preview at the time when I wrote this article. If it has a stable release now, you can remove the `--prerelease` parameter for the preceding command. |
||||
|
|
||||
|
We will store the OpenAI key and model name in user secrets. So, locate the root path of the CMS Kit project (`src\CmsKitDemo` folder) and execute the following commands in order in a command-line terminal: |
||||
|
|
||||
|
````bash |
||||
|
dotnet user-secrets init |
||||
|
dotnet user-secrets set OpenAIKey <your-openai-key> |
||||
|
dotnet user-secrets set ModelName <your-openai-model-name> |
||||
|
```` |
||||
|
|
||||
|
For this example, you need to have an [OpenAI API Key](https://platform.openai.com/). That's all. Now, we are ready to use the AI. |
||||
|
|
||||
|
## Implementing the AI Summarization |
||||
|
|
||||
|
Let's start from the most important point of this article: Comment summarization. I will create a class named `AiCommentSummarizer` to implement the summarization work. Here, the full content of that class: |
||||
|
|
||||
|
````csharp |
||||
|
using System.Text; |
||||
|
using Microsoft.Extensions.AI; |
||||
|
using OpenAI; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
|
||||
|
namespace CmsKitDemo.Utils; |
||||
|
|
||||
|
public class AiCommentSummarizer : ITransientDependency |
||||
|
{ |
||||
|
private readonly IConfiguration _configuration; |
||||
|
|
||||
|
public AiCommentSummarizer(IConfiguration configuration) |
||||
|
{ |
||||
|
_configuration = configuration; |
||||
|
} |
||||
|
|
||||
|
public async Task<string> SummarizeAsync(string[] commentTexts) |
||||
|
{ |
||||
|
// Get the model and key from the configuration |
||||
|
var aiModel = _configuration["ModelName"]; |
||||
|
var apiKey = _configuration["OpenAIKey"]; |
||||
|
|
||||
|
if (aiModel.IsNullOrEmpty() || apiKey.IsNullOrEmpty()) |
||||
|
{ |
||||
|
return ""; |
||||
|
} |
||||
|
|
||||
|
// Create the IChatClient |
||||
|
var client = new OpenAIClient(apiKey) |
||||
|
.GetChatClient(aiModel) |
||||
|
.AsIChatClient(); |
||||
|
|
||||
|
// Create a prompt (input for AI) |
||||
|
var promptBuilder = new StringBuilder(); |
||||
|
|
||||
|
promptBuilder.AppendLine( |
||||
|
@"There are comments from different users of our website about an image. |
||||
|
We want to summarize the comments into a single comment. |
||||
|
Return a single comment with a maximum of 512 characters. Comments are separated by a newline character and given below." |
||||
|
); |
||||
|
promptBuilder.AppendLine(); |
||||
|
|
||||
|
foreach (var commentText in commentTexts) |
||||
|
{ |
||||
|
promptBuilder.AppendLine("User comment:"); |
||||
|
promptBuilder.AppendLine(commentText); |
||||
|
promptBuilder.AppendLine(); |
||||
|
} |
||||
|
|
||||
|
// Submit the prompt and get the response |
||||
|
var response = await client.GetResponseAsync( |
||||
|
promptBuilder.ToString(), |
||||
|
new ChatOptions { MaxOutputTokens = 1024 } |
||||
|
); |
||||
|
|
||||
|
return response.Text; |
||||
|
} |
||||
|
} |
||||
|
```` |
||||
|
|
||||
|
That class is pretty simple and already decorated with comments: |
||||
|
|
||||
|
* First, we are getting the API Key and an OpenAI model name from user secrets. I used `gpt-4.1` as the model name, but you can use another available model. |
||||
|
* Then we are obtaining an `IChatClient` reference for OpenAI. `IChatClient` interface is an abstraction that is provided by the [Microsoft AI Extensions Library](https://learn.microsoft.com/en-us/dotnet/ai/ai-extensions) library, so we can implement rest of the code independently from OpenAI. |
||||
|
* Then we continue by building a proper prompt (input) for the AI operation. |
||||
|
* And finally we are using the AI to generate a response (the summary). |
||||
|
|
||||
|
At this point, all the AI-related work has already been done. The rest of this article explains how to integrate that summarization feature with the [CMS Kit Demo application](https://cms-kit-demo.abpdemo.com/). |
||||
|
|
||||
|
## Adding a CommentsSummary Property to the GalleryImage Entity |
||||
|
|
||||
|
The `GalleryImage` entity is used to represent an image on [the image gallery](https://cms-kit-demo.abpdemo.com/image-gallery). I add a `CommentsSummary` property to that entity: |
||||
|
|
||||
|
````csharp |
||||
|
public class GalleryImage : CreationAuditedAggregateRoot<Guid> |
||||
|
{ |
||||
|
public string Description { get; set; } |
||||
|
|
||||
|
public Guid CoverImageMediaId { get; set; } |
||||
|
|
||||
|
public string CommentsSummary { get; set; } // The new property is here |
||||
|
|
||||
|
//... |
||||
|
} |
||||
|
```` |
||||
|
|
||||
|
Since the CMS Kit Demo application uses Entity Framework Core, I need to add a new database schema migration and update the database: |
||||
|
|
||||
|
````bash |
||||
|
dotnet ef migrations add Added_Summary_To_GalleryImage |
||||
|
dotnet ef database update |
||||
|
```` |
||||
|
|
||||
|
## Updating the Summary |
||||
|
|
||||
|
Great, we have a `GalleryImage.CommentsSummary` property now. But, how will it be updated when a users adds or removes a comment for an image? To implement that; |
||||
|
|
||||
|
* We will listen all the change events for user comments (when a user adds, removes or updates a comment). |
||||
|
* Whenever a comment is changed, we will find the related gallery image, retrieve all the user comments for this image, use the `AiCommentSummarizer` class to summarize all the comments. |
||||
|
* Finally, we wil set the `GalleryImage.CommentsSummary` property with the generated summary text. |
||||
|
|
||||
|
Here, the implementation: |
||||
|
|
||||
|
````csharp |
||||
|
using CmsKitDemo.Entities; |
||||
|
using CmsKitDemo.Utils; |
||||
|
using Microsoft.EntityFrameworkCore; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.Domain.Entities.Events; |
||||
|
using Volo.Abp.Domain.Repositories; |
||||
|
using Volo.Abp.EventBus; |
||||
|
using Volo.CmsKit.Comments; |
||||
|
|
||||
|
namespace CmsKitDemo.EventHandlers; |
||||
|
|
||||
|
public class GalleryImageCommentListener : |
||||
|
ILocalEventHandler<EntityChangedEventData<Comment>>, |
||||
|
ITransientDependency |
||||
|
{ |
||||
|
private readonly IRepository<GalleryImage, Guid> _galleryImageRepository; |
||||
|
private readonly IRepository<Comment, Guid> _commentRepository; |
||||
|
private readonly AiCommentSummarizer _aiCommentSummarizer; |
||||
|
|
||||
|
public GalleryImageCommentListener( |
||||
|
IRepository<GalleryImage, Guid> galleryImageRepository, |
||||
|
IRepository<Comment, Guid> commentRepository, |
||||
|
AiCommentSummarizer aiCommentSummarizer) |
||||
|
{ |
||||
|
_galleryImageRepository = galleryImageRepository; |
||||
|
_commentRepository = commentRepository; |
||||
|
_aiCommentSummarizer = aiCommentSummarizer; |
||||
|
} |
||||
|
|
||||
|
public async Task HandleEventAsync(EntityChangedEventData<Comment> eventData) |
||||
|
{ |
||||
|
var comment = eventData.Entity; |
||||
|
|
||||
|
//Here, we only interest in comments related to image gallery items |
||||
|
if (comment.EntityType != CmsKitDemoConsts.ImageGalleryEntityType) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
if (!Guid.TryParse(comment.EntityId, out var galleryImageId)) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// Get the related image from database |
||||
|
var galleryImage = await _galleryImageRepository.FindAsync(galleryImageId); |
||||
|
if (galleryImage == null) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// Get all the comments related to the image |
||||
|
var queryable = await _commentRepository.GetQueryableAsync(); |
||||
|
var allCommentTexts = await queryable |
||||
|
.Where(c => c.EntityType == CmsKitDemoConsts.ImageGalleryEntityType && |
||||
|
c.EntityId == comment.EntityId) |
||||
|
.Select(c => c.Text) |
||||
|
.ToArrayAsync(); |
||||
|
|
||||
|
// Update the summary of comments related to the image |
||||
|
if (allCommentTexts.Length <= 0) |
||||
|
{ |
||||
|
galleryImage.CommentsSummary = ""; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
galleryImage.CommentsSummary = |
||||
|
await _aiCommentSummarizer.SummarizeAsync(allCommentTexts); |
||||
|
} |
||||
|
|
||||
|
// Update the image in database |
||||
|
await _galleryImageRepository.UpdateAsync(galleryImage); |
||||
|
} |
||||
|
} |
||||
|
```` |
||||
|
|
||||
|
Let's explain that class: |
||||
|
|
||||
|
* `GalleryImageCommentListener` implements the `ILocalEventHandler<EntityChangedEventData<Comment>>` interface. In this way, it can handle an event whenever a `Comment` [entity](https://abp.io/docs/latest/framework/architecture/domain-driven-design/entities) is changed (created, updated or deleted). We are using ABP's [local event bus](https://abp.io/docs/latest/framework/infrastructure/event-bus/local) and its [pre-defined events](https://abp.io/docs/latest/framework/infrastructure/event-bus/local#pre-built-events). |
||||
|
* `HandleEventAsync` is called by the ABP Framework whenever a new `Comment` is created, or an existing `Comment` is deleted or updated. |
||||
|
* ABP's `Comment` entity is reusable and it can be associated with any kind of objects (blog posts, images, etc). So, first we are checking if this comment is related to an image gallery item. |
||||
|
* Then we are getting the related `GalleryImage` entity from the database. |
||||
|
* And getting all comments (including the new one) from the database for this image. |
||||
|
* Finally, using the `AiCommentSummarizer` class to generate the summary and set the `CommentsSummary` property. |
||||
|
|
||||
|
## Show the Summary Card on the UI |
||||
|
|
||||
|
Everything is ready on the backend. Now, we can show the summary text on the user interface. To do, that, I added `CommentsSummary` property also to the `GalleryImageDto` class and used it on the `/Pages/Gallery/Detail.cshtml` view: |
||||
|
|
||||
|
````csharp |
||||
|
@if (!Model.Image.CommentsSummary.IsNullOrEmpty()) |
||||
|
{ |
||||
|
<div class="card mt-3"> |
||||
|
<div class="card-body"> |
||||
|
<h6 class="card-title">Summary of the User Comments</h6> |
||||
|
<p class="mb-auto">@Model.Image.CommentsSummary</p> |
||||
|
</div> |
||||
|
</div> |
||||
|
} |
||||
|
```` |
||||
|
|
||||
|
That section renders the following card on the user interface: |
||||
|
|
||||
|
 |
||||
|
|
||||
|
## Conclusion |
||||
|
|
||||
|
In this article, I demonstrated how to use [Microsoft AI Extensions Library](https://learn.microsoft.com/en-us/dotnet/ai/ai-extensions) to work with OpenAI for summarization of multiple user comments. I reused the [ABP's CMS Kit Demo application](https://github.com/abpframework/cms-kit-demo) to show it in a more real world example. |
||||
|
|
||||
|
## Source Code |
||||
|
|
||||
|
* [Source code of the CMS Kit Demo application](https://github.com/abpframework/cms-kit-demo) |
||||
|
* [All the changes made for this article (as a pull request)](https://github.com/abpframework/cms-kit-demo/pull/18) |
||||
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 27 KiB |
@ -1,3 +1,52 @@ |
|||||
# Configuration |
# Configuration |
||||
|
|
||||
ASP.NET Core has an flexible and extensible key-value based configuration system. In fact, the configuration system is a part of Microsoft.Extensions libraries and it is independent from ASP.NET Core. That means it can be used in any type of application. See [Microsoft's documentation](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/) to learn the configuration infrastructure. ABP is 100% compatible with the configuration system. |
ASP.NET Core has an flexible and extensible key-value based configuration system. The configuration system is a part of Microsoft.Extensions libraries and it is independent from ASP.NET Core. That means it can be used in any type of application. |
||||
|
|
||||
|
See [Microsoft's documentation](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/) to learn the configuration infrastructure. ABP is 100% compatible with the configuration system. |
||||
|
|
||||
|
## Getting the Configuration |
||||
|
|
||||
|
You may need to get the `IConfiguration` service in various places in your codebase. The following section shows two common ways. |
||||
|
|
||||
|
### In Module Classes |
||||
|
|
||||
|
You typically need to get configuration while initializing your application. You can get the `IConfiguration` service using the `ServiceConfigurationContext.Configuration` property inside your [module class](../architecture/modularity/basics.md) as the following example: |
||||
|
|
||||
|
````csharp |
||||
|
public class MyAppModule : AbpModule |
||||
|
{ |
||||
|
public override void ConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
var connectionString = context.Configuration["ConnectionStrings:Default"]; |
||||
|
} |
||||
|
} |
||||
|
```` |
||||
|
|
||||
|
`context.Configuration` is a shortcut property for the `context.Services.GetConfiguration()` method. In general, prefer using `context.Configuration` for simplicity and readability when working within module classes. Use `context.Services.GetConfiguration()` in other contexts where you have an `IServiceCollection` object but do not have access to the `context.Configuration` property. (`IServiceCollection.GetConfiguration` is an extension method that can be used whenever you have an `IServiceCollection` object). |
||||
|
|
||||
|
### In Your Services |
||||
|
|
||||
|
You can directly [inject](dependency-injection.md) the `IConfiguration` service into your services: |
||||
|
|
||||
|
````csharp |
||||
|
public class MyService : ITransientDependency |
||||
|
{ |
||||
|
private readonly IConfiguration _configuration; |
||||
|
|
||||
|
public MyService(IConfiguration configuration) |
||||
|
{ |
||||
|
_configuration = configuration; |
||||
|
} |
||||
|
|
||||
|
public string? GetConnectionString() |
||||
|
{ |
||||
|
return _configuration["ConnectionStrings:Default"]; |
||||
|
} |
||||
|
} |
||||
|
```` |
||||
|
|
||||
|
## See Also |
||||
|
|
||||
|
* [Microsoft's Configuration Documentation](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/) |
||||
|
* [The Options Pattern](options.md) |
||||
|
|
||||
|
|||||
|
After Width: | Height: | Size: 329 KiB |
|
After Width: | Height: | Size: 382 KiB |
|
After Width: | Height: | Size: 64 KiB |
|
After Width: | Height: | Size: 56 KiB |
|
After Width: | Height: | Size: 1.6 MiB |
|
After Width: | Height: | Size: 44 KiB |
@ -0,0 +1,8 @@ |
|||||
|
namespace Volo.Abp.AspNetCore.Components.WebAssembly.WebApp; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// This class is used to indicate that the AddBlazorWebAppTieredServices method has been called for compatibility with the old template code
|
||||
|
/// </summary>
|
||||
|
internal sealed class AddBlazorWebAppTieredServicesHasBeenCalled |
||||
|
{ |
||||
|
} |
||||
@ -1 +1 @@ |
|||||
.dataTable{width:100% !important;border-spacing:0 !important;}.table td,.table th{padding:8px 10px;}.dataTable tbody tr td button{cursor:pointer;}.dataTable tbody tr td div.dropdown ul.dropdown-menu li{cursor:pointer;} |
.dataTable{width:100%!important;border-spacing:0!important}.table td,.table th{padding:8px 10px}.dataTable tbody tr td button{cursor:pointer}.dataTable tbody tr td div.dropdown ul.dropdown-menu li{cursor:pointer}.abp-action-button ui.dropdown-menu[data-popper-reference-hidden]{visibility:hidden;pointer-events:none} |
||||
@ -0,0 +1,12 @@ |
|||||
|
namespace Volo.Abp.Domain.Entities.Caching; |
||||
|
|
||||
|
public class EntityCacheItemWrapper<TEntityCacheItem> |
||||
|
where TEntityCacheItem : class |
||||
|
{ |
||||
|
public TEntityCacheItem? Value { get; set; } |
||||
|
|
||||
|
public EntityCacheItemWrapper(TEntityCacheItem? value) |
||||
|
{ |
||||
|
Value = value; |
||||
|
} |
||||
|
} |
||||