How to Add AI Agents to Existing .NET Applications

Tapesh Mehta Tapesh Mehta | Published on: Feb 11, 2026 | Est. reading time: 25 minutes
How to Add AI Agents to Existing .NET Applications

Table of Contents

Artificial intelligence is transforming how we build applications, and .NET developers now have powerful tools to integrate AI agents into their existing systems. This comprehensive guide walks you through adding intelligent, autonomous AI agents to your .NET applications, whether you’re working with ASP.NET Core, WPF, or any other .NET framework.

Understanding AI Agents in .NET Applications

AI agents are autonomous software entities that can perceive their environment, make decisions, and take actions to achieve specific goals. In the .NET ecosystem, AI agents typically leverage large language models (LLMs) like GPT-4, Claude, or open-source alternatives to understand natural language, reason about problems, and interact with your application’s components.

Unlike simple API calls to AI services, agents maintain context, can use tools and functions, plan multi-step operations, and adapt their behavior based on feedback. This makes them ideal for complex tasks like customer support, data analysis, content generation, and process automation.

Why Add AI Agents to Your .NET Application?

  • Enhanced User Experience: Provide intelligent, conversational interfaces that understand natural language queries and respond contextually.
  • Automation: Automate complex workflows that previously required human intervention, from data processing to customer service.
  • Scalability: Handle increasing volumes of user interactions without proportionally increasing support staff.
  • Business Intelligence: Extract insights from unstructured data, generate reports, and answer complex business questions.
  • Personalization: Create tailored experiences based on user preferences, history, and behavior patterns.

Prerequisites

Before we begin, ensure you have:

  • .NET 6.0 or later SDK installed
  • Visual Studio 2022 or Visual Studio Code
  • An Azure OpenAI account, OpenAI API key, or access to another LLM provider
  • Basic understanding of C#, async/await, and dependency injection
  • Familiarity with your existing .NET application’s architecture

Choosing Your AI Agent Framework

Several frameworks are available for building AI agents in .NET. Let’s examine the most popular options:

Option 1: Microsoft Semantic Kernel

Semantic Kernel is Microsoft’s lightweight SDK that integrates AI services with conventional programming languages. It’s the most mature option for .NET developers and offers excellent integration with Azure services.

dotnet add package Microsoft.SemanticKernel

Pros:

  • Native .NET implementation with excellent performance
  • Strong Microsoft support and active community
  • Built-in support for multiple AI providers
  • Plugin system for extending functionality

Cons:

  • Newer framework, still evolving rapidly
  • Documentation can lag behind features

Option 2: LangChain for .NET

LangChain has a .NET port that brings the popular Python framework’s capabilities to C# developers.

dotnet add package LangChain

Pros:

  • Extensive ecosystem of tools and integrations
  • Great for complex agent workflows
  • Strong community from Python version

Cons:

  • Port may not have feature parity with Python version
  • Less .NET-idiomatic API design

Option 3: Azure OpenAI with Custom Implementation

For maximum control, you can build your agent framework directly using Azure OpenAI SDK.

dotnet add package Azure.AI.OpenAI

Pros:

  • Complete control over implementation
  • No framework overhead
  • Easier to integrate with existing patterns

Cons:

  • More code to write and maintain
  • Need to implement agent patterns yourself

For this guide, we’ll focus on Microsoft Semantic Kernel as it offers the best balance of power, flexibility, and .NET integration.

Step-by-Step Implementation with Semantic Kernel

Step 1: Install Required NuGet Packages

Start by adding the necessary packages to your project:

dotnet add package Microsoft.SemanticKernel
dotnet add package Microsoft.SemanticKernel.Plugins.Core
dotnet add package Microsoft.Extensions.Configuration
dotnet add package Microsoft.Extensions.Configuration.UserSecrets

Step 2: Configure Your AI Service

First, set up your configuration in appsettings.json:

{
  "AzureOpenAI": {
    "Endpoint": "https://your-resource.openai.azure.com/",
    "DeploymentName": "gpt-4",
    "ApiKey": "your-api-key-here"
  },
  "OpenAI": {
    "ModelId": "gpt-4",
    "ApiKey": "your-openai-key-here"
  }
}

For production, use Azure Key Vault or environment variables instead of storing keys in configuration files.

Step 3: Create Your First AI Agent

Create a basic agent service:

using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.ChatCompletion;
using Microsoft.SemanticKernel.Connectors.OpenAI;

public class AIAgentService
{
    private readonly Kernel _kernel;
    private readonly IChatCompletionService _chatService;
    
    public AIAgentService(IConfiguration configuration)
    {
        var builder = Kernel.CreateBuilder();
        
        // Configure Azure OpenAI
        builder.AddAzureOpenAIChatCompletion(
            deploymentName: configuration["AzureOpenAI:DeploymentName"],
            endpoint: configuration["AzureOpenAI:Endpoint"],
            apiKey: configuration["AzureOpenAI:ApiKey"]
        );
        
        // Or configure OpenAI directly
        // builder.AddOpenAIChatCompletion(
        //     modelId: configuration["OpenAI:ModelId"],
        //     apiKey: configuration["OpenAI:ApiKey"]
        // );
        
        _kernel = builder.Build();
        _chatService = _kernel.GetRequiredService<IChatCompletionService>();
    }
    
    public async Task<string> GetResponseAsync(string userMessage)
    {
        var history = new ChatHistory();
        history.AddSystemMessage("You are a helpful AI assistant integrated into a .NET application.");
        history.AddUserMessage(userMessage);
        
        var response = await _chatService.GetChatMessageContentAsync(
            history,
            executionSettings: new OpenAIPromptExecutionSettings 
            { 
                MaxTokens = 500,
                Temperature = 0.7
            }
        );
        
        return response.Content;
    }
}

Step 4: Add Plugins for Extended Functionality

Plugins allow your AI agent to interact with your application’s data and services. Here’s how to create a custom plugin:

using System.ComponentModel;
using Microsoft.SemanticKernel;

public class OrderManagementPlugin
{
    private readonly IOrderRepository _orderRepository;
    
    public OrderManagementPlugin(IOrderRepository orderRepository)
    {
        _orderRepository = orderRepository;
    }
    
