This example is an ASP.NET Core application with integrated DevExpress Reports and an AI assistant. User requests and assistant responses are displayed on-screen using the DevExtreme Chat (dxChat) component.
The AI assistant's role depends on associated DevExpress Reports component:
- Data Analysis Assistant: An assistant for the DevExpress Web Document Viewer. This assistant analyzes report content and answers questions related to information within the report.
- UI Assistant: An assistant for the DevExpress Web Report Designer. This assistant explains how to use the Designer UI to accomplish various tasks. Responses are based on information from end-user documentation for DevExpress Web Reporting components.
Note: AI Assistant initialization takes time. The assistant tab becomes available once Azure OpenAI uploads and indexes the source document on the server.
To answer questions, the application uploads ODF documents to Azure OpenAI, and creates a chat agent using the Azure OpenAI Responses API. The agent uses File Search and Code Interpreter tools to read and analyze the document.
Note
DevExpress AI-powered extensions follow the "bring your own key" principle. DevExpress does not offer a REST API and does not ship any built-in LLMs/SLMs. You need an active Azure/Open AI subscription to obtain the REST API endpoint, key, and model deployment name. These variables must be specified at application startup to register AI clients and enable DevExpress AI-powered Extensions in your application.
Create an Azure OpenAI resource in the Azure portal. Refer to the following help topic for additional information: Microsoft - Create and deploy an Azure OpenAI Service resource.
Once you obtain a private endpoint and an API key, register them as AZURE_OPENAI_ENDPOINT and AZURE_OPENAI_APIKEY environment variables. The EnvSettings.cs file reads these settings. DeploymentName is the name of your Azure model deployment. he model must support Responses API, File Search, and Code Interpreter tools (for example, gpt-5.4):
public static class EnvSettings {
public static string AzureOpenAIEndpoint { get { return Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT"); } }
public static string AzureOpenAIKey { get { return Environment.GetEnvironmentVariable("AZURE_OPENAI_APIKEY"); } }
public static string DeploymentName { get { return "gpt-5.4"; } }
}Files to Review:
Register AI services in your application. Add the following code to the Program.cs file:
using Azure;
using Azure.AI.OpenAI;
using ReportingApp.Services;
using Microsoft.Extensions.Logging;
// ...
var azureOpenAIClient = new AzureOpenAIClient(
new Uri(EnvSettings.AzureOpenAIEndpoint),
new AzureKeyCredential(EnvSettings.AzureOpenAIKey));
// Create a Responses API agent (with File Search and Code Interpreter tools) for each chat.
builder.Services.AddSingleton<AgentFactory>(sp =>
new(azureOpenAIClient, EnvSettings.DeploymentName, sp.GetRequiredService<ILogger<AgentFactory>>()));
builder.Services.AddSingleton<IAIReportingChatService, AIReportingChatService>();
// ...Files to Review:
On the server side, the AIReportingChatService manages chat sessions:
public interface IAIReportingChatService {
IChatResponseProvider GetChatProvider(string sessionId);
Task<string> OpenDocumentChatAsync(Stream data);
Task<string> OpenDesignerChatAsync();
Task CloseChatAsync(string sessionId);
}The AgentFactory class creates an agent that answers questions. When a chat opens, AgentFactory.CreateAgentWithFileAsync does the following:
- Uploads the source PDF to Azure OpenAI and adds it to a short-lived vector store.
- Creates a Responses API agent with File Search and Code Interpreter tools. The agent reads the document content and runs calculations to answer data-driven questions.
- Starts a session that preserves the conversation history.
- Returns an
IChatResponseProvider.
AIReportingChatService stores each provider by session id, and deletes the uploaded file and vector store when the chat is closed.
For information on the OpenAI Responses API, refer to the following documents:
You can review and tailor agent instructions in the following file: AgentInstructions.cs.
Files to Review:
The following image displays the Web Document Viewer UI implementation described in this example. The AI Assistant tab uses a DevExtreme Chat (dxChat) component to display requests and responses:
On the BeforeRender event, add a new tab (a container for the assistant interface):
@model DevExpress.XtraReports.Web.WebDocumentViewer.WebDocumentViewerModel
@await Html.PartialAsync("_AILayout")
<script>
let aiTab;
async function BeforeRender(sender, args) {
const previewModel = args;
const reportPreview = previewModel.reportPreview;
aiTab = createAssistantTab();
const model = aiTab.model;
previewModel.tabPanel.tabs.push(aiTab);
// ...
}
</script>
@{
var viewerRender = Html.DevExpress().WebDocumentViewer("DocumentViewer")
.Height("100%")
.ClientSideEvents(configure => {
configure.BeforeRender("BeforeRender");
})
.Bind(Model);
@viewerRender.RenderHtml()
}
@* ... *@Once the document is ready, the DocumentReady event handler sends a request to the server and obtains the chat (session) id:
async function DocumentReady(sender, args) {
const response = await sender.PerformCustomDocumentOperation(null, true);
if (response.customData && aiTab?.model) {
aiTab.model.chatId = response.customData;
aiTab.visible = true;
}
}The PerformCustomDocumentOperation method exports the report to PDF and opens a chat that uses the exported document to answer user questions:
// ...
public override async Task<DocumentOperationResponse> PerformOperationAsync(DocumentOperationRequest request, PrintingSystemBase printingSystem, PrintingSystemBase printingSystemWithEditingFields) {
using(var stream = new MemoryStream()) {
printingSystem.ExportToPdf(stream, printingSystem.ExportOptions.Pdf);
var chatId = await chatService.OpenDocumentChatAsync(stream);
return new DocumentOperationResponse {
DocumentId = request.DocumentId,
CustomData = chatId,
Succeeded = true
};
}
}See the following files for implementation details:
Each time a user sends a message, the onMessageEntered event handler passes the request to the assistant:
//...
async function getAIResponse(instance, text, id) {
const formData = new FormData();
formData.append('text', text);
formData.append('chatId', id);
lastUserQuery = text;
return _tryFetch(instance, async () => {
const response = await fetch('/AI/GetAnswer', {
method: 'POST',
body: formData
});
if (!response.ok) {
_handleError(instance, { code: `${response.status}`, message: `Internal server error` });
return;
}
return await response.text();
}, 'GetAnswer');
}
// ...
function RenderAssistantMessage(instance, message) {
instance.option({ typingUsers: [] });
instance.renderMessage({ timestamp: new Date(), text: message, author: assistant.name, id: assistant.id });
}
// ...
onMessageEntered: async (e) => {
lastRefreshButton?.remove();
const instance = e.component;
instance.option('alerts', []);
instance.renderMessage(e.message);
instance.option({ typingUsers: [assistant] });
const userInput = e.message.text;
if (!assistant.id && model.chatId) {
assistant.id = model.chatId;
}
const response = await getAIResponse(instance, userInput, assistant.id);
RenderAssistantMessage(instance, response);
}
// ...AIController.GetAnswer receives answers from the assistant.
The following image displays the Web Report Designer UI implementation outlined in this example. The AI Assistant tab uses a DevExtreme Chat (dxChat) component to display requests and responses:
On the BeforeRender event, add a new tab (a container for the assistant interface):
@model DevExpress.XtraReports.Web.ReportDesigner.ReportDesignerModel
<script>
async function BeforeRender(sender, args) {
const result = await fetch(`/AI/CreateUserAssistant`);
const chatId = await result.text();
const tab = createAssistantTab(chatId);
args.tabPanel.tabs.push(tab);
}
</script>
@await Html.PartialAsync("_AILayout")
@{
var designerRender = Html.DevExpress().ReportDesigner("reportDesigner")
.Height("100%")
.ClientSideEvents(configure => {
configure.BeforeRender("BeforeRender");
})
.Bind(Model);
@designerRender.RenderHtml()
}
@section Scripts {
@* ... *@
<script src="~/js/aiIntegration.js"></script>
@designerRender.RenderScripts()
}
@* ... *@On the BeforeRender event, send a request to AIController to create the assistant:
async function BeforeRender(sender, args) {
const result = await fetch(`/AI/CreateUserAssistant`);
const chatId = await result.text();
// ...
}The AIController.CreateUserAssistant action calls AIReportingChatService.OpenDesignerChatAsync to read the documentation.pdf file (end-user documentation for Web Reporting Controls in PDF format) and creates a chat based on the specified instructions. See the AIReportingChatService.cs file to review implementation details.
Each time a user sends a message, the onMessageEntered event handler passes the request to the assistant:
//...
async function getAIResponse(instance, text, id) {
const formData = new FormData();
formData.append('text', text);
formData.append('chatId', id);
lastUserQuery = text;
return _tryFetch(instance, async () => {
const response = await fetch('/AI/GetAnswer', {
method: 'POST',
body: formData
});
if (!response.ok) {
_handleError(instance, { code: `${response.status}`, message: `Internal server error` });
return;
}
return await response.text();
}, 'GetAnswer');
}
// ...
function RenderAssistantMessage(instance, message) {
instance.option({ typingUsers: [] });
instance.renderMessage({ timestamp: new Date(), text: message, author: assistant.name, id: assistant.id });
}
// ...
onMessageEntered: async (e) => {
lastRefreshButton?.remove();
const instance = e.component;
instance.option('alerts', []);
instance.renderMessage(e.message);
instance.option({ typingUsers: [assistant] });
const userInput = e.message.text;
if (!assistant.id && model.chatId) {
assistant.id = model.chatId;
}
const response = await getAIResponse(instance, userInput, assistant.id);
RenderAssistantMessage(instance, response);
}
// ...AIController.GetAnswer receives answers from the specified assistant.
- DevExtreme Chat - Getting Started
- Reporting for ASP.NET Core - Summarize and Translate DevExpress Reports Using Azure OpenAI
- Reporting for Blazor - Integrate AI-powered Summarize and Translate Features based on Azure OpenAI
- AI Chat for Blazor - How to add DxAIChat component in Blazor, MAUI, WPF, and WinForms applications
- Rich Text Editor and HTML Editor for Blazor - How to integrate AI-powered extensions
(you will be redirected to DevExpress.com to submit your response)

