Integrating OpenAI with .NET 8 for Optimizely Solutions

The integration of Generative AI (GenAI) is rapidly moving from a niche experiment to a core business necessity. For developers working with robust platforms like Optimizely CMS 12 and Commerce 14, leveraging AI—such as OpenAI or Azure OpenAI services—can unlock massive efficiencies in content creation, personalization, and product catalog management.

This article provides a step-by-step guide on how to cleanly integrate AI services into your existing .NET 8 application structure, ensuring scalability and maintainability, specifically within the context of an Optimizely project.

1. Configuring the .NET 8 AI Service Foundation

Security and configuration are paramount when dealing with external API keys. We will use .NET 8's standard configuration system and the IHttpClientFactory pattern for robust API communication.

1.1 Configuration Setup

Define your API settings in appsettings.json. We recommend using environment variables or Azure Key Vault for production secrets, but this structure defines the binding target:

{
  "OpenAISettings": {
    "ApiKey": "sk-YOUR_SECRET_API_KEY",
    "ModelName": "gpt-4o-mini",
    "BaseUrl": "https://api.openai.com/v1/chat/completions"
  }
}

Create a corresponding settings class in MyProject.Model/Settings/OpenAISettings.cs:

namespace MyProject.Model.Settings
{
    public class OpenAISettings
    {
        public const string Position = "OpenAISettings";
        public string ApiKey { get; set; } = string.Empty;
        public string ModelName { get; set; } = string.Empty;
        public string BaseUrl { get; set; } = string.Empty;
    }
}

1.2 Registering Services in Program.cs

We register the settings and configure a named HttpClient to handle the necessary Authorization headers using the configuration values.

// File: MyProject.Web/Program.cs

// ... (Existing Optimizely registrations)

builder.Services.Configure<OpenAISettings>(
    builder.Configuration.GetSection(OpenAISettings.Position));

// Register IHttpClientFactory for OpenAI
builder.Services.AddHttpClient("OpenAI", (serviceProvider, client) =>
{
    var settings = serviceProvider.GetRequiredService<IOptions<OpenAISettings>>().Value;
    
    client.BaseAddress = new Uri(settings.BaseUrl.Substring(0, settings.BaseUrl.LastIndexOf('/')));
    
    // Set required API key header
    client.DefaultRequestHeaders.Authorization = 
        new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", settings.ApiKey);
});

// Register the AI Service
builder.Services.AddSingleton<IAIService, OpenAIService>();

var app = builder.Build();
// ...

2. Building the Generative AI Service Layer

The core logic resides in a dedicated service that manages the request and response serialization. We will use System.Text.Json for modern JSON handling.

2.1 Defining the AI Service Contract

Define the interface for dependency injection (MyProject.Model/Services/IAIService.cs):

namespace MyProject.Model.Services
{
    public interface IAIService
    {
        Task<string> GenerateMarketingContentAsync(string inputTopic, int wordCount);
    }
}

2.2 Implementing the OpenAI Service

This service handles the construction of the standardized OpenAI chat request payload.

using System.Text.Json;
using Microsoft.Extensions.Options;
using MyProject.Model.Settings;

namespace MyProject.Model.Services
{
    public class OpenAIService : IAIService
    {
        private readonly HttpClient _httpClient;
        private readonly OpenAISettings _settings;

        public OpenAIService(IHttpClientFactory httpClientFactory, IOptions<OpenAISettings> options)
        {
            _httpClient = httpClientFactory.CreateClient("OpenAI");
            _settings = options.Value;
        }

        public async Task<string> GenerateMarketingContentAsync(string inputTopic, int wordCount)
        {
            var systemPrompt = $@"You are an expert marketing copywriter for an e-commerce platform. 
                                  Generate a concise, SEO-friendly marketing description, exactly {wordCount} words long. 
                                  The output must only contain the generated text, nothing else.";
            
            var requestPayload = new
            {
                model = _settings.ModelName,
                messages = new[]
                {
                    new { role = "system", content = systemPrompt },
                    new { role = "user", content = inputTopic }
                },
                temperature = 0.7 
            };

            var jsonContent = JsonSerializer.Serialize(requestPayload);
            var content = new StringContent(jsonContent, System.Text.Encoding.UTF8, "application/json");

            var response = await _httpClient.PostAsync("chat/completions", content);

            if (!response.IsSuccessStatusCode)
            {
                // Log detailed error response here
                throw new HttpRequestException($"OpenAI API call failed: {response.StatusCode}");
            }

            var responseJson = await response.Content.ReadAsStringAsync();
            
            // Simple deserialization structure (assuming a direct text response)
            using (JsonDocument document = JsonDocument.Parse(responseJson))
            {
                var choice = document.RootElement
                                     .GetProperty("choices")[0]
                                     .GetProperty("message")
                                     .GetProperty("content")
                                     .GetString();
                return choice?.Trim() ?? "Failed to generate content.";
            }
        }
    }
}

3. Integrating AI into Optimizely Editors

Once the service is built, Optimizely developers can inject IAIService directly into Controllers, Initialization Modules, or custom admin tools used for editing Content Blocks or Commerce Catalog items.

For example, using it within an existing Content Controller to provide a suggestion based on the page name:

// File: MyProject.Web/Controllers/StandardPageController.cs (Conceptual Example)
public class StandardPageController : PageControllerBase<StandardPage>
{
    private readonly IAIService _aiService;

    public StandardPageController(IAIService aiService)
    {
        _aiService = aiService;
    }

    [HttpGet]
    public async Task<ActionResult> Index(StandardPage currentPage)
    {
        // Example usage: Generating a content summary 
        if (currentPage.ContentSummary == null)
        {
            var topic = $"A marketing summary for the page titled '{currentPage.Name}' which focuses on {currentPage.MainBody}";
            var generatedSummary = await _aiService.GenerateMarketingContentAsync(topic, 30);
            
            // Note: In a real Optimizely scenario, this should be done in an Edit Mode/Admin action, 
            // not during front-end rendering. This illustrates service consumption.
        }

        var model = new StandardPageModel(currentPage);
        return View(model);
    }
}

4. Troubleshooting Common Integration Issues

Connection or Authorization Errors (401/403)

  • Cause: The API Key in appsettings.json is incorrect, expired, or the Bearer token header is malformed.
  • Solution: Verify the IHttpClientFactory setup in Program.cs is correctly reading the ApiKey from IOptions<OpenAISettings> and setting the Authorization header using "Bearer", settings.ApiKey. Check for trailing whitespace in the key.

JSON Serialization Issues

  • Cause: The structure of the request payload sent to the AI service does not match the required schema (e.g., missing model or incorrect structure for messages).
  • Solution: Ensure your anonymous object structure (or POCOs, if used) perfectly matches the OpenAI API documentation, especially the nested array structure for messages.
← Quay lįŗ”i Blog
Integrating OpenAI with .NET 8 for Optimizely Solutions - Ginbok