    [KernelFunction, Description("Get order details by order ID")]
    public async Task<string> GetOrderDetailsAsync(
        [Description("The order ID to look up")] string orderId)
    {
        var order = await _orderRepository.GetOrderByIdAsync(orderId);
        
        if (order == null)
            return $"Order {orderId} not found.";
        
        return $"Order {orderId}: Status={order.Status}, Total=${order.Total}, Items={order.Items.Count}";
    }
    
    [KernelFunction, Description("Update order status")]
    public async Task<string> UpdateOrderStatusAsync(
        [Description("The order ID")] string orderId,
        [Description("New status (Processing, Shipped, Delivered, Cancelled)")] string status)
    {
        var success = await _orderRepository.UpdateOrderStatusAsync(orderId, status);
        return success 
            ? $"Order {orderId} status updated to {status}" 
            : $"Failed to update order {orderId}";
    }
    
    [KernelFunction, Description("Get customer's recent orders")]
    public async Task<string> GetCustomerOrdersAsync(
        [Description("Customer email address")] string email,
        [Description("Number of recent orders to retrieve")] int count = 5)
    {
        var orders = await _orderRepository.GetCustomerOrdersAsync(email, count);
        
        if (!orders.Any())
            return $"No orders found for {email}";
        
        var orderSummary = string.Join("\n", 
            orders.Select(o => $"- Order {o.Id}: {o.Status} (${o.Total})"));
        
        return $"Recent orders for {email}:\n{orderSummary}";
    }
}

Register the plugin with your kernel:

public class AIAgentService
{
    private readonly Kernel _kernel;
    
    public AIAgentService(
        IConfiguration configuration, 
        IOrderRepository orderRepository)
    {
        var builder = Kernel.CreateBuilder();
        
        builder.AddAzureOpenAIChatCompletion(
            deploymentName: configuration["AzureOpenAI:DeploymentName"],
            endpoint: configuration["AzureOpenAI:Endpoint"],
            apiKey: configuration["AzureOpenAI:ApiKey"]
        );
        
        // Register plugins
        builder.Plugins.AddFromObject(
            new OrderManagementPlugin(orderRepository), 
            "OrderManagement"
        );
        
        _kernel = builder.Build();
    }
    
    public async Task<string> ProcessRequestAsync(string userMessage)
    {
        var history = new ChatHistory();
        history.AddSystemMessage(@"You are an AI assistant for an e-commerce platform. 
            You can help customers check order status, update orders, and answer questions.
            Be helpful, professional, and concise.");
        
        history.AddUserMessage(userMessage);
        
        // Enable automatic function calling
        var executionSettings = new OpenAIPromptExecutionSettings
        {
            ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions,
            MaxTokens = 500,
            Temperature = 0.7
        };
        
        var chatService = _kernel.GetRequiredService<IChatCompletionService>();
        var response = await chatService.GetChatMessageContentAsync(
            history,
            executionSettings: executionSettings,
            kernel: _kernel
        );
        
        return response.Content;
    }
}

Step 5: Integrate with Your Existing Application

Register the AI agent service in your dependency injection container:

// In Program.cs or Startup.cs
builder.Services.AddSingleton<IOrderRepository, OrderRepository>();
builder.Services.AddSingleton<AIAgentService>();

Now you can use the AI agent in your controllers, services, or any other component:

[ApiController]
[Route("api/[controller]")]
public class ChatController : ControllerBase
{
    private readonly AIAgentService _aiAgent;
    
    public ChatController(AIAgentService aiAgent)
    {
        _aiAgent = aiAgent;
    }
    
    [HttpPost]
    public async Task<IActionResult> SendMessage([FromBody] ChatRequest request)
    {
        try
        {
            var response = await _aiAgent.ProcessRequestAsync(request.Message);
            return Ok(new { response });
        }
        catch (Exception ex)
        {
            return StatusCode(500, new { error = ex.Message });
        }
    }
}

public class ChatRequest
{
    public string Message { get; set; }
}

Building a Customer Support AI Agent

Let’s build a more sophisticated customer support agent that can handle multiple types of requests and maintain conversation context.

Creating the Agent Service

public class CustomerSupportAgent
{
    private readonly Kernel _kernel;
    private readonly IChatCompletionService _chatService;
    private readonly ILogger<CustomerSupportAgent> _logger;
    private readonly Dictionary<string, ChatHistory> _conversationHistory;
    
    public CustomerSupportAgent(
        IConfiguration configuration,
        IOrderRepository orderRepository,
        ICustomerRepository customerRepository,
        IProductRepository productRepository,
        ILogger<CustomerSupportAgent> logger)
    {
        var builder = Kernel.CreateBuilder();
        
        builder.AddAzureOpenAIChatCompletion(
            deploymentName: configuration["AzureOpenAI:DeploymentName"],
            endpoint: configuration["AzureOpenAI:Endpoint"],
            apiKey: configuration["AzureOpenAI:ApiKey"]
        );
        
        // Add multiple plugins
        builder.Plugins.AddFromObject(new OrderManagementPlugin(orderRepository));
        builder.Plugins.AddFromObject(new CustomerServicePlugin(customerRepository));
        builder.Plugins.AddFromObject(new ProductCatalogPlugin(productRepository));
        
        _kernel = builder.Build();
        _chatService = _kernel.GetRequiredService<IChatCompletionService>();
        _logger = logger;
        _conversationHistory = new Dictionary<string, ChatHistory>();
    }
    
    public async Task<AgentResponse> ProcessMessageAsync(
        string sessionId, 
        string userMessage, 
        string customerEmail = null)
    {
        try
        {
            // Get or create conversation history
            if (!_conversationHistory.ContainsKey(sessionId))
            {
                var history = new ChatHistory();
                history.AddSystemMessage(GetSystemPrompt(customerEmail));
                _conversationHistory[sessionId] = history;
            }
            
            var chatHistory = _conversationHistory[sessionId];
            chatHistory.AddUserMessage(userMessage);
            
            _logger.LogInformation(
                "Processing message for session {SessionId}: {Message}", 
                sessionId, 
                userMessage
            );
            
            var executionSettings = new OpenAIPromptExecutionSettings
            {
                ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions,
                MaxTokens = 800,
                Temperature = 0.7,
                TopP = 0.95
            };
            
            var response = await _chatService.GetChatMessageContentAsync(
                chatHistory,
                executionSettings: executionSettings,
                kernel: _kernel
            );
            
            chatHistory.AddAssistantMessage(response.Content);
            
            return new AgentResponse
            {
                Message = response.Content,
                SessionId = sessionId,
                Timestamp = DateTime.UtcNow,
                TokensUsed = response.Metadata?["Usage"]?.ToString()
            };
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error processing message for session {SessionId}", sessionId);
            throw;
        }
    }
    
