Skip to main content

Outline

This project will guide you through building a C# Opal tool that acts as a custom workflow step within Optimizely CMP. This tool will validate content based on specific criteria (e.g., minimum word count, presence of required keywords) before allowing the workflow to proceed.

By the end of this module, you will be able to:

  • Develop robust validation logic using parameter models
  • Understand deployment considerations for C# Opal tools.
  • Create a custom workflow step or a multilingual content manager.

Project Goal: To create a C# Opal tool that can be integrated into a CMP workflow to enforce content quality standards.

Scenario: A content marketing team wants to ensure that all articles published through CMP meet certain editorial guidelines, specifically a minimum word count and the inclusion of predefined SEO keywords. If an article does not meet these criteria, the workflow should be blocked, and the author should receive a clear message.

Key Steps and Implementation Details

Project Setup:

  • Create a new ASP.NET Core Web API project ( Core Concepts & APIs lesson)
  • Install OptimizelyOpal.OpalToolsSDK.
  • Configure Program.cs to use AddOpalToolsService() and UseOpalToolsService().
  • Install System.ComponentModel.DataAnnotations if not already present

Define Tool Parameters (Input Model):

  • Create a C# class ContentValidationParams to represent the input from CMP. This will include the ContentGuid of the article being validated, the MinimumWordCount, and a list of RequiredKeywords.
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;

public class ContentValidationParams
{
    [Required(ErrorMessage = "Content GUID is required.")]
    public string ContentGuid { get; set; }

    [Range(100, 5000, ErrorMessage = "Minimum word count must be between 100 and 5000.")]
    public int MinimumWordCount { get; set; }

    public List RequiredKeywords { get; set; } = new List();
}

Implement Content Fetching (Simulated or Real):

  • For this example, we'll simulate fetching content. In a real scenario, you would use Optimizely's Content Delivery API or CMP's internal APIs to retrieve the actual article text.
  • Create a mock IContentService and ContentService (as shown in Lesson 3.4) that returns a dummy article text based on the ContentGuid.

Implement Validation Logic:

  • Create a static method for your tool, applying the [Tool] attribute.
  • Inside the method, use Validator.TryValidateObject for basic data annotation validation.
  • Implement custom logic to:
    • Get the article text (from your simulated service).
    • Calculate the word count of the article.
    • Check for the presence of each required keyword in the article text.
  • Return a success status or throw an ArgumentException with a clear error message if validation fails.
using OptimizelyOpal.OpalToolsSDK.Attributes;
using System.ComponentModel.DataAnnotations;
using System.Text.RegularExpressions; // For word count and keyword check
using System.Linq; // For LINQ operations
using System.Threading.Tasks;

// Assume IContentService and ContentService are defined as in Lesson 3.4
// and registered in Program.cs: builder.Services.AddScoped();

public static class WorkflowTools
{
    [Tool(name: "validate_article_for_workflow", description: "Validates an article's content for workflow progression.")]
    public static async Task ValidateArticleForWorkflow(ContentValidationParams parameters, [FromServices] IContentService contentService)
    {
        // 1. Validate input parameters using Data Annotations
        var validationContext = new ValidationContext(parameters, serviceProvider: null, items: null);
        var validationResults = new List();

        bool isValid = Validator.TryValidateObject(parameters, validationContext, validationResults, true);

        if (!isValid)
        {
            var errors = validationResults.Select(r => r.ErrorMessage).ToList();
            throw new ArgumentException($"Input validation failed: {string.Join("; ", errors)}");
        }

        // 2. Simulate fetching article content
        // In a real scenario, this would be an API call to get the actual article text
        var articleContent = await contentService.GetContentById(parameters.ContentGuid);
        if (articleContent == null)
        {
            throw new KeyNotFoundException($"Article with GUID '{parameters.ContentGuid}' not found in CMP.");
        }

        string articleText = articleContent.ToString(); // Assuming content is convertible to string for simplicity

        // 3. Perform word count validation
        int actualWordCount = Regex.Matches(articleText, @"\b\w+\b").Count;
        if (actualWordCount < parameters.MinimumWordCount)
        {
            throw new ArgumentException($"Content validation failed: Article has {actualWordCount} words, but requires at least {parameters.MinimumWordCount}.");
        }

        // 4. Check for required keywords
        var missingKeywords = new List();
        foreach (var keyword in parameters.RequiredKeywords)
        {
            if (!articleText.Contains(keyword, StringComparison.OrdinalIgnoreCase))
            {
                missingKeywords.Add(keyword);
            }
        }

        if (missingKeywords.Any())
        {
            throw new ArgumentException($"Content validation failed: Missing required keywords: {string.Join(", ", missingKeywords)}.");
        }

        // If all validations pass
        return new { status = "success", message = "Article content passed all validation checks. Workflow can proceed." };
    }
}

Register the Tool: Ensure your WorkflowTools class (or the class containing your tool method) is registered with the OpalToolsService in Program.cs.

Testing:

Run your ASP.NET Core application locally: dotnet run

Access the Discovery Endpoint: Navigate to <a href="http://localhost:5000/discovery">http://localhost:5000/discovery</a> (or your application's port) in your browser to verify the tool manifest is correctly exposed.

Test with an API Client (Postman/Insomnia):

  • Send POST requests to <a href="http://localhost:5000/tools/validate_article_for_workflow">http://localhost:5000/tools/validate_article_for_workflow</a>.
  • Test Case 1 (Success):

{
"contentGuid": "some-article-guid-123",
"minimumWordCount": 50,
"requiredKeywords": ["marketing", "strategy"]
}

(Ensure your mock IContentService returns content that satisfies these criteria).

  • Test Case 2 (Fails - Low Word Count)

{
"contentGuid": "some-article-guid-123",
"minimumWordCount": 500,
"requiredKeywords": ["marketing"]
}

  • Test Case 3 (Fails - Missing Keyword):

{
"contentGuid": "some-article-guid-123",
"minimumWordCount": 50,
"requiredKeywords": ["marketing", "nonexistent_keyword"]
}

  • Test Case 4 (Invalid Input)

{
"contentGuid": null,
"minimumWordCount": 50,
"requiredKeywords": []
}

Integrate into CMP Workflow (Conceptual):

  • In CMP, navigate to Workflow settings.
  • Add a new "External Step" to your content workflow.
  • Configure the external step to call your deployed C# Opal tool's endpoint (<a href="http://your-deployed-url/tools/validate_article_for_workflow)">http://your-deployed-url/tools/validate_article_for_workflow)</a>.
  • Map the necessary CMP content fields (e.g., article body, word count field) to the parameters expected by your tool (ContentGuid, MinimumWordCount, RequiredKeywords).
  • Test the workflow by submitting an article that meets/fails the criteria.

By completing this project, you will have developed a robust C# Opal tool that integrates seamlessly with CMP workflows, demonstrating how to enforce business rules and automate content governance.