|
After Width: | Height: | Size: 189 KiB |
|
After Width: | Height: | Size: 525 KiB |
@ -0,0 +1,93 @@ |
|||
# ABP.IO Platform 9.0 Has Been Released Based on .NET 9.0 |
|||
|
|||
 |
|||
|
|||
Today, [ABP](https://abp.io/) 9.0 stable version has been released based on [.NET 9.0](https://dotnet.microsoft.com/en-us/download/dotnet/9.0). You can create solutions with ABP 9.0 starting from ABP Studio v0.9.11 or by using the ABP CLI as explained in the following sections. |
|||
|
|||
## What's New With Version 9.0? |
|||
|
|||
All the new features were explained in detail in the [9.0 RC Announcement Post](https://abp.io/blog/announcing-abp-9-0-release-candidate), so there is no need to review them again. You can check it out for more details. |
|||
|
|||
## Getting Started with 9.0 |
|||
|
|||
### Creating New Solutions |
|||
|
|||
You can check the [Get Started page](https://abp.io/get-started) to see how to get started with ABP. You can either download [ABP Studio](https://abp.io/get-started#abp-studio-tab) (**recommended**, if you prefer a user-friendly GUI application - desktop application) or use the [ABP CLI](https://abp.io/docs/latest/cli) to create new solutions. |
|||
|
|||
By default, ABP Studio uses stable versions to create solutions. Therefore, it will be creating the solution with the latest stable version, which is v9.0 for now, so you don't need to specify the version. **You can create solutions with ABP 9.0 starting from v0.9.11.** |
|||
|
|||
### How to Upgrade an Existing Solution |
|||
|
|||
You can upgrade your existing solutions with either ABP Studio or ABP CLI. In the following sections, both approaches are explained: |
|||
|
|||
### Upgrading via ABP Studio |
|||
|
|||
If you are already using the ABP Studio, you can upgrade it to the latest version to align it with ABP v9.0. ABP Studio periodically checks for updates in the background, and when a new version of ABP Studio is available, you will be notified through a modal. Then, you can update it by confirming the opened modal. See [the documentation](https://abp.io/docs/latest/studio/installation#upgrading) for more info. |
|||
|
|||
After upgrading the ABP Studio, then you can open your solution in the application, and simply click the **Switch to stable** action button to instantly upgrade your solution: |
|||
|
|||
 |
|||
|
|||
> Please note that ABP CLI & ABP Studio only upgrade the related ABP packages, so you need to upgrade the other packages for .NET 9.0 manually. |
|||
|
|||
### Upgrading via ABP CLI |
|||
|
|||
Alternatively, you can upgrade your existing solution via ABP CLI. First, you need to install the ABP CLI or upgrade it to the latest version. |
|||
|
|||
If you haven't installed it yet, you can run the following command: |
|||
|
|||
```bash |
|||
dotnet tool install -g Volo.Abp.Studio.Cli |
|||
``` |
|||
|
|||
Or to update the existing CLI, you can run the following command: |
|||
|
|||
```bash |
|||
dotnet tool update -g Volo.Abp.Studio.Cli |
|||
``` |
|||
|
|||
After installing/updating the ABP CLI, you can use the [`update` command](https://abp.io/docs/latest/CLI#update) to update all the ABP related NuGet and NPM packages in your solution as follows: |
|||
|
|||
```bash |
|||
abp update |
|||
``` |
|||
|
|||
You can run this command in the root folder of your solution to update all ABP related packages. |
|||
|
|||
> Please note that ABP CLI & ABP Studio only upgrade the related ABP packages, so you need to upgrade the other packages for .NET 9.0 manually. |
|||
|
|||
## Migration Guides |
|||
|
|||
There are a few breaking changes in this version that may affect your application. Please read the migration guide carefully, if you are upgrading from v8.x: [ABP Version 9.0 Migration Guide](https://abp.io/docs/9.0/release-info/migration-guides/abp-9-0) |
|||
|
|||
## Community News |
|||
|
|||
### Highlights from .NET 9.0 |
|||
|
|||
Our team has closely followed the ASP.NET Core and Entity Framework Core 9.0 releases, read Microsoft's guides and documentation, and adapted the changes to our ABP.IO Platform. We are proud to say that we've shipped the ABP 9.0 based on .NET 9.0 just after Microsoft's .NET 9.0 release. |
|||
|
|||
In addition to the ABP's .NET 9.0 upgrade, our team has created many great articles to highlight the important features coming with ASP.NET Core 9.0 and Entity Framework Core 9.0. |
|||
|
|||
> You can read [this post](https://volosoft.com/blog/Highlights-for-ASP-NET-Entity-Framework-Core-NET-9-0) to see the list of all articles. |
|||
|
|||
### New ABP Community Articles |
|||
|
|||
In addition to [the articles to highlight .NET 9.0 features written by our team](https://volosoft.com/blog/Highlights-for-ASP-NET-Entity-Framework-Core-NET-9-0), here are some of the recent posts added to the [ABP Community](https://abp.io/community): |
|||
|
|||
* [Video: Building Modular Monolith Applications with ASP.NET Core & ABP Studio](https://abp.io/community/videos/building-modular-monolith-applications-with-asp.net-core-abp-studio-66znukvf) by [Halil İbrahim Kalkan](https://x.com/hibrahimkalkan) |
|||
* [How to create your Own AI Bot on WhatsApp Using an ABP.io Template](https://abp.io/community/articles/how-to-create-your-own-ai-bot-on-whatsapp-using-the-abp-framework-c6jgvt9c) by [Michael Kokula](https://abp.io/community/members/Michal_Kokula) |
|||
* [ABP Now Supports .NET 9](https://abp.io/community/articles/abp-now-supports-.net-9-zpkznc4f) by [Alper Ebiçoğlu](https://x.com/alperebicoglu) |
|||
|
|||
Thanks to the ABP Community for all the content they have published. You can also [post your ABP related (text or video) content](https://abp.io/community/posts/submit) to the ABP Community. |
|||
|
|||
### ABP Community Talks 2024.7: What’s New with .NET 9 & ABP 9? |
|||
|
|||
 |
|||
|
|||
In this episode of ABP Community Talks, 2024.7; we will dive into the features that came with .NET 9.0 with [Alper Ebicoglu](https://github.com/ebicoglu), [Engincan Veske](https://github.com/EngincanV), [Berkan Sasmaz](https://github.com/berkansasmaz) and [Ahmet Faruk Ulu](https://github.com/ahmetfarukulu). |
|||
|
|||
## Conclusion |
|||
|
|||
This version comes with some new features and a lot of enhancements to the existing features. You can see the [Road Map](https://docs.abp.io/en/abp/9.0/Road-Map) documentation to learn about the release schedule and planned features for the next releases. Please try ABP v9.0 and provide feedback to help us release more stable versions. |
|||
|
|||
Thanks for being a part of this community! |
|||
|
After Width: | Height: | Size: 27 KiB |
@ -0,0 +1,385 @@ |
|||
# How to Use OpenAI API with ABP Framework |
|||
|
|||
In this article, I will show you how to integrate and use the [OpenAI API](https://github.com/openai/openai-dotnet?tab=readme-ov-file#getting-started) with the [ABP Framework](https://abp.io/). We will explore step-by-step how these technologies can work together to enhance your application with powerful AI capabilities, such as natural language processing, image generation, and more. |
|||
|
|||
 |
|||
|
|||
## Creating an ABP Project |
|||
|
|||
To begin integrating OpenAI API with ABP Framework, you first need to create an ABP project. Follow these steps to create and set up your ABP project: |
|||
### Step 1: Install ABP CLI |
|||
|
|||
The ABP CLI is a command-line interface tool that helps you create and manage ABP projects easily. To install the ABP CLI, run the following command in your terminal: |
|||
|
|||
```bash |
|||
dotnet tool install -g Volo.Abp.Studio.Cli |
|||
``` |
|||
|
|||
### Step 2: Create a New ABP Project |
|||
|
|||
Once you have installed the ABP CLI, you can create a new ABP project using the following command: |
|||
|
|||
```bash |
|||
abp new Acme.OpenAIIntegration -t app --ui-framework mvc --database-provider ef -dbms PostgreSQL --csf |
|||
``` |
|||
|
|||
> This command will generate a complete ABP project with an [MVC UI](https://abp.io/docs/latest/framework/ui/mvc-razor-pages/overall). The examples provided in this article make use of UI controllers for demonstration purposes. However, the same approach can easily be applied to other UI types supported by ABP, such as Blazor or Angular. You can find other options [here](https://abp.io/docs/latest/cli). |
|||
## OpenAI Integration Setup |
|||
|
|||
To begin integrating OpenAI API with ABP Framework, follow these steps: |
|||
|
|||
### Step 1: Create an API Key |
|||
|
|||
To use the OpenAI services, you first need an API key. To obtain one, first [create a new OpenAI account](https://platform.openai.com/signup) or [log in](https://platform.openai.com/login). Next, navigate to the [API key page](https://platform.openai.com/account/api-keys) and select "Create new secret key", optionally naming the key. Make sure to save your API key somewhere safe and do not share it with anyone. |
|||
|
|||
This key will be used to authenticate your application when making requests to the OpenAI endpoints. |
|||
|
|||
### Step 2: Adding *Microsoft.Extensions.AI* Package |
|||
|
|||
To integrate OpenAI API with ABP, we use [Microsoft.Extensions.AI](https://www.nuget.org/packages/Microsoft.Extensions.AI.OpenAI/). This package offers a unified API for integrating AI services, making it easy for developers to work with different AI providers. You can find more details in [this blog post](https://devblogs.microsoft.com/dotnet/introducing-microsoft-extensions-ai-preview/). |
|||
|
|||
To begin integrating OpenAI API with ABP Framework, follow these steps: |
|||
|
|||
1. Add the **Microsoft.Extensions.AI** and **Microsoft.Extensions.AI.OpenAI** (used to interact specifically with OpenAI services. Additionally, this package has alternatives like [Azure OpenAI](https://www.nuget.org/packages/Microsoft.Extensions.AI.OpenAI/), [Azure AI Inference](https://www.nuget.org/packages/Microsoft.Extensions.AI.AzureAIInference/), and [Ollama](https://www.nuget.org/packages/Microsoft.Extensions.AI.Ollama/), offering flexibility for developers to choose the AI provider that best fits their needs) packages: |
|||
|
|||
```bash |
|||
dotnet add package Microsoft.Extensions.AI --prerelease |
|||
dotnet add package Microsoft.Extensions.AI.OpenAI --prerelease |
|||
``` |
|||
|
|||
2. Add the required configuration to the `appsettings.json` file located inside the `Acme.OpenAIIntegration.Web` project and dependencies to your `ConfigureServices` method: |
|||
|
|||
```json |
|||
"AI": { |
|||
"OpenAI": { |
|||
"Key": "YOUR-API-KEY", |
|||
"Chat": { |
|||
"ModelId": "gpt-4o-mini" |
|||
} |
|||
} |
|||
} |
|||
``` |
|||
|
|||
> Replace the value of the `Key` with your OpenAI API key. |
|||
|
|||
Next, add the following code to the `ConfigureServices` method in `OpenAIIntegrationBlazorModule`: |
|||
|
|||
```csharp |
|||
context.Services.AddSingleton(new OpenAIClient(configuration["AI:OpenAI:Key"])); |
|||
|
|||
context.Services.AddChatClient(services => |
|||
services.GetRequiredService<OpenAIClient>().AsChatClient(configuration["AI:OpenAI:Chat:ModelId"] ?? "gpt-4o-mini")); |
|||
``` |
|||
|
|||
## Creating a Sample Page |
|||
|
|||
To demonstrate the use of OpenAI API, let's create a page named `Sample` in the `Acme.OpenAIIntegration.Web` project: |
|||
|
|||
Create a `Sample` folder under the `Pages` folder of the `Acme.OpenAIIntegration.Web` project. Add a new Razor Page by right-clicking the `Sample` folder then selecting `Add > Razor Page`. Name it `Index`. |
|||
|
|||
Open the `Index.cshtml` and change the whole content as shown below: |
|||
|
|||
> Note: This example demonstrates a simple implementation of a sample page that interacts with the OpenAI API, covering chat, [retrieval-augmented generation (RAG)](https://github.com/openai/openai-dotnet?tab=readme-ov-file#how-to-use-assistants-with-retrieval-augmented-generation-rag), and image generation features. Each example is explained in detail in the next section, so feel free to continue for a better understanding of the steps and logic involved. |
|||
|
|||
```html |
|||
@page |
|||
@model Acme.OpenAIIntegration.Web.Pages.Sample |
|||
@{ |
|||
ViewData["Title"] = "OpenAI API Demonstration"; |
|||
} |
|||
|
|||
<h1>@ViewData["Title"]</h1> |
|||
|
|||
<br/><br/> |
|||
|
|||
<div class="row"> |
|||
<div class="col-md-4"> |
|||
<h2>Chat Example</h2> |
|||
<form method="post" asp-page-handler="Chat"> |
|||
<div class="form-group"> |
|||
<label asp-for="ChatInput">Enter your message:</label> |
|||
<textarea asp-for="ChatInput" class="form-control" rows="4"></textarea> |
|||
</div> |
|||
<button type="submit" class="btn btn-primary mt-2">Send</button> |
|||
</form> |
|||
@if (!string.IsNullOrEmpty(Model.ChatResponse)) |
|||
{ |
|||
<h3 class="mt-3">Response:</h3> |
|||
<p>@Model.ChatResponse</p> |
|||
} |
|||
</div> |
|||
|
|||
<div class="col-md-4"> |
|||
<h2>RAG Example</h2> |
|||
<form method="post" asp-page-handler="RAG"> |
|||
<div class="form-group mt-2"> |
|||
<label asp-for="RAGQuery">Query:</label> |
|||
<input asp-for="RAGQuery" class="form-control" /> |
|||
</div> |
|||
<button type="submit" class="btn btn-primary mt-2">Ask</button> |
|||
</form> |
|||
@if (!string.IsNullOrEmpty(Model.RAGResponse)) |
|||
{ |
|||
<h3 class="mt-3">Result:</h3> |
|||
<p>@Model.RAGResponse</p> |
|||
} |
|||
</div> |
|||
|
|||
<div class="col-md-4"> |
|||
<h2>Image Generation Example</h2> |
|||
<form method="post" asp-page-handler="ImageGeneration"> |
|||
<div class="form-group"> |
|||
<label asp-for="ImagePrompt">Image Description:</label> |
|||
<input asp-for="ImagePrompt" class="form-control" /> |
|||
</div> |
|||
<button type="submit" class="btn btn-primary mt-2">Generate Image</button> |
|||
</form> |
|||
@if (Model.GeneratedImageBytes != null) |
|||
{ |
|||
<h3 class="mt-3">Generated Image:</h3> |
|||
<img src="data:image/png;base64,@Convert.ToBase64String(Model.GeneratedImageBytes)" alt="Generated image" class="img-fluid mt-2" /> |
|||
} |
|||
</div> |
|||
</div> |
|||
``` |
|||
|
|||
`Index.cshtml.cs` content should be like that: |
|||
|
|||
```csharp |
|||
using System; |
|||
using System.ClientModel; |
|||
using System.IO; |
|||
using System.Text; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Microsoft.AspNetCore.Mvc.RazorPages; |
|||
using Microsoft.Extensions.AI; |
|||
using OpenAI; |
|||
using OpenAI.Assistants; |
|||
using OpenAI.Files; |
|||
using OpenAI.Images; |
|||
|
|||
namespace Acme.OpenAIIntegration.Web.Pages; |
|||
|
|||
public class Sample : PageModel |
|||
{ |
|||
[BindProperty] |
|||
public string ChatInput { get; set; } |
|||
public string ChatResponse { get; set; } |
|||
|
|||
[BindProperty] |
|||
public string RAGQuery { get; set; } |
|||
public string RAGResponse { get; set; } |
|||
|
|||
[BindProperty] |
|||
public string ImagePrompt { get; set; } |
|||
public byte[] GeneratedImageBytes { get; set; } |
|||
|
|||
private readonly IChatClient _chatClient; |
|||
private readonly OpenAIClient _openAiClient; |
|||
|
|||
public Sample( |
|||
IChatClient chatClient, |
|||
OpenAIClient openAiClient) |
|||
{ |
|||
_chatClient = chatClient; |
|||
_openAiClient = openAiClient; |
|||
} |
|||
|
|||
public async Task<IActionResult> OnPostChatAsync() |
|||
{ |
|||
ChatResponse = $"Chat response: {(await _chatClient.CompleteAsync(ChatInput)).Message}"; |
|||
return Page(); |
|||
} |
|||
|
|||
public async Task<IActionResult> OnPostRAGAsync() |
|||
{ |
|||
#pragma warning disable OPENAI001 |
|||
var fileClient = _openAiClient.GetOpenAIFileClient(); |
|||
var assistantClient = _openAiClient.GetAssistantClient(); |
|||
|
|||
using var document = BinaryData.FromBytes(GetExceptionHandlingDocumentContent().ToArray()).ToStream(); |
|||
var exceptionHandlingDoc = await fileClient.UploadFileAsync( |
|||
document, |
|||
"ExceptionHandling.md", |
|||
FileUploadPurpose.Assistants); |
|||
|
|||
AssistantCreationOptions assistantOptions = new() |
|||
{ |
|||
Name = "Exception Handling Assistant", |
|||
Instructions = |
|||
""" |
|||
This assistant helps you with exception handling in ABP Framework. You can ask questions about exception handling and get answers. |
|||
|
|||
- Do not make any assumptions when asked for information that is not in the document |
|||
- Give the most accurate information possible |
|||
- Give short(max 1-2 sentence) and concise answers |
|||
- Do not provide file citations |
|||
""", |
|||
|
|||
Tools = |
|||
{ |
|||
new FileSearchToolDefinition(), |
|||
}, |
|||
ToolResources = new() |
|||
{ |
|||
FileSearch = new() |
|||
{ |
|||
NewVectorStores = |
|||
{ |
|||
new VectorStoreCreationHelper([exceptionHandlingDoc.Value.Id]), |
|||
} |
|||
} |
|||
}, |
|||
}; |
|||
|
|||
var assistant = await assistantClient.CreateAssistantAsync("gpt-4o", assistantOptions); |
|||
|
|||
ThreadCreationOptions threadOptions = new() |
|||
{ |
|||
InitialMessages = { RAGQuery } |
|||
}; |
|||
|
|||
ThreadRun threadRun = assistantClient.CreateThreadAndRun(assistant.Value.Id, threadOptions); |
|||
|
|||
do |
|||
{ |
|||
Thread.Sleep(TimeSpan.FromSeconds(1)); |
|||
threadRun = assistantClient.GetRun(threadRun.ThreadId, threadRun.Id); |
|||
} while (!threadRun.Status.IsTerminal); |
|||
|
|||
CollectionResult<ThreadMessage> messages |
|||
= assistantClient.GetMessages(threadRun.ThreadId, |
|||
new MessageCollectionOptions() { Order = MessageCollectionOrder.Ascending }); |
|||
|
|||
var response = new StringBuilder(); |
|||
|
|||
foreach (var message in messages) |
|||
{ |
|||
response.AppendLine($"[{message.Role.ToString().ToUpper()}]: "); |
|||
foreach (var contentItem in message.Content) |
|||
{ |
|||
if (!string.IsNullOrEmpty(contentItem.Text)) |
|||
{ |
|||
response.AppendLine(contentItem.Text); |
|||
|
|||
if (contentItem.TextAnnotations.Count > 0) |
|||
{ |
|||
response.AppendLine(""); |
|||
} |
|||
} |
|||
} |
|||
|
|||
response.AppendLine(""); |
|||
#pragma warning restore OPENAI001 |
|||
} |
|||
|
|||
RAGResponse = response.ToString(); |
|||
|
|||
return Page(); |
|||
} |
|||
|
|||
public async Task<IActionResult> OnPostImageGenerationAsync() |
|||
{ |
|||
var client = _openAiClient.GetImageClient("dall-e-3"); |
|||
|
|||
var image = await client.GenerateImageAsync(ImagePrompt, new ImageGenerationOptions |
|||
{ |
|||
ResponseFormat = GeneratedImageFormat.Bytes |
|||
}); |
|||
|
|||
var imageBytes = image.Value.ImageBytes; |
|||
|
|||
using var memoryStream = new MemoryStream(); |
|||
await imageBytes.ToStream().CopyToAsync(memoryStream); |
|||
GeneratedImageBytes = memoryStream.ToArray(); |
|||
|
|||
return Page(); |
|||
} |
|||
|
|||
public ReadOnlySpan<byte> GetExceptionHandlingDocumentContent() |
|||
{ |
|||
return """ |
|||
# Exception Handling |
|||
|
|||
ABP provides a built-in infrastructure and offers a standard model for handling exceptions. |
|||
|
|||
* Automatically **handles all exceptions** and sends a standard **formatted error message** to the client for an API/AJAX request. |
|||
* Automatically hides **internal infrastructure errors** and returns a standard error message. |
|||
* Provides an easy and configurable way to **localize** exception messages. |
|||
* Automatically maps standard exceptions to **HTTP status codes** and provides a configurable option to map custom exceptions. |
|||
|
|||
## Automatic Exception Handling |
|||
|
|||
`AbpExceptionFilter` handles an exception if **any of the following conditions** are met: |
|||
|
|||
* Exception is thrown by a **controller action** which returns an **object result** (not a view result). |
|||
* The request is an AJAX request (`X-Requested-With` HTTP header value is `XMLHttpRequest`). |
|||
* Client explicitly accepts the `application/json` content type (via `accept` HTTP header). |
|||
|
|||
If the exception is handled it's automatically **logged** and a formatted **JSON message** is returned to the client. |
|||
|
|||
## Business Exceptions |
|||
|
|||
Most of your own exceptions will be business exceptions. The `IBusinessException` interface is used to mark an exception as a business exception. |
|||
|
|||
`BusinessException` implements the `IBusinessException` interface in addition to the `IHasErrorCode`, `IHasErrorDetails` and `IHasLogLevel` interfaces. The default log level is `Warning`. |
|||
|
|||
Usually you have an error code related to a particular business exception. For example: |
|||
|
|||
````C# |
|||
throw new BusinessException(QaErrorCodes.CanNotVoteYourOwnAnswer); |
|||
```` |
|||
|
|||
### User Friendly Exception |
|||
|
|||
If an exception implements the `IUserFriendlyException` interface, then ABP does not change it's `Message` and `Details` properties and directly send it to the client. |
|||
|
|||
`UserFriendlyException` class is the built-in implementation of the `IUserFriendlyException` interface. Example usage: |
|||
|
|||
````C# |
|||
throw new UserFriendlyException( |
|||
"Username should be unique!" |
|||
); |
|||
```` |
|||
* The `IUserFriendlyException` interface is derived from the `IBusinessException` and the `UserFriendlyException` class is derived from the `BusinessException` class. |
|||
|
|||
"""u8; |
|||
} |
|||
} |
|||
``` |
|||
|
|||
## Running the Application |
|||
|
|||
After completing the setup, you can run the application using the following command: |
|||
|
|||
```bash |
|||
dotnet run --project ./src/Acme.OpenAIIntegration.Web |
|||
``` |
|||
|
|||
Once the application is running, open your browser and navigate to `/Sample`. You should see the `Sample` page we created, which contains sections for Chat, RAG (Retrieval-Augmented Generation), and Image Generation. You can find the screenshot of the page below: |
|||
|
|||
 |
|||
|
|||
## Examples Overview |
|||
|
|||
To showcase the integration of the OpenAI API with the ABP Framework, we implemented three different examples: |
|||
|
|||
1. **Chat Example**: This example demonstrates how to use OpenAI's chat capabilities by allowing users to enter a message and receive an AI-generated response. The implementation involves setting up a simple form on the `Sample` page where users can input their message. The form submission triggers the `OnPostChatAsync` method, which uses the `IChatClient` to generate a response. |
|||
|
|||
 |
|||
|
|||
2. **Retrieval-Augmented Generation (RAG) Example**: In this example, we use OpenAI to answer user queries by referencing custom documents uploaded to the OpenAI API. The implementation involves uploading a document using the `OpenAIFileClient` and creating an assistant with specific instructions to handle the uploaded content. In this case, the document is a section from ABP's Exception Handling documentation, which includes examples on how ABP handles exceptions, user-friendly error messages, and business exceptions. Users can input their query on the `Sample` page, and the `OnPostRAGAsync` method processes the query to generate precise answers based on the document content. If users ask questions that are not covered in the document, the assistant clearly indicates that the information is not available, as per the instructions provided. For example, when asked about `Object Extensions`, the response begins with: "The uploaded document does not contain information about `Object Extensions`...". This demonstrates how the assistant adheres to the provided instructions. You can also find this example illustrated in the GIF below. |
|||
|
|||
 |
|||
|
|||
 |
|||
|
|||
3. **Image Generation Example**: This example leverages the [DALL-E](https://openai.com/index/dall-e-3/) model to generate images based on user-provided prompts. On the `Sample` page, users can provide a description of the image they want to generate, and the `OnPostImageGenerationAsync` method uses the `OpenAIClient` to generate the image. |
|||
|
|||
 |
|||
|
|||
## Conclusion |
|||
|
|||
In this article, we covered how to integrate the OpenAI API with the ABP Framework by creating a sample project, setting up the OpenAI services, and implementing examples for conversational AI, knowledge-based assistance, and image generation. By following these steps, you can add powerful AI-driven capabilities to your application, making it more interactive, intelligent, and capable of meeting user needs effectively. |
|||
|
After Width: | Height: | Size: 147 KiB |
|
After Width: | Height: | Size: 565 KiB |
|
After Width: | Height: | Size: 954 KiB |
|
After Width: | Height: | Size: 160 KiB |
|
After Width: | Height: | Size: 220 KiB |
|
After Width: | Height: | Size: 49 KiB |
@ -0,0 +1,234 @@ |
|||
# The new Unit Test structure in ABP application |
|||
|
|||
A typical ABP modular project usually consists of three main projects: `Application`, `Domain`, and `EntityFrameworkCore/MongoDB`. In these projects, we may provide many services that require unit testing. |
|||
|
|||
Using abstract unit test classes involves first writing tests in the `Application` and `Domain` layers that are independent of the storage technology, ensuring the correctness of core business logic. These abstract tests are then implemented in `EntityFrameworkCore` or `MongoDB`. The benefits of this approach include: |
|||
|
|||
1. **Reduced Coupling**: Core logic tests do not depend on specific storage technologies, so switching databases does not require rewriting test code. |
|||
2. **Better Isolation**: Focuses on verifying business logic correctness, avoiding interference from database operations. |
|||
3. **Increased Reusability**: The same abstract tests can be reused with different storage implementations. |
|||
4. **Easier Maintenance and Extensibility**: Different storage implementations can be extended independently without breaking existing tests. |
|||
5. **Faster and More Reliable Tests**: Reduces dependency on databases, making tests faster and more stable. |
|||
|
|||
## How to migrate old unit tests to the new unit test structure |
|||
|
|||
Assume our project name is `MyCompanyName.MyProjectName`. |
|||
|
|||
### Changes to the `MyCompanyName.MyProjectName.Application.Tests` project: |
|||
|
|||
1. Remove the `MyCompanyName.MyProjectName.Application.Tests` project's `MyProjectNameApplicationCollection` class. |
|||
2. Modify the `MyCompanyName.MyProjectName.Application.Tests` project's `MyProjectNameApplicationTestBase` class. |
|||
|
|||
```csharp |
|||
public abstract class MyProjectNameApplicationTestBase<TStartupModule> : MyProjectNameTestBase<TStartupModule> |
|||
where TStartupModule : IAbpModule |
|||
{ |
|||
//... |
|||
} |
|||
``` |
|||
|
|||
3. Modify the `MyCompanyName.MyProjectName.Application.Tests` project's unit test classes to become abstract unit test classes, such as: `SampleAppServiceTests`. |
|||
|
|||
```csharp |
|||
public abstract class SampleAppServiceTests<TStartupModule> : MyProjectNameApplicationTestBase<TStartupModule> |
|||
where TStartupModule : IAbpModule |
|||
{ |
|||
[Fact] |
|||
public async Task Initial_Data_Should_Contain_Admin_User() |
|||
{ |
|||
//... |
|||
} |
|||
} |
|||
``` |
|||
|
|||
### Changes to the `MyCompanyName.MyProjectName.Domain.Tests` project: |
|||
|
|||
1. Remove the `MyCompanyName.MyProjectName.Domain.Tests` project's `MyProjectNameDomainCollection` class. |
|||
2. Modify the `MyCompanyName.MyProjectName.Domain.Tests` project's `MyProjectNameDomainTestBase` class. |
|||
|
|||
```csharp |
|||
public abstract class MyProjectNameDomainTestBase<TStartupModule> : MyProjectNameTestBase<TStartupModule> |
|||
where TStartupModule : IAbpModule |
|||
{ |
|||
//... |
|||
} |
|||
``` |
|||
|
|||
3. Modify the `MyCompanyName.MyProjectName.Domain.Tests` project's unit test classes to become abstract unit test classes, such as: `SampleDomainTests`. |
|||
|
|||
```csharp |
|||
public abstract class SampleDomainTests<TStartupModule> : MyProjectNameDomainTestBase<TStartupModule> |
|||
where TStartupModule : IAbpModule |
|||
{ |
|||
[Fact] |
|||
public async Task Should_Set_Email_Of_A_User() |
|||
{ |
|||
//... |
|||
} |
|||
} |
|||
``` |
|||
|
|||
4. Modify the `MyCompanyName.MyProjectName.Domain.Tests` project's `csproj` and module class. Remove references to `EntityFrameworkCore/MongoDB`. |
|||
|
|||
`MyCompanyName.MyProjectName.Domain.Tests.csproj`: |
|||
```xml |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
//... |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\..\src\MyCompanyName.MyProjectName.Domain\MyCompanyName.MyProjectName.Domain.csproj" /> |
|||
<ProjectReference Include="..\MyCompanyName.MyProjectName.TestBase\MyCompanyName.MyProjectName.TestBase.csproj" /> |
|||
</ItemGroup> |
|||
</Project> |
|||
|
|||
``` |
|||
|
|||
`MyProjectNameDomainTestModule.cs`: |
|||
|
|||
```csharp |
|||
[DependsOn( |
|||
typeof(MyProjectNameDomainModule), |
|||
typeof(MyProjectNameTestBaseModule) |
|||
)] |
|||
public class MyProjectNameDomainTestModule : AbpModule |
|||
{ |
|||
//... |
|||
} |
|||
``` |
|||
|
|||
### Changes to the `MyCompanyName.MyProjectName.EntityFrameworkCore.Tests` project: |
|||
|
|||
Here, we need to create implementation classes for all abstract unit tests. |
|||
|
|||
```csharp |
|||
[Collection(MyProjectNameTestConsts.CollectionDefinitionName)] |
|||
public class EfCoreSampleAppServiceTests : SampleAppServiceTests<MyProjectNameEntityFrameworkCoreTestModule> |
|||
{ |
|||
//... |
|||
} |
|||
``` |
|||
|
|||
```csharp |
|||
[Collection(MyProjectNameTestConsts.CollectionDefinitionName)] |
|||
public class EfCoreSampleDomainTests : SampleDomainTests<MyProjectNameEntityFrameworkCoreTestModule> |
|||
{ |
|||
//... |
|||
} |
|||
``` |
|||
|
|||
We also need to modify the project's dependencies and module class, which should directly or indirectly reference the `Application` and `Domain` test projects. |
|||
|
|||
`MyCompanyName.MyProjectName.EntityFrameworkCore.Tests.csproj`: |
|||
|
|||
```xml |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
//... |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\..\src\MyCompanyName.MyProjectName.EntityFrameworkCore\MyCompanyName.MyProjectName.EntityFrameworkCore.csproj" /> |
|||
<ProjectReference Include="..\MyCompanyName.MyProjectName.Application.Tests\MyCompanyName.MyProjectName.Application.Tests.csproj" /> |
|||
<ProjectReference Include="..\..\..\..\..\framework\src\Volo.Abp.EntityFrameworkCore.Sqlite\Volo.Abp.EntityFrameworkCore.Sqlite.csproj" /> |
|||
</ItemGroup> |
|||
</Project> |
|||
``` |
|||
|
|||
`MyProjectNameEntityFrameworkCoreTestModule.cs`: |
|||
|
|||
```csharp |
|||
[DependsOn( |
|||
typeof(MyProjectNameApplicationTestModule), |
|||
typeof(MyProjectNameEntityFrameworkCoreModule), |
|||
typeof(AbpEntityFrameworkCoreSqliteModule) |
|||
)] |
|||
public class MyProjectNameEntityFrameworkCoreTestModule : AbpModule |
|||
{ |
|||
//... |
|||
} |
|||
``` |
|||
|
|||
### Changes to the `MyCompanyName.MyProjectName.MongoDB.Tests` project (skip this step if not using MongoDB): |
|||
|
|||
Like the `EntityFrameworkCore` project, we need to create implementation classes for all abstract unit tests and modify the project's dependencies and module class. |
|||
|
|||
```csharp |
|||
[Collection(MyProjectNameTestConsts.CollectionDefinitionName)] |
|||
public class MongoDBSampleAppServiceTests : SampleAppServiceTests<MyProjectNameMongoDbTestModule> |
|||
{ |
|||
//... |
|||
} |
|||
``` |
|||
|
|||
```csharp |
|||
[Collection(MyProjectNameTestConsts.CollectionDefinitionName)] |
|||
public class MongoDBSampleDomainTests : SampleDomainTests<MyProjectNameMongoDbTestModule> |
|||
{ |
|||
//... |
|||
} |
|||
``` |
|||
|
|||
```xml |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
//... |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\..\src\MyCompanyName.MyProjectName.MongoDB\MyCompanyName.MyProjectName.MongoDB.csproj" /> |
|||
<ProjectReference Include="..\MyCompanyName.MyProjectName.Application.Tests\MyCompanyName.MyProjectName.Application.Tests.csproj" /> |
|||
</ItemGroup> |
|||
</Project> |
|||
``` |
|||
|
|||
```csharp |
|||
[DependsOn( |
|||
typeof(MyProjectNameApplicationTestModule), |
|||
typeof(MyProjectNameMongoDbModule) |
|||
)] |
|||
public class MyProjectNameMongoDbTestModule : AbpModule |
|||
{ |
|||
//... |
|||
} |
|||
``` |
|||
|
|||
### Changes to the `MyCompanyName.MyProjectName.Web.Tests` project: |
|||
|
|||
We need to reference the `EntityFrameworkCore/MongoDB` test projects in this test project. |
|||
|
|||
```xml |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
//... |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\MyCompanyName.MyProjectName.Application.Tests\MyCompanyName.MyProjectName.Application.Tests.csproj" /> |
|||
<ProjectReference Include="..\..\src\MyCompanyName.MyProjectName.Web\MyCompanyName.MyProjectName.Web.csproj" /> |
|||
<ProjectReference Include="..\..\..\..\..\framework\src\Volo.Abp.AspNetCore.TestBase\Volo.Abp.AspNetCore.TestBase.csproj" /> |
|||
<ProjectReference Include="..\MyCompanyName.MyProjectName.EntityFrameworkCore.Tests\MyCompanyName.MyProjectName.EntityFrameworkCore.Tests.csproj" /> |
|||
</ItemGroup> |
|||
</Project> |
|||
``` |
|||
|
|||
```csharp |
|||
[DependsOn( |
|||
typeof(AbpAspNetCoreTestBaseModule), |
|||
typeof(MyProjectNameWebModule), |
|||
typeof(MyProjectNameApplicationTestModule), |
|||
typeof(MyProjectNameEntityFrameworkCoreTestModule) |
|||
)] |
|||
public class MyProjectNameWebTestModule : AbpModule |
|||
{ |
|||
//... |
|||
} |
|||
``` |
|||
|
|||
We no longer need the `MyProjectNameWebCollection` class in this project. Please delete it and use `[Collection(MyProjectNameTestConsts.CollectionDefinitionName)]` instead. |
|||
|
|||
## Conclusion |
|||
|
|||
This is our new unit test structure. Decoupling unit tests from storage technologies ensures the independence of business logic and allows easy switching between storage implementations. Abstract unit test classes improve test reusability, maintainability, and efficiency, reducing refactoring costs and providing flexibility for future tech updates. |
|||
|
|||
## References |
|||
|
|||
- [Unit Test](https://abp.io/docs/latest/testing/unit-tests) |
|||
- [Abstract all db-related unit tests](https://github.com/abpframework/abp/pull/17880) |
|||
|
Before Width: | Height: | Size: 8.5 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 54 KiB |
|
After Width: | Height: | Size: 86 KiB |
|
After Width: | Height: | Size: 56 KiB |
|
After Width: | Height: | Size: 46 KiB |
|
After Width: | Height: | Size: 75 KiB |
|
After Width: | Height: | Size: 72 KiB |
|
After Width: | Height: | Size: 71 KiB |
|
After Width: | Height: | Size: 117 KiB |
|
After Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 163 KiB After Width: | Height: | Size: 165 KiB |
|
After Width: | Height: | Size: 112 KiB |
|
After Width: | Height: | Size: 656 KiB |
|
After Width: | Height: | Size: 55 KiB |
|
After Width: | Height: | Size: 82 KiB |
@ -0,0 +1,19 @@ |
|||
# Idle Session Timeout |
|||
|
|||
The `Idle Session Timeout` feature allows you to automatically log out users after a certain period of inactivity. |
|||
|
|||
## Configure Idle Session Timeout |
|||
|
|||
You can enable/disable the `Idle Session Timeout` feature in the `Setting > Account > Idle Session Timeout` page. |
|||
|
|||
The default idle session timeout is 1 hour. You can change it by selecting a different value from the dropdown list or entering a custom value(in minutes). |
|||
|
|||
 |
|||
|
|||
Once the idle session timeout is reached, the user will see a warning modal before being logged out. if user does not respond for 60 seconds, the user will be logged out automatically. |
|||
|
|||
 |
|||
|
|||
## How it works |
|||
|
|||
There is JavaScript code running in the background to detect user activity. such as mouse movement, key press, click, etc. If there is no activity detected for setting time, The warning modal will be shown to the user. |
|||
@ -1,3 +1 @@ |
|||
# ABP Studio Microservice Solution: Adding New Microservices |
|||
|
|||
This document is planned to be written later. |
|||
> This document is [moved to here](../adding-new-microservices.md). |
|||
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 9.1 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 8.7 KiB After Width: | Height: | Size: 6.5 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 144 KiB |
|
After Width: | Height: | Size: 55 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 68 KiB |
|
After Width: | Height: | Size: 75 KiB |
|
After Width: | Height: | Size: 187 KiB |
|
After Width: | Height: | Size: 106 KiB |
|
After Width: | Height: | Size: 122 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 41 KiB |
|
After Width: | Height: | Size: 135 KiB |
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 119 KiB |
|
After Width: | Height: | Size: 59 KiB |
|
After Width: | Height: | Size: 76 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 101 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 78 KiB |
|
After Width: | Height: | Size: 86 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 117 KiB |
|
After Width: | Height: | Size: 47 KiB |
@ -0,0 +1,40 @@ |
|||
# Web Application Development (with ABP Suite) Tutorial |
|||
````json |
|||
//[doc-params] |
|||
{ |
|||
"UI": ["MVC"], |
|||
"DB": ["EF"] |
|||
} |
|||
```` |
|||
````json |
|||
//[doc-nav] |
|||
{ |
|||
"Next": { |
|||
"Name": "Creating the Solution", |
|||
"Path": "tutorials/book-store-with-abp-suite/part-01" |
|||
} |
|||
} |
|||
```` |
|||
|
|||
> This tutorial is suitable for those who have an ABP Team or a higher [license](https://abp.io/pricing). |
|||
|
|||
## About This Tutorial |
|||
|
|||
> In this tutorial, you will use the [ABP Suite](../../suite/index.md) to generate everything you need to build the **BookStore** application, such as [*Entities*](../../framework/architecture/domain-driven-design/entities.md), [*Domain Services*](../../framework/architecture/domain-driven-design/domain-services.md), [*Application Services*](../../framework/architecture/domain-driven-design/application-services.md), *CRUD pages* and more... |
|||
|
|||
In this tutorial series, you will build an ABP based web application named `Acme.BookStore`. This application is used to manage a list of books and their authors. It is developed using the following technologies: |
|||
|
|||
* **{{DB_Value}}** as the database provider. |
|||
* **{{UI_Value}}** as the UI Framework. |
|||
|
|||
This tutorial is organized as the following parts: |
|||
|
|||
- [Part 1: Creating the Solution](part-01.md) |
|||
- [Part 2: Creating the Books](part-02.md) |
|||
- [Part 3: Creating the Authors](part-03.md) |
|||
- [Part 4: Book to Author Relation](part-04.md) |
|||
- [Part 5: Customizing the Generated Code](part-05.md) |
|||
|
|||
### Download the Source Code |
|||
|
|||
After logging in to the ABP website, you can download the source code from [here](https://abp.io/api/download/samples/suite-bookstore-mvc-ef). |
|||
@ -0,0 +1,47 @@ |
|||
# Web Application Development (with ABP Suite) Tutorial - Part 1: Creating the Solution |
|||
|
|||
````json |
|||
//[doc-params] |
|||
{ |
|||
"UI": ["MVC"], |
|||
"DB": ["EF"] |
|||
} |
|||
```` |
|||
````json |
|||
//[doc-nav] |
|||
{ |
|||
"Previous": { |
|||
"Name": "Overview", |
|||
"Path": "tutorials/book-store-with-abp-suite/index" |
|||
}, |
|||
"Next": { |
|||
"Name": "Creating the Books", |
|||
"Path": "tutorials/book-store-with-abp-suite/part-02" |
|||
} |
|||
} |
|||
```` |
|||
|
|||
Before starting the development, create a new solution named `Acme.BookStore` and run it by following the [getting started tutorial](../../get-started/layered-web-application.md). |
|||
|
|||
You can use the following configurations: |
|||
|
|||
* **Solution Template:** Application (Layered) |
|||
* **Solution Name:** `Acme.BookStore` |
|||
* **UI Framework:** {{if UI=="MVC"}} ASP.NET Core MVC / Razor Pages {{end}} |
|||
* **UI Theme:** LeptonX |
|||
* **Mobile Framework:** None |
|||
* **Database Provider:** {{if DB=="EF"}} Entity Framework Core {{end}} |
|||
* **Public Website:** No |
|||
* **Tiered:** No |
|||
|
|||
You can select the other options based on your preference. |
|||
|
|||
> **Please complete the [Get Started](../../get-started/layered-web-application.md) guide and run the web application before going further.** |
|||
|
|||
The initial solution structure should be like the following in the ABP Studio's [Solution Explorer](../../studio/solution-explorer.md): |
|||
|
|||
 |
|||
|
|||
## Summary |
|||
|
|||
We've created the initial layered monolith solution. In the next part, we will learn how to create entities, and generate CRUD pages based on the specified options (including tests, UI, customizable code support etc.) with [ABP Suite](../../suite/index.md). |
|||
@ -0,0 +1,123 @@ |
|||
# Web Application Development (with ABP Suite) Tutorial - Part 2: Creating the Books |
|||
````json |
|||
//[doc-params] |
|||
{ |
|||
"UI": ["MVC"], |
|||
"DB": ["EF"] |
|||
} |
|||
```` |
|||
````json |
|||
//[doc-nav] |
|||
{ |
|||
"Previous": { |
|||
"Name": "Creating the Solution", |
|||
"Path": "tutorials/book-store-with-abp-suite/part-01" |
|||
}, |
|||
"Next": { |
|||
"Name": "Creating the Authors", |
|||
"Path": "tutorials/book-store-with-abp-suite/part-03" |
|||
} |
|||
} |
|||
```` |
|||
|
|||
In this part, you will create a new entity named `Book` and generate CRUD pages for the related entities with everything that you would normally implement manually (including application services, tests, CRUD pages, database relations and more...) via [ABP Suite](../../suite/index.md) with few clicks. |
|||
|
|||
## Opening the ABP Suite |
|||
|
|||
> Please **stop the application** in ABP Studio's *Solution Runner* panel, if it's currently running, because ABP Suite will make changes in the solution and it might need to build the solution in some steps and running the solution prevents to build it. |
|||
|
|||
After creating the solution in the previous part, now you can open the ABP Suite and start generating CRUD pages. You can select the *ABP Suite -> Open* command on the main menu to open ABP Suite: |
|||
|
|||
 |
|||
|
|||
After clicking the related command, pre-integrated browser of ABP Studio should open, then you can start generating entities and all related codes with a few configurations: |
|||
|
|||
 |
|||
|
|||
## Creating the Book Entity |
|||
|
|||
Before creating the `Book` entity, first we can create a `BookType` enum in the `Acme.BookStore.Domain.Shared` project under the **Books** folder as follows: |
|||
|
|||
```csharp |
|||
namespace Acme.BookStore.Books; |
|||
|
|||
public enum BookType |
|||
{ |
|||
Undefined, |
|||
Adventure, |
|||
Biography, |
|||
Dystopia, |
|||
Fantastic, |
|||
Horror, |
|||
Science, |
|||
ScienceFiction, |
|||
Poetry |
|||
} |
|||
``` |
|||
|
|||
After creating an _enum_ file in your project, you can define it as a property while creating the entity. ABP Suite asks for an enum path to read the enum file, and set the namespace, and enum name in the generated code accordingly. Then, you can create the `Book` entity with some properties. |
|||
|
|||
Type `Book` for the *Name* field and leave the other options as is. ABP Suite automatically calculates proper values for the rest of the inputs for you: |
|||
|
|||
 |
|||
|
|||
ABP Suite sets: |
|||
|
|||
* Entity type as **master** (ABP Suite allows you to establish [master-child relationship](../../suite/creating-master-detail-relationship.md)), |
|||
* Base class as **FullAuditedAggregateRoot** ([see other possible values](../../framework/architecture/domain-driven-design/entities.md)), |
|||
* Primary key type as **Guid**, |
|||
* Plural name, database name, namespace, page title, menu item and more... |
|||
|
|||
You can change the menu-item value as **book** to show a proper icon in the generated UI, and also enable **code customization**, **creating unit & integration tests**, and other options as you wish: |
|||
|
|||
 |
|||
|
|||
After, specifying the entity metadata, open the *Properties* tab and create the properties shown in the following figure: |
|||
|
|||
 |
|||
|
|||
While defining the properties, you define a *Type* property with the type of *enum*. ABP Suite asks for an enum path and fill the all other inputs by reading the specified enum file. Therefore, you can specify the enum path for the `Type` property like in the following figure: |
|||
|
|||
 |
|||
|
|||
> After you select the enum file, ABP Suite automatically sets the namespace and enum name, and lists your enum values in the next section. You can change these values, but for now, you can leave as is. |
|||
|
|||
Here is the all details for the `Book` entity: |
|||
|
|||
* `Name` is **required**, it's a **string** property and maximum length is **128**. |
|||
* `Type` is an **enum** and the enum file path is *\Acme.BookStore.Domain.Shared\Books\BookType.cs*. |
|||
* `PublishDate` is a **DateTime** property and **not nullable**. |
|||
* `Price` is a **float** property and **required**. |
|||
|
|||
You can leave the other configurations as default. |
|||
|
|||
> ABP Suite allows you to define properties with a great range of options, for example, you can specify the property type as *string*, *int*, *float*, *Guid*, *DateTime*, and even *File* (for file upload) and also you can set any options while defining your properties, such as specifying it as *required*, or *nullable*, setting *max-min length*, *default value* and more... |
|||
|
|||
After that, you can click the **Save and Generate** button to start the code generation process: |
|||
|
|||
 |
|||
|
|||
ABP Suite will generate the necessary code for you. It generates: |
|||
|
|||
* `Book` entity (also `BookBase` class, which is allow you customizing the generated entity), |
|||
* Repository implementation (`EfCoreBookRepository` class), |
|||
* `BookManager` domain service, |
|||
* Input & Output **DTOs** and **application service** implementations (`IBookAppService` & `BookAppService`), |
|||
* **Unit & integration tests**, |
|||
* A new **migration** (and also applies to the database), |
|||
* All related **permission**, **object mapping** and **navigation menu item** configurations, |
|||
* and all required **UI components and pages**... |
|||
|
|||
It will take some time to complete the process. After the process is completed, you will see a success message, you can click the *Ok* button, and build & start the application by clicking the *Run -> Build & Start* button in the *Solution Runner* panel: |
|||
|
|||
 |
|||
|
|||
After the application is started, you can right-click and *Browse* on the application to open it in the ABP Studio's pre-integrated browser. You can see the Books page in the following figure with a single record: |
|||
|
|||
 |
|||
|
|||
On this page, you can create a new book, update an existing book, delete a book, export all records (or the filtered records) to excel, filter the records by using the advanced filter section, bulk delete multiple records and so on. |
|||
|
|||
## Summary |
|||
|
|||
In this part, you've created a new entity named `Book` and generated the necessary code for it with [ABP Suite](../../suite/index.md) with a few clicks. ABP Suite generated the all code for you, including the **entity**, **application service**, **database relations**, **unit & integration tests**, **UI** and **defined the custom hooks for code customization**. |
|||
@ -0,0 +1,79 @@ |
|||
# Web Application Development (with ABP Suite) Tutorial - Part 3: Creating the Authors |
|||
````json |
|||
//[doc-params] |
|||
{ |
|||
"UI": ["MVC"], |
|||
"DB": ["EF"] |
|||
} |
|||
```` |
|||
````json |
|||
//[doc-nav] |
|||
{ |
|||
"Previous": { |
|||
"Name": "Creating the Books", |
|||
"Path": "tutorials/book-store-with-abp-suite/part-02" |
|||
}, |
|||
"Next": { |
|||
"Name": "Book to Author Relation", |
|||
"Path": "tutorials/book-store-with-abp-suite/part-04" |
|||
} |
|||
} |
|||
```` |
|||
|
|||
In the previous part, you have created the `Book` entity and in this part, you will create a new entity named `Author` and generate all necesssary code via [ABP Suite](../../suite/index.md) with few clicks. After creating the `Author` entity, you will be establishing [one-to-many relationship](../../suite/generating-crud-page.md#step-by-step-creating-a-navigation-property-with-1-to-many-relationship) with *Book* and *Author* entities, in the next part. |
|||
|
|||
## Creating the Author Entity |
|||
|
|||
After generating the all necessary code for the `Book` entity, and testing the *Books* page, by building & starting the application, now you can continue with creating the `Author` entity. |
|||
|
|||
> Before, creating the `Author` entity, please **stop the application** in ABP Studio's *Solution Runner* panel, because ABP Suite will make changes in the solution and it might need to build the solution in some steps and running the solution prevents to build it. |
|||
|
|||
Click the entity selection box in the top right of the *CRUD page generation* page, and select the *-New entity-*: |
|||
|
|||
 |
|||
|
|||
Then, you can type `Author` for the *Name* field and leave the other options as is (you can change the menu icon as **pen** for a proper menu icon, and/or other options, if you wish). ABP Suite automatically calculates proper values for the rest of the inputs for you: |
|||
|
|||
 |
|||
|
|||
ABP Suite sets: |
|||
|
|||
* Entity type as **master** (ABP Suite allows you to establish [master-child relationship](../../suite/creating-master-detail-relationship.md)), |
|||
* Base class as **FullAuditedAggregateRoot** ([see other possible values](../../framework/architecture/domain-driven-design/entities.md)), |
|||
* Primary key type as **Guid**, |
|||
* Plural name, database name, namespace, page title, menu item and more... |
|||
* Also, it enables **code customization**, **UI code generation**, **unit & integration test generation** and **bulk delete** by default. |
|||
|
|||
After, specifying the entity metadata, open the *Properties* tab and create the properties shown in the following figure: |
|||
|
|||
 |
|||
|
|||
Here the details: |
|||
|
|||
* `Name` is **required**, it's a **string** property. Minimum length is **2** and maximum length is **128**. |
|||
* `BirthDate` is a **DateTime** property and **not nullable**. |
|||
* `ShortBio` is **required**, it's a **string** property, it's a **textarea**, maximum length is **256**. |
|||
|
|||
You can leave the other configurations as default. |
|||
|
|||
> **Note:** All properties are marked as **filterable** by default, and they appear in the advanced filter section because of that. You can set any properties you want as **not filterable** and then the related property will be removed from the advanced filter section and code will be generated accordingly. |
|||
|
|||
You can click the **Save and Generate** button to start the code generation process: |
|||
|
|||
 |
|||
|
|||
ABP Suite will generate the necessary code for you. It will take some time to complete the process. After the process is completed, you will see a success message, you can click the *Ok* button, and build & start the application by clicking the *Run -> Build & Start* button in the *Solution Runner* panel: |
|||
|
|||
 |
|||
|
|||
After the application is started, you can right-click and *Browse* on the application to open it in the ABP Studio's pre-integrated browser and try to add a new author: |
|||
|
|||
 |
|||
|
|||
As you can see, the *Name* field is **required**, the *Birth Date* field shows a datepicker, and the *Short Bio* field is also **required** and it's a **textarea**. You just configured how you want your properties with some options (for example, setting the short bio as **textarea** and **required**), and ABP Suite generated code according that. |
|||
|
|||
## Summary |
|||
|
|||
In this part, you've created a new entity named `Author` and generated the necessary code for it with [ABP Suite](../../suite/index.md) with a few clicks. ABP Suite generated the all code for you, including the **entity**, **application service**, **database relations**, **unit & integration tests**, **UI** and **defined the custom hooks for code customization**. |
|||
|
|||
In the next part, you will establish [one-to-many relation](../../suite/generating-crud-page.md) between the `Book` and `Author` entities. |
|||
@ -0,0 +1,170 @@ |
|||
# Web Application Development (with ABP Suite) Tutorial - Part 4: Book to Author Relation |
|||
````json |
|||
//[doc-params] |
|||
{ |
|||
"UI": ["MVC"], |
|||
"DB": ["EF"] |
|||
} |
|||
```` |
|||
````json |
|||
//[doc-nav] |
|||
{ |
|||
"Previous": { |
|||
"Name": "Creating the Author", |
|||
"Path": "tutorials/book-store-with-abp-suite/part-03" |
|||
}, |
|||
"Next": { |
|||
"Name": "Customizing the Generated Code", |
|||
"Path": "tutorials/book-store-with-abp-suite/part-05" |
|||
} |
|||
} |
|||
```` |
|||
|
|||
In the previous parts, you have created the `Book` and `Author` entities (& generated code for all functionalities) for the book store application. However, currently there is no relation between these entities. |
|||
|
|||
In this part, you will establish to **one-to-many relation** between the `Book` and `Author` entities. |
|||
|
|||
## Establishing Relations with ABP Suite |
|||
|
|||
ABP Suite allows establishing both **one-to-many** and [many-to-many](../../suite/creating-many-to-many-relationship.md) relationships. |
|||
|
|||
In this tutorial, you will only establish **one-to-many relation** between `Book` and `Author` entities. It's pretty straightforward to establish a relationship with ABP Suite. You should just need to navigate to the *Navigations* tab, and provide the metadata for navigation property (1-n) or navigation collection (n-n) relations. |
|||
|
|||
## Creating Book to Author Relationship |
|||
|
|||
> Please **stop the application** in ABP Studio's *Solution Runner* panel, because ABP Suite will make changes in the solution and it might need to build the solution in some steps and running the solution prevents to build it. |
|||
|
|||
To establish **one-to-many relations** between *Book* and *Author* entities, select the `Book` entity from the entity selection box on the top-right of the *CRUD page generation* page: |
|||
|
|||
 |
|||
|
|||
Then, you can open the *Navigations* tab, and click the **Add navigation property (1-n)** button. After that, a navigation property model will open, and you can fill the fields like in the following figure: |
|||
|
|||
 |
|||
|
|||
Here is the details: |
|||
|
|||
* Selected the entity as `Author`. (ABP Suite will establish one-to-many relation between *Book* and *Author* entities with this configuration) |
|||
* Set the property name as *AuthorId*, it will be set as foreign-key restriction in the database and all related database configurations will be made by ABP Suite. |
|||
* Selected the display property as *Name*, this will be used in the dropdown component to set an author with a book & also it will be shown in the datatable of the *Books* page. |
|||
* Also, made the relation **required** and set it **filterable** so books can be filterable by authors. |
|||
|
|||
> **Note**: You should delete all existing books in the database (if any), before the code generation. Because, a new foreign-key will be added to the _books_ table and if there is any record in the table, then a new migration can't apply to the database and you may need to update the database manually. |
|||
|
|||
After, specifying the metadata, you can click the *Ok* button to close the modal. Then, click the **Save and generate** button to start code generation process. ABP Suite will establish one-to-many relationship between the entities, and will generate all necessary code automatically: |
|||
|
|||
 |
|||
|
|||
It will take some time to complete the process. After the process is completed, you will see a success message, you can click the *Ok* button, and build & start the application by clicking the *Run -> Build & Start* button in the *Solution Runner* panel: |
|||
|
|||
 |
|||
|
|||
After the application is started, you can right-click and *Browse* on the application to open it in the ABP Studio's pre-integrated browser. You can first create an author and then create a book with the author for testing: |
|||
|
|||
 |
|||
|
|||
Also, notice that, in the advanced filter section, there is an **Author** dropdown, which you can use to filter books by authors (remember you set **filterable** while defining navigation property and thanks to that, ABP Suite generated the code accordingly): |
|||
|
|||
 |
|||
|
|||
## Unit & Integration Tests |
|||
|
|||
Since you completed the bookstore application, now we can check the generated tests, and run them to see if all of them pass or not. |
|||
|
|||
There are several test projects in the solution: |
|||
|
|||
 |
|||
|
|||
> Test projects slightly differs based on your UI and Database selection. For example, if you select MongoDB, then the `Acme.BookStore.EntityFrameworkCore.Tests` will be `Acme.BookStore.MongoDB.Tests`. |
|||
|
|||
ABP Suite generated unit & integration tests, for the `Book` & `Author` entities. If you open the **Test explorer** in your IDE, you will see the following tests are generated: |
|||
|
|||
 |
|||
|
|||
ABP Suite generated tests for repository implementations & application service implementations for the generated code, if you enable *Create unit & integration tests* option, while creating the entity. Since, you already did that in the previous parts, it generated the all required tests for the entities. |
|||
|
|||
Let's examine one of the generated test classes. Open the *BooksAppServiceTests* (under the *test/Acme.BookStore.Application.Tests/Books/BookApplicationTests.cs*) and check the `CreateAsync` method: |
|||
|
|||
```csharp |
|||
[Fact] |
|||
public async Task CreateAsync() |
|||
{ |
|||
// Arrange |
|||
var input = new BookCreateDto |
|||
{ |
|||
Name = "6c3d1eda8bf04852b7bd5dfdbbd93224b252478c2e474d4c8faf24fa6b182168ca830d4f80e64e4a8e363f33e151d1d34a04be4709274c7fbf2214f9bb3a16c3", |
|||
Type = default, |
|||
PublishDate = new DateTime(2006, 8, 21), |
|||
Price = 754882891, |
|||
AuthorId = Guid.Parse("602460f6-df6e-456a-89d9-8c5870dfc583") |
|||
}; |
|||
|
|||
// Act |
|||
var serviceResult = await _booksAppService.CreateAsync(input); |
|||
|
|||
// Assert |
|||
var result = await _bookRepository.FindAsync(c => c.Id == serviceResult.Id); |
|||
|
|||
result.ShouldNotBe(null); |
|||
result.Name.ShouldBe("6c3d1eda8bf04852b7bd5dfdbbd93224b252478c2e474d4c8faf24fa6b182168ca830d4f80e64e4a8e363f33e151d1d34a04be4709274c7fbf2214f9bb3a16c3"); |
|||
result.Type.ShouldBe(default); |
|||
result.PublishDate.ShouldBe(new DateTime(2006, 8, 21)); |
|||
result.Price.ShouldBe(754882891); |
|||
} |
|||
``` |
|||
|
|||
ABP Suite; |
|||
|
|||
* Create the `BookCreateDto` input DTO object, and fill its values with dummy data to simulate creating a book, |
|||
* Then, it calls the `IBooksAppService.CreateAsync` method to create a book, |
|||
* And finally, asserts the returned result to see if it's as expected or not. |
|||
|
|||
Notice, also the *AuthorId* is set in the `BookCreateDto` object. At that point, you might ask yourself that I haven't created the author with that ID before, should not it throw exception? |
|||
|
|||
No, it will not throw an exception, because ABP Suite also generates simple dummy data for the entities just for the tests! You can see the test data seed contributors under the *Acme.BookStore.Domain.Tests* project: |
|||
|
|||
 |
|||
|
|||
Here is the content of the `AuthorsDataSeedContributor.SeedAsync` method: |
|||
|
|||
```csharp |
|||
public async Task SeedAsync(DataSeedContext context) |
|||
{ |
|||
if (IsSeeded) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
await _authorRepository.InsertAsync(new Author |
|||
( |
|||
id: Guid.Parse("602460f6-df6e-456a-89d9-8c5870dfc583"), |
|||
name: "d7bbb3bff0d54ad799477298c4572e9c05fd1175ab21416da17d0001e2b697cd7fef99fdb4414f26a05789667a97442bd65865510ba34c3599e874ccf08b45e4", |
|||
birthDate: new DateTime(2010, 2, 11), |
|||
shortBio: "3c2ff43c18e34d7b9ad3f1b9c444cbb000f90808d3774cb6b7702b957f472d74048597f93df744f6a6fdf507be428e016edec982f1174e09b124982cbc40156290ce6bc9fd7b49b4972741956cc847891cb55ad0942f4534b90aa0561d3e0c200340b613c7ad40c38b4b2f2c39298169a853473faed34341a130b31e1eb57e92" |
|||
)); |
|||
|
|||
await _authorRepository.InsertAsync(new Author |
|||
( |
|||
id: Guid.Parse("6ea5a6b2-919e-4334-9728-13f4872e5e0e"), |
|||
name: "fd332fb58f184716962b08fbaa92f1c3e0963d843ba34c82bb5409517f60da3727c43b05e8d4490f996c5d19265962e53a69ed5e3e144509aad1441e37ce5081", |
|||
birthDate: new DateTime(2010, 6, 10), |
|||
shortBio: "b7808946c46c42e3935c4d8203d82973cfb98c5d81644f1da4ce1e643767849e23e0eb12a92f48be8f7eec0c07aefa043721fdd3fea542cfa644d2b7d428dc8842647180ef8a47139e097f6674c4f0d86c46765c406042a2a858865cb112ecd78d9ef6f5843e444994641f924a38a2d24ee4e212d41444888d3c0861af0cf9dd" |
|||
)); |
|||
|
|||
await _unitOfWorkManager!.Current!.SaveChangesAsync(); |
|||
|
|||
IsSeeded = true; |
|||
} |
|||
``` |
|||
|
|||
Since ABP Suite generated the test data seed contributors for each entity, you have initial data while testing your services. Also, as you would notice, the id in this example (*602460f6-df6e-456a-89d9-8c5870dfc583*) is same as the *authorId* field in the `BooksAppServiceTests.CreateAsync` method. |
|||
|
|||
Let's execute all tests, and see the results: |
|||
|
|||
 |
|||
|
|||
## Summary |
|||
|
|||
So far, you have created the all functionality for the bookstore application without needing to write any single line of code. ABP Suite generated the entities, application services, UI components, unit & integration tests and more... |
|||
|
|||
In the next part, you will write some code and modify the ABP Suite's generated code by writing the code in the specified hookpoints. Thanks to [ABP Suite's Customized Code Support](../../suite/customizing-the-generated-code.md), in the next generation, our custom code will not be overridden and will be preserved. |
|||
@ -0,0 +1,123 @@ |
|||
# Web Application Development (with ABP Suite) Tutorial - Part 5: Customizing the Generated Code |
|||
````json |
|||
//[doc-params] |
|||
{ |
|||
"UI": ["MVC"], |
|||
"DB": ["EF"] |
|||
} |
|||
```` |
|||
````json |
|||
//[doc-nav] |
|||
{ |
|||
"Previous": { |
|||
"Name": "Book to Author Relation", |
|||
"Path": "tutorials/book-store-with-abp-suite/part-04" |
|||
} |
|||
} |
|||
```` |
|||
|
|||
So far, you have created the all functionality for the bookstore application without needing to write any single line of code. In this part, let's write some code and check one of the great features of the ABP Suite, which is [Customizable Code Support](../../suite/customizing-the-generated-code.md). |
|||
|
|||
## Customizable Code Support |
|||
|
|||
ABP Suite allows you to customize the generated code blocks and preserve your custom code changes in the next CRUD Page Generation. It specifies hook points to allow adding custom code blocks. Then, the code written by you to these hook points will be respected and will not be overridden in the next CRUD Page Generation. |
|||
|
|||
To enable custom code support, you should check the *Customizable code* option in the **CRUD Page Generation** page (it's selected by default), and you enabled it for both entities: |
|||
|
|||
 |
|||
|
|||
## Custom Code Hookpoints |
|||
|
|||
When you enable the *custom code support*, ABP Suite adds some hookpoints that you can write your own custom code without worrying about, are my codes being overridden with the next CRUD page generation. |
|||
|
|||
On the C# side, ABP Suite adds abstract base classes for entities, application services, interfaces, domain services and so on... (and partial classes for interfaces) |
|||
|
|||
You can write your custom code in those classes (with the `*.Extended.cs` extension) and next time when you need to re-generate the entity, your custom code will not be overridden (only the base abstract classes will be re-generated and your changes on Suite will be respected): |
|||
|
|||
 |
|||
|
|||
> For example, you can create a new repository method like in the example above, and in the next CRUD page generation, you will ABP Suite won't override your custom code. |
|||
|
|||
On the UI side, ABP Suite provides convenient comment placeholders within pages for MVC, Blazor, and Angular UIs. These comment sections serve as hook points where you can add your custom code. |
|||
|
|||
For example, if you open the *Books/Index.cshtml* file in your IDE, you will see those placeholders like following: |
|||
|
|||
```xml |
|||
<!-- Code omitted for brevity --> |
|||
|
|||
@section styles |
|||
{ |
|||
@*//<suite-custom-code-block-1>*@ |
|||
@*//</suite-custom-code-block-1>*@ |
|||
} |
|||
|
|||
<!-- ... --> |
|||
``` |
|||
|
|||
You can write your custom codes between the _**<suite-custom-code-block-n></suite-custom-code-block-n>**_ placeholders and you can also extend these placeholders by customizing the [ABP Suite templates](../../suite/editing-templates.md). |
|||
|
|||
> For more information, please refer to [Customizing the Generated Code documentation](../../suite/customizing-the-generated-code.md) |
|||
|
|||
## Implementing Custom Code |
|||
|
|||
Let's see the custom code support in action. We can demonstrate this feature with an easy example. |
|||
|
|||
Assume that we want to show the author's name with his abbreviated name. For example, for the author *John Ronald Reuel Tolkien*, we want to show the name *John Ronald Reuel Tolkien (a.k.a J.R.R.T)*. Achieving that is pretty straightforward. |
|||
|
|||
We just need to open the *src/Acme.BookStore.Application/Books/BooksAppService.Extended.cs* file and override the base `GetListAsync` method, which is called on the books page: |
|||
|
|||
```csharp |
|||
using System; |
|||
using System.Linq; |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Application.Dtos; |
|||
using Volo.Abp.Domain.Repositories; |
|||
using Volo.Abp.Caching; |
|||
|
|||
namespace Acme.BookStore.Books |
|||
{ |
|||
public class BooksAppService : BooksAppServiceBase, IBooksAppService |
|||
{ |
|||
//<suite-custom-code-autogenerated> |
|||
public BooksAppService(IBookRepository bookRepository, BookManager bookManager, IDistributedCache<BookDownloadTokenCacheItem, string> downloadTokenCache, IRepository<Acme.BookStore.Authors.Author, Guid> authorRepository) |
|||
: base(bookRepository, bookManager, downloadTokenCache, authorRepository) |
|||
{ |
|||
} |
|||
//</suite-custom-code-autogenerated> |
|||
|
|||
//Write your custom code... |
|||
public override async Task<PagedResultDto<BookWithNavigationPropertiesDto>> GetListAsync(GetBooksInput input) |
|||
{ |
|||
var result = await base.GetListAsync(input); |
|||
|
|||
foreach (var book in result.Items) |
|||
{ |
|||
var akaName = book.Author.Name.Split(" ").Select(q => q[0]).JoinAsString("."); |
|||
book.Author.Name += $" (a.k.a {akaName})"; |
|||
} |
|||
|
|||
return result; |
|||
} |
|||
} |
|||
} |
|||
``` |
|||
|
|||
* Here, we have overridden the `GetListAsync` method and changed its result according to our need. |
|||
* Notice, this class is derieved from the `BooksAppServiceBase` class and implements the `IBooksAppService`. |
|||
* Thus, ABP Suite only modifies the `BooksAppServiceBase` class and implements the necessary services in each generation, but does not generate the files with the `*.Extended` postfixes and let you implement your own custom code. |
|||
* You can create new methods, override an existing method and change its behaviour, add custom hookpoints to extend customization capabilities and more... |
|||
|
|||
Now, we can open ABP Suite and try to regenerate the book entity and see if our custom code is gone or not: |
|||
|
|||
 |
|||
|
|||
After the regeneration has been completed, we can check the **BooksAppService.Extended.cs** file and we should see our custom code is there without any modification. ABP Suite didn't override our custom code, and finally, we can run the application to see the final result: |
|||
|
|||
 |
|||
|
|||
ABP Suite's custom code support is not limited to the backend side. You can also override the UI. You can refer to the [Customizing the Generated Code document](../../suite/customizing-the-generated-code.md) for further info. |
|||
|
|||
## Summary |
|||
|
|||
In this tutorial, you created the bookstore application without needing to write a single line of code with ABP Suite. Then, in this last part, you have written custom code in the specified hookpoints and it did not override by ABP Suite. |
|||