    private string GetSystemPrompt(string customerEmail)
    {
        return $@"You are a helpful customer support agent for TechStore, an e-commerce platform.
            
Your responsibilities:
- Answer questions about orders, products, and shipping
- Help customers track their orders
- Provide product recommendations
- Handle returns and refunds
- Escalate complex issues to human agents when necessary

{(string.IsNullOrEmpty(customerEmail) ? "" : $"Current customer: {customerEmail}")}

Guidelines:
- Be friendly, professional, and empathetic
- Keep responses concise but informative
- Use the available functions to look up accurate information
- If you cannot help with something, politely explain and offer alternatives
- Always confirm before making changes to orders
- Protect customer privacy and security";
    }
    
    public void ClearHistory(string sessionId)
    {
        _conversationHistory.Remove(sessionId);
    }
}

public class AgentResponse
{
    public string Message { get; set; }
    public string SessionId { get; set; }
    public DateTime Timestamp { get; set; }
    public string TokensUsed { get; set; }
}

Building Custom Plugins

Here’s a complete customer service plugin:

public class CustomerServicePlugin
{
    private readonly ICustomerRepository _customerRepository;
    
    public CustomerServicePlugin(ICustomerRepository customerRepository)
    {
        _customerRepository = customerRepository;
    }
    
    [KernelFunction, Description("Get customer profile information")]
    public async Task<string> GetCustomerProfileAsync(
        [Description("Customer email address")] string email)
    {
        var customer = await _customerRepository.GetByEmailAsync(email);
        
        if (customer == null)
            return $"Customer with email {email} not found.";
        
        return $@"Customer Profile:
- Name: {customer.FirstName} {customer.LastName}
- Email: {customer.Email}
- Member Since: {customer.CreatedDate:yyyy-MM-dd}
- Loyalty Tier: {customer.LoyaltyTier}
- Total Orders: {customer.TotalOrders}
- Lifetime Value: ${customer.LifetimeValue:N2}";
    }
    
    [KernelFunction, Description("Create a support ticket")]
    public async Task<string> CreateSupportTicketAsync(
        [Description("Customer email")] string email,
        [Description("Ticket subject")] string subject,
        [Description("Ticket description")] string description,
        [Description("Priority: Low, Medium, High, Urgent")] string priority = "Medium")
    {
        var ticket = new SupportTicket
        {
            CustomerEmail = email,
            Subject = subject,
            Description = description,
            Priority = priority,
            Status = "Open",
            CreatedDate = DateTime.UtcNow
        };
        
        var ticketId = await _customerRepository.CreateSupportTicketAsync(ticket);
        
        return $@"Support ticket created successfully:
- Ticket ID: {ticketId}
- Priority: {priority}
- Status: Open
A support representative will respond within 24 hours.";
    }
    
    [KernelFunction, Description("Check if customer is eligible for refund")]
    public async Task<string> CheckRefundEligibilityAsync(
        [Description("Order ID")] string orderId)
    {
        var eligibility = await _customerRepository.CheckRefundEligibilityAsync(orderId);
        
        return eligibility.IsEligible
            ? $"Order {orderId} is eligible for refund. Reason: {eligibility.Reason}"
            : $"Order {orderId} is not eligible for refund. Reason: {eligibility.Reason}";
    }
}

Implementing Chat Interface

Create a real-time chat controller with SignalR:

using Microsoft.AspNetCore.SignalR;

public class ChatHub : Hub
{
    private readonly CustomerSupportAgent _agent;
    private readonly ILogger<ChatHub> _logger;
    
    public ChatHub(CustomerSupportAgent agent, ILogger<ChatHub> logger)
    {
        _agent = agent;
        _logger = logger;
    }
    
    public async Task SendMessage(string message, string customerEmail = null)
    {
        var sessionId = Context.ConnectionId;
        
        try
        {
            // Send typing indicator
            await Clients.Caller.SendAsync("TypingIndicator", true);
            
            var response = await _agent.ProcessMessageAsync(
                sessionId, 
                message, 
                customerEmail
            );
            
            await Clients.Caller.SendAsync("TypingIndicator", false);
            await Clients.Caller.SendAsync("ReceiveMessage", response.Message);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error in chat hub");
            await Clients.Caller.SendAsync("TypingIndicator", false);
            await Clients.Caller.SendAsync(
                "Error", 
                "I apologize, but I encountered an error. Please try again."
            );
        }
    }
    
    public override async Task OnDisconnectedAsync(Exception exception)
    {
        _agent.ClearHistory(Context.ConnectionId);
        await base.OnDisconnectedAsync(exception);
    }
}

Advanced AI Agent Patterns

1. Multi-Agent Systems

Sometimes you need specialized agents for different tasks. Here’s how to orchestrate multiple agents:

public class MultiAgentOrchestrator
{
    private readonly Dictionary<string, Kernel> _agents;
    
    public MultiAgentOrchestrator(IConfiguration configuration)
    {
        _agents = new Dictionary<string, Kernel>
        {
            ["sales"] = CreateSalesAgent(configuration),
            ["technical"] = CreateTechnicalSupportAgent(configuration),
            ["billing"] = CreateBillingAgent(configuration)
        };
    }
    
    public async Task<string> RouteAndProcessAsync(string message, string context = null)
    {
        // Use a classifier agent to determine which specialized agent to use
        var agentType = await ClassifyRequestAsync(message);
        var agent = _agents[agentType];
        
        // Process with the appropriate specialized agent
        var chatService = agent.GetRequiredService<IChatCompletionService>();
        var history = new ChatHistory();
        history.AddUserMessage(message);
        
        var response = await chatService.GetChatMessageContentAsync(
            history,
            executionSettings: new OpenAIPromptExecutionSettings
            {
                ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions
            },
            kernel: agent
        );
        
        return response.Content;
    }
    
    private async Task<string> ClassifyRequestAsync(string message)
    {
        // Simple classification logic - in production, use a more sophisticated approach
        var lowerMessage = message.ToLower();
        
        if (lowerMessage.Contains("buy") || lowerMessage.Contains("purchase") || 
            lowerMessage.Contains("recommend"))
            return "sales";
        
        if (lowerMessage.Contains("invoice") || lowerMessage.Contains("payment") || 
            lowerMessage.Contains("refund"))
            return "billing";
        
        return "technical";
    }
}

2. Agent Memory and Context Management

Implement persistent memory for your agents using a database or cache:

public class AgentMemoryService
{
    private readonly IDistributedCache _cache;
    private readonly ILogger<AgentMemoryService> _logger;
    
    public AgentMemoryService(
        IDistributedCache cache, 
        ILogger<AgentMemoryService> logger)
    {
        _cache = cache;
        _logger = logger;
    }
    
    public async Task<ChatHistory> GetOrCreateHistoryAsync(string sessionId)
    {
        var cacheKey = $"chat_history_{sessionId}";
        var cachedHistory = await _cache.GetStringAsync(cacheKey);
        
        if (!string.IsNullOrEmpty(cachedHistory))
        {
            return JsonSerializer.Deserialize<ChatHistory>(cachedHistory);
        }
        
        return new ChatHistory();
    }
    
    public async Task SaveHistoryAsync(string sessionId, ChatHistory history)
    {
        var cacheKey = $"chat_history_{sessionId}";
        var options = new DistributedCacheEntryOptions
        {
            AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(24)
        };
        
        var serialized = JsonSerializer.Serialize(history);
        await _cache.SetStringAsync(cacheKey, serialized, options);
    }
    
    public async Task<UserContext> GetUserContextAsync(string userId)
    {
        var cacheKey = $"user_context_{userId}";
        var cached = await _cache.GetStringAsync(cacheKey);
        
        if (!string.IsNullOrEmpty(cached))
        {
            return JsonSerializer.Deserialize<UserContext>(cached);
        }
        
        return new UserContext { UserId = userId };
    }
    
    public async Task UpdateUserContextAsync(UserContext context)
    {
        var cacheKey = $"user_context_{context.UserId}";
        var options = new DistributedCacheEntryOptions
        {
            SlidingExpiration = TimeSpan.FromDays(30)
        };
        
        var serialized = JsonSerializer.Serialize(context);
        await _cache.SetStringAsync(cacheKey, serialized, options);
    }
}

public class UserContext
{
    public string UserId { get; set; }
    public string LastTopic { get; set; }
    public Dictionary<string, string> Preferences { get; set; } = new();
    public List<string> PreviousQuestions { get; set; } = new();
    public DateTime LastInteraction { get; set; }
}

3. Function Calling and Tool Use

Create sophisticated tools that your agent can use dynamically:

public class DataAnalysisPlugin
{
    private readonly IDataService _dataService;
    
    [KernelFunction, Description("Query database and return results")]
    public async Task<string> QueryDatabaseAsync(
        [Description("SQL query to execute (read-only)")] string query)
    {
        // Validate query is read-only
        if (!IsReadOnlyQuery(query))
            return "Error: Only SELECT queries are allowed";
        
        var results = await _dataService.ExecuteQueryAsync(query);
        return FormatQueryResults(results);
    }
    
    [KernelFunction, Description("Generate data visualization")]
    public async Task<string> CreateVisualizationAsync(
        [Description("Data to visualize in JSON format")] string jsonData,
        [Description("Chart type: bar, line, pie, scatter")] string chartType)
    {
        var chartUrl = await _dataService.GenerateChartAsync(jsonData, chartType);
        return $"Chart created: {chartUrl}";
    }
    
    [KernelFunction, Description("Perform statistical analysis")]
    public async Task<string> AnalyzeDataAsync(
        [Description("Dataset identifier")] string datasetId,
        [Description("Analysis type: summary, correlation, regression")] string analysisType)
    {
        var analysis = await _dataService.PerformAnalysisAsync(datasetId, analysisType);
        return FormatAnalysisResults(analysis);
    }
    
    private bool IsReadOnlyQuery(string query)
    {
        var normalizedQuery = query.Trim().ToLower();
        return normalizedQuery.StartsWith("select") && 
               !normalizedQuery.Contains("into") &&
               !normalizedQuery.Contains("update") &&
               !normalizedQuery.Contains("delete") &&
               !normalizedQuery.Contains("insert") &&
               !normalizedQuery.Contains("drop") &&
               !normalizedQuery.Contains("alter");
    }
}

Integrating with Existing .NET Architectures

ASP.NET Core Web API Integration

// Startup configuration
public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers();
    services.AddSignalR();
    
    // Add distributed cache for conversation history
    services.AddStackExchangeRedisCache(options =>
    {
        options.Configuration = Configuration["Redis:ConnectionString"];
    });
    
    // Register AI services
    services.AddSingleton<CustomerSupportAgent>();
    services.AddSingleton<AgentMemoryService>();
    
    // Add CORS for frontend
    services.AddCors(options =>
    {
        options.AddDefaultPolicy(builder =>
        {
            builder.WithOrigins("https://yourdomain.com")
                   .AllowAnyMethod()
                   .AllowAnyHeader()
                   .AllowCredentials();
        });
    });
}

public void Configure(IApplicationBuilder app)
{
    app.UseRouting();
    app.UseCors();
    
    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllers();
        endpoints.MapHub<ChatHub>("/chatHub");
    });
}

Blazor Integration

@page "/chat"
@using Microsoft.AspNetCore.SignalR.Client
@inject NavigationManager Navigation
@inject CustomerSupportAgent Agent
@implements IAsyncDisposable

<div class="chat-container">
    <div class="messages">
        @foreach (var message in messages)
        {
            <div class="message @message.Sender.ToLower()">
                <div class="content">@message.Text</div>
                <div class="timestamp">@message.Timestamp.ToString("HH:mm")</div>
            </div>
        }
        
        @if (isTyping)
        {
            <div class="typing-indicator">AI is typing...</div>
        }
    </div>
    
    <div class="input-area">
        <input @bind="currentMessage" 
               @onkeydown="HandleKeyPress" 
               placeholder="Type your message..." />
        <button @onclick="SendMessage">Send</button>
    </div>
</div>

@code {
    private List<ChatMessage> messages = new();
    private string currentMessage = "";
    private bool isTyping = false;
    private HubConnection hubConnection;
    
    protected override async Task OnInitializedAsync()
    {
        hubConnection = new HubConnectionBuilder()
            .WithUrl(Navigation.ToAbsoluteUri("/chatHub"))
            .Build();
        
        hubConnection.On<string>("ReceiveMessage", (message) =>
        {
            messages.Add(new ChatMessage 
            { 
                Sender = "AI", 
                Text = message, 
                Timestamp = DateTime.Now 
            });
            isTyping = false;
            StateHasChanged();
        });
        
        hubConnection.On<bool>("TypingIndicator", (typing) =>
        {
            isTyping = typing;
            StateHasChanged();
        });
        
        await hubConnection.StartAsync();
    }
    
    private async Task SendMessage()
    {
        if (string.IsNullOrWhiteSpace(currentMessage))
            return;
        
        messages.Add(new ChatMessage 
        { 
            Sender = "User", 
            Text = currentMessage, 
            Timestamp = DateTime.Now 
        });
        
        await hubConnection.SendAsync("SendMessage", currentMessage);
        currentMessage = "";
    }
    
    private async Task HandleKeyPress(KeyboardEventArgs e)
    {
        if (e.Key == "Enter")
            await SendMessage();
    }
    
    public async ValueTask DisposeAsync()
    {
        if (hubConnection is not null)
            await hubConnection.DisposeAsync();
    }
    
    private class ChatMessage
    {
        public string Sender { get; set; }
        public string Text { get; set; }
        public DateTime Timestamp { get; set; }
    }
}

WPF Desktop Application Integration

public class ChatViewModel : INotifyPropertyChanged
{
    private readonly CustomerSupportAgent _agent;
    private ObservableCollection<ChatMessage> _messages;
    private string _currentMessage;
    private bool _isProcessing;
    
    public ChatViewModel()
    {
        var configuration = new ConfigurationBuilder()
            .AddJsonFile("appsettings.json")
            .Build();
        
        var orderRepository = new OrderRepository();
        var logger = LoggerFactory.Create(builder => 
            builder.AddConsole()).CreateLogger<CustomerSupportAgent>();
        
        _agent = new CustomerSupportAgent(
            configuration, 
            orderRepository,
            new CustomerRepository(),
            new ProductRepository(),
            logger
        );
        
        Messages = new ObservableCollection<ChatMessage>();
        SendMessageCommand = new RelayCommand(async () => await SendMessageAsync());
    }
    
    public ObservableCollection<ChatMessage> Messages
    {
        get => _messages;
        set
        {
            _messages = value;
            OnPropertyChanged();
        }
    }
    
    public string CurrentMessage
    {
        get => _currentMessage;
        set
        {
            _currentMessage = value;
            OnPropertyChanged();
        }
    }
    
    public bool IsProcessing
    {
        get => _isProcessing;
        set
        {
            _isProcessing = value;
            OnPropertyChanged();
        }
    }
    
    public ICommand SendMessageCommand { get; }
    
    private async Task SendMessageAsync()
    {
        if (string.IsNullOrWhiteSpace(CurrentMessage) || IsProcessing)
            return;
        
        var userMessage = CurrentMessage;
        CurrentMessage = "";
        
        Messages.Add(new ChatMessage
        {
            Sender = "You",
            Text = userMessage,
            Timestamp = DateTime.Now
        });
        
        IsProcessing = true;
        
        try
        {
            var sessionId = "desktop-session";
            var response = await _agent.ProcessMessageAsync(sessionId, userMessage);
            
            Messages.Add(new ChatMessage
            {
                Sender = "AI Assistant",
                Text = response.Message,
                Timestamp = DateTime.Now
            });
        }
        catch (Exception ex)
        {
            Messages.Add(new ChatMessage
            {
                Sender = "System",
                Text = $"Error: {ex.Message}",
                Timestamp = DateTime.Now
            });
        }
        finally
        {
            IsProcessing = false;
        }
    }
    
    public event PropertyChangedEventHandler PropertyChanged;
    
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

Security and Best Practices

API Key Management

Never hardcode API keys. Use Azure Key Vault or environment variables:

public class SecureConfigurationService
{
    private readonly SecretClient _secretClient;
    
    public SecureConfigurationService(IConfiguration configuration)
    {
        var keyVaultUrl = configuration["KeyVault:Url"];
        _secretClient = new SecretClient(
            new Uri(keyVaultUrl), 
            new DefaultAzureCredential()
        );
    }
    
    public async Task<string> GetOpenAIKeyAsync()
    {
        var secret = await _secretClient.GetSecretAsync("OpenAI-ApiKey");
        return secret.Value.Value;
    }
}

Input Validation and Sanitization

public class InputValidator
{
    private static readonly int MaxMessageLength = 2000;
    private static readonly HashSet<string> ProhibitedPatterns = new()
    {
        "DROP TABLE", "DELETE FROM", "UPDATE SET", "INSERT INTO"
    };
    
    public static ValidationResult ValidateUserInput(string input)
    {
        if (string.IsNullOrWhiteSpace(input))
            return ValidationResult.Error("Input cannot be empty");
        
        if (input.Length > MaxMessageLength)
            return ValidationResult.Error($"Input exceeds maximum length of {MaxMessageLength}");
        
        var upperInput = input.ToUpper();
        foreach (var pattern in ProhibitedPatterns)
        {
            if (upperInput.Contains(pattern))
                return ValidationResult.Error("Input contains prohibited content");
        }
        
        // Check for potential prompt injection
        if (ContainsPotentialInjection(input))
            return ValidationResult.Warning("Input may contain prompt injection attempt");
        
        return ValidationResult.Success();
    }
    
    private static bool ContainsPotentialInjection(string input)
    {
        var injectionPatterns = new[]
        {
            "ignore previous instructions",
            "disregard above",
            "forget everything",
            "new instructions:",
            "system:",
            "role:"
        };
        
        var lowerInput = input.ToLower();
        return injectionPatterns.Any(pattern => lowerInput.Contains(pattern));
    }
}

public class ValidationResult
{
    public bool IsValid { get; set; }
    public string Message { get; set; }
    public ValidationLevel Level { get; set; }
    
    public static ValidationResult Success() => 
        new() { IsValid = true, Level = ValidationLevel.Success };
    
    public static ValidationResult Warning(string message) => 
        new() { IsValid = true, Message = message, Level = ValidationLevel.Warning };
    
    public static ValidationResult Error(string message) => 
        new() { IsValid = false, Message = message, Level = ValidationLevel.Error };
}

public enum ValidationLevel
{
    Success,
    Warning,
    Error
}

Rate Limiting and Cost Control

public class RateLimitingService
{
    private readonly IDistributedCache _cache;
    private readonly ILogger<RateLimitingService> _logger;
    
    private const int MaxRequestsPerMinute = 20;
    private const int MaxTokensPerDay = 100000;
    
    public async Task<bool> CheckRateLimitAsync(string userId)
    {
        var minuteKey = $"rate_limit_minute_{userId}";
        var dayKey = $"rate_limit_day_{userId}";
        
        // Check per-minute limit
        var minuteCount = await GetCountAsync(minuteKey);
        if (minuteCount >= MaxRequestsPerMinute)
        {
            _logger.LogWarning("Rate limit exceeded for user {UserId}", userId);
            return false;
        }
        
        await IncrementCountAsync(minuteKey, TimeSpan.FromMinutes(1));
        return true;
    }
    
    public async Task<bool> CheckTokenLimitAsync(string userId, int tokensUsed)
    {
        var dayKey = $"token_usage_day_{userId}";
        var currentUsage = await GetCountAsync(dayKey);
        
        if (currentUsage + tokensUsed > MaxTokensPerDay)
        {
            _logger.LogWarning(
                "Daily token limit would be exceeded for user {UserId}", 
                userId
            );
            return false;
        }
        
        await IncrementCountAsync(dayKey, TimeSpan.FromDays(1), tokensUsed);
        return true;
    }
    
    private async Task<int> GetCountAsync(string key)
    {
        var cached = await _cache.GetStringAsync(key);
        return int.TryParse(cached, out var count) ? count : 0;
    }
    
    private async Task IncrementCountAsync(string key, TimeSpan expiry, int amount = 1)
    {
        var current = await GetCountAsync(key);
        var newValue = current + amount;
        
        var options = new DistributedCacheEntryOptions
        {
            AbsoluteExpirationRelativeToNow = expiry
        };
        
        await _cache.SetStringAsync(key, newValue.ToString(), options);
    }
}

Monitoring and Logging

public class AgentTelemetryService
{
    private readonly ILogger<AgentTelemetryService> _logger;
    private readonly TelemetryClient _telemetryClient;
    
    public void TrackAgentInteraction(AgentInteractionMetrics metrics)
    {
        _logger.LogInformation(
            "Agent interaction: SessionId={SessionId}, Duration={Duration}ms, " +
            "TokensUsed={TokensUsed}, ToolsCalled={ToolsCalled}",
            metrics.SessionId,
            metrics.Duration.TotalMilliseconds,
            metrics.TokensUsed,
            metrics.ToolsCalled.Count
        );
        
        // Send to Application Insights
        _telemetryClient.TrackEvent("AgentInteraction", new Dictionary<string, string>
        {
            ["SessionId"] = metrics.SessionId,
            ["UserMessage"] = metrics.UserMessage,
            ["ResponseLength"] = metrics.ResponseLength.ToString(),
            ["Success"] = metrics.Success.ToString()
        },
        new Dictionary<string, double>
        {
            ["Duration"] = metrics.Duration.TotalMilliseconds,
            ["TokensUsed"] = metrics.TokensUsed,
            ["ToolCallCount"] = metrics.ToolsCalled.Count
        });
    }
    
    public void TrackError(Exception exception, string sessionId, string context)
    {
        _logger.LogError(
            exception,
            "Agent error in session {SessionId}: {Context}",
            sessionId,
            context
        );
        
        _telemetryClient.TrackException(exception, new Dictionary<string, string>
        {
            ["SessionId"] = sessionId,
            ["Context"] = context
        });
    }
}

public class AgentInteractionMetrics
{
    public string SessionId { get; set; }
    public string UserMessage { get; set; }
    public TimeSpan Duration { get; set; }
    public int TokensUsed { get; set; }
    public int ResponseLength { get; set; }
    public List<string> ToolsCalled { get; set; } = new();
    public bool Success { get; set; }
}

Testing AI Agents

Unit Testing

public class CustomerSupportAgentTests
{
    [Fact]
    public async Task ProcessMessage_WithValidInput_ReturnsResponse()
    {
        // Arrange
        var mockConfig = new Mock<IConfiguration>();
        var mockOrderRepo = new Mock<IOrderRepository>();
        var mockLogger = new Mock<ILogger<CustomerSupportAgent>>();
        
        mockConfig.Setup(c => c["AzureOpenAI:Endpoint"])
            .Returns("https://test.openai.azure.com/");
        mockConfig.Setup(c => c["AzureOpenAI:DeploymentName"])
            .Returns("gpt-4");
        mockConfig.Setup(c => c["AzureOpenAI:ApiKey"])
            .Returns("test-key");
        
        var agent = new CustomerSupportAgent(
            mockConfig.Object,
            mockOrderRepo.Object,
            Mock.Of<ICustomerRepository>(),
            Mock.Of<IProductRepository>(),
            mockLogger.Object
        );
        
        // Act
        var response = await agent.ProcessMessageAsync(
            "test-session",
            "What is your return policy?"
        );
        
        // Assert
        Assert.NotNull(response);
        Assert.NotEmpty(response.Message);
        Assert.Equal("test-session", response.SessionId);
    }
    
    [Theory]
    [InlineData("")]
    [InlineData(null)]
    [InlineData("   ")]
    public async Task ProcessMessage_WithInvalidInput_ThrowsException(string input)
    {
        // Arrange
        var agent = CreateTestAgent();
        
        // Act & Assert
        await Assert.ThrowsAsync<ArgumentException>(() => 
            agent.ProcessMessageAsync("session", input)
        );
    }
}

Integration Testing

public class AgentIntegrationTests : IClassFixture<WebApplicationFactory<Program>>
{
    private readonly WebApplicationFactory<Program> _factory;
    
    public AgentIntegrationTests(WebApplicationFactory<Program> factory)
    {
        _factory = factory;
    }
    
    [Fact]
    public async Task ChatEndpoint_ProcessesMessage_ReturnsValidResponse()
    {
        // Arrange
        var client = _factory.CreateClient();
        var request = new ChatRequest { Message = "Hello, I need help" };
        
        // Act
        var response = await client.PostAsJsonAsync("/api/chat", request);
        
        // Assert
        response.EnsureSuccessStatusCode();
        var content = await response.Content.ReadFromJsonAsync<ChatResponse>();
        Assert.NotNull(content);
        Assert.NotEmpty(content.Response);
    }
    
    [Fact]
    public async Task OrderLookup_WithValidOrderId_ReturnsOrderDetails()
    {
        // Arrange
        var client = _factory.CreateClient();
        var request = new ChatRequest 
        { 
            Message = "Can you check the status of order ORD-12345?" 
        };
        
        // Act
        var response = await client.PostAsJsonAsync("/api/chat", request);
        var content = await response.Content.ReadFromJsonAsync<ChatResponse>();
        
        // Assert
        Assert.Contains("ORD-12345", content.Response);
        Assert.Contains("status", content.Response.ToLower());
    }
}

Performance Optimization

Caching Strategies

public class CachedAgentService
{
    private readonly IMemoryCache _memoryCache;
    private readonly CustomerSupportAgent _agent;
    
    public async Task<string> GetResponseWithCacheAsync(string query)
    {
        var cacheKey = $"agent_response_{ComputeHash(query)}";
        
        if (_memoryCache.TryGetValue(cacheKey, out string cachedResponse))
        {
            return cachedResponse;
        }
        
        var response = await _agent.ProcessMessageAsync("cache-session", query);
        
        var cacheOptions = new MemoryCacheEntryOptions
        {
            AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1),
            SlidingExpiration = TimeSpan.FromMinutes(15)
        };
        
        _memoryCache.Set(cacheKey, response.Message, cacheOptions);
        return response.Message;
    }
    
    private string ComputeHash(string input)
    {
        using var sha256 = SHA256.Create();
        var bytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(input));
        return Convert.ToBase64String(bytes);
    }
}

Async/Await Best Practices

public class OptimizedAgentService
{
    public async Task<List<AgentResponse>> ProcessMultipleMessagesAsync(
        List<string> messages)
    {
        // Process messages in parallel for better performance
        var tasks = messages.Select(async message =>
        {
            return await ProcessSingleMessageAsync(message);
        });
        
        var responses = await Task.WhenAll(tasks);
        return responses.ToList();
    }
    
    public async Task<AgentResponse> ProcessWithTimeoutAsync(
        string message,
        TimeSpan timeout)
    {
        using var cts = new CancellationTokenSource(timeout);
        
        try
        {
            var task = ProcessSingleMessageAsync(message);
            return await task.WaitAsync(cts.Token);
        }
        catch (OperationCanceledException)
        {
            return new AgentResponse
            {
                Message = "Request timed out. Please try again.",
                Success = false
            };
        }
    }
}

Batch Processing

public class BatchProcessingService
{
    private readonly CustomerSupportAgent _agent;
    private readonly ILogger<BatchProcessingService> _logger;
    
    public async Task<BatchProcessingResult> ProcessBatchAsync(
        List<BatchRequest> requests,
        int maxConcurrency = 5)
    {
        var semaphore = new SemaphoreSlim(maxConcurrency);
        var results = new ConcurrentBag<AgentResponse>();
        var errors = new ConcurrentBag<BatchError>();
        
        var tasks = requests.Select(async request =>
        {
            await semaphore.WaitAsync();
            try
            {
                var response = await _agent.ProcessMessageAsync(
                    request.SessionId,
                    request.Message
                );
                results.Add(response);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error processing batch request");
                errors.Add(new BatchError
                {
                    RequestId = request.Id,
                    Error = ex.Message
                });
            }
            finally
            {
                semaphore.Release();
            }
        });
        
        await Task.WhenAll(tasks);
        
        return new BatchProcessingResult
        {
            SuccessCount = results.Count,
            ErrorCount = errors.Count,
            Responses = results.ToList(),
            Errors = errors.ToList()
        };
    }
}

Real-World Use Cases

1. Intelligent Document Processing

public class DocumentProcessingAgent
{
    private readonly Kernel _kernel;
    
    [KernelFunction, Description("Extract structured data from invoice")]
    public async Task<string> ExtractInvoiceDataAsync(
        [Description("Invoice text content")] string invoiceText)
    {
        var prompt = $@"Extract the following information from this invoice:
- Invoice Number
- Date
- Vendor Name
- Total Amount
- Line Items (description, quantity, price)

Invoice:
{invoiceText}

Return the data in JSON format.";
        
        var response = await _kernel.InvokePromptAsync(prompt);
        return response.GetValue<string>();
    }
}

2. Automated Code Review Assistant

public class CodeReviewAgent
{
    [KernelFunction, Description("Review code for issues")]
    public async Task<string> ReviewCodeAsync(
        [Description("Code to review")] string code,
        [Description("Programming language")] string language)
    {
        var prompt = $@"Review this {language} code for:
1. Potential bugs
2. Security vulnerabilities
3. Performance issues
4. Code style violations
5. Best practice recommendations

Code:
```{language}
{code}
```

Provide specific, actionable feedback.";
        
        var response = await _kernel.InvokePromptAsync(prompt);
        return response.GetValue<string>();
    }
}

3. Data Analysis and Reporting

public class DataAnalysisAgent
{
    [KernelFunction, Description("Analyze sales data and generate insights")]
    public async Task<string> AnalyzeSalesDataAsync(
        [Description("Sales data in JSON format")] string salesData,
        [Description("Time period")] string period)
    {
        var prompt = $@"Analyze this sales data for {period} and provide:
1. Key trends and patterns
2. Top performing products
3. Underperforming areas
4. Actionable recommendations
5. Forecasts for next period

Data:
{salesData}";
        
        var response = await _kernel.InvokePromptAsync(prompt);
        return response.GetValue<string>();
    }
}

Common Challenges and Solutions

Challenge 1: Managing Conversation Context

Problem: Conversations can exceed token limits, causing errors or context loss.

Solution: Implement conversation summarization:

public class ConversationManager
{
    private const int MaxHistoryLength = 10;
    
    public async Task<ChatHistory> ManageHistoryAsync(ChatHistory history)
    {
        if (history.Count <= MaxHistoryLength)
            return history;
        
        // Summarize old messages
        var oldMessages = history.Take(history.Count - MaxHistoryLength);
        var summary = await SummarizeMessagesAsync(oldMessages);
        
        var newHistory = new ChatHistory();
        newHistory.AddSystemMessage($"Previous conversation summary: {summary}");
        
        // Add recent messages
        foreach (var message in history.Skip(history.Count - MaxHistoryLength))
        {
            newHistory.Add(message);
        }
        
        return newHistory;
    }
}

Challenge 2: Handling API Failures

Problem: API calls can fail due to network issues, rate limits, or service outages.

Solution: Implement retry logic with exponential backoff:

public class ResilientAgentService
{
    private readonly IAsyncPolicy<AgentResponse> _retryPolicy;
    
    public ResilientAgentService()
    {
        _retryPolicy = Policy<AgentResponse>
            .Handle<HttpRequestException>()
            .Or<TimeoutException>()
            .WaitAndRetryAsync(
                3,
                retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
                onRetry: (outcome, timespan, retryCount, context) =>
                {
                    _logger.LogWarning(
                        "Retry {RetryCount} after {Delay}ms due to: {Exception}",
                        retryCount,
                        timespan.TotalMilliseconds,
                        outcome.Exception?.Message
                    );
                }
            );
    }
    
    public async Task<AgentResponse> ProcessWithRetryAsync(string message)
    {
        return await _retryPolicy.ExecuteAsync(async () =>
        {
            return await _agent.ProcessMessageAsync("session", message);
        });
    }
}

Challenge 3: Prompt Engineering

Problem: Getting consistent, high-quality responses from the AI.

Solution: Create a prompt template system:

public class PromptTemplateService
{
    private readonly Dictionary<string, string> _templates = new()
    {
        ["customer_support"] = @"You are a professional customer support agent.
Guidelines:
- Be helpful and empathetic
- Provide accurate information
- Use available tools to look up data
- Escalate to human when necessary
- Keep responses under 200 words

Customer context: {customerInfo}
Previous interactions: {history}

Customer message: {message}",
        
        ["technical_support"] = @"You are a technical support specialist.
Your role:
- Diagnose technical issues
- Provide step-by-step solutions
- Reference documentation when available
- Collect necessary troubleshooting info

Issue context: {context}
User message: {message}"
    };
    
    public string BuildPrompt(string templateName, Dictionary<string, string> variables)
    {
        if (!_templates.ContainsKey(templateName))
            throw new ArgumentException($"Template {templateName} not found");
        
        var template = _templates[templateName];
        
        foreach (var variable in variables)
        {
            template = template.Replace($"{{{variable.Key}}}", variable.Value);
        }
        
        return template;
    }
}

Conclusion

Adding AI agents to your .NET applications opens up powerful possibilities for automation, user experience enhancement, and intelligent decision-making. By following the patterns and practices outlined in this guide, you can successfully integrate AI capabilities while maintaining security, performance, and reliability.

Key takeaways:

  • Choose the right framework for your needs (Semantic Kernel recommended for .NET)
  • Design plugins thoughtfully to extend agent capabilities
  • Implement proper security measures including input validation and API key management
  • Use caching and async patterns for optimal performance
  • Monitor and log agent interactions for debugging and improvement
  • Test thoroughly, including edge cases and failure scenarios
  • Start with simple use cases and gradually increase complexity

The field of AI agents is rapidly evolving, with new capabilities and patterns emerging regularly. Stay updated with the latest developments in Microsoft Semantic Kernel and the broader AI ecosystem to continue enhancing your applications with cutting-edge AI features.

Ready to take your .NET applications to the next level with AI? Start by implementing a simple agent for one use case, measure its impact, and expand from there. The investment in learning these patterns will pay dividends as AI becomes increasingly central to modern application development.

Share

clutch profile designrush wirefuture profile goodfirms wirefuture profile
Precision-Crafted Software for Every Need! 🔍

WireFuture stands for precision in every line of code. Whether you're a startup or an established enterprise, our bespoke software solutions are tailored to fit your exact requirements.

Hire Now

Categories
.NET Development Angular Development JavaScript Development KnockoutJS Development NodeJS Development PHP Development Python Development React Development Software Development SQL Server Development VueJS Development All
About Author
wirefuture - founder

Tapesh Mehta

verified Verified
Expert in Software Development

Tapesh Mehta is a seasoned tech worker who has been making apps for the web, mobile devices, and desktop for over 15+ years. Tapesh knows a lot of different computer languages and frameworks. For robust web solutions, he is an expert in Asp.Net, PHP, and Python. He is also very good at making hybrid mobile apps, which use Ionic, Xamarin, and Flutter to make cross-platform user experiences that work well together. In addition, Tapesh has a lot of experience making complex desktop apps with WPF, which shows how flexible and creative he is when it comes to making software. His work is marked by a constant desire to learn and change.

Get in Touch
Your Ideas, Our Strategy – Let's Connect.

No commitment required. Whether you’re a charity, business, start-up or you just have an idea – we’re happy to talk through your project.

Embrace a worry-free experience as we proactively update, secure, and optimize your software, enabling you to focus on what matters most – driving innovation and achieving your business goals.

Hire Your A-Team Here to Unlock Potential & Drive Results
You can send an email to contact@wirefuture.com
clutch wirefuture profile designrush wirefuture profile goodfirms wirefuture profile good firms award-4 award-5 award-6