DEV Community

Cover image for ChatGPT custom GPT and C#
Karen Payne
Karen Payne

Posted on

62 1

ChatGPT custom GPT and C#

Introduction

When displaying an image on a web page, it's recommended that the alt attribute holds a textual replacement for the image. This is mandatory and incredibly useful for accessibility—screen readers read the attribute value out to their users so they know what the image means.

To assist with this ChatGPT with a paid subscription, a developer can create a custom GPT that, once one or more images are uploaded, can produce alternative text for an image using instructions and rules provided by the user.

Learn how to use a custom GPT to create alternative text, which is then saved to a text file in a C# project. The project stores the image and alternate text in an SQL-Server database, and then, in an ASP.NET Core project, the image with the alternative text from the database is rendered.

The benefits of storing images and alternative text are twofold: consistency for multiple projects and the ease of making changes in one place after the text is in the database.

Example for alternative text from using a custom GPT

<img src="Wine.png" alt="Glass of red wine with a clear stem, filled halfway, on a white background." />
Enter fullscreen mode Exit fullscreen mode

Not bad but we could remove "on a white background".

Best practices

Dictate that images should be stored in the file system instead of in a database. In the example used, the photos represent categories for products in a modified version of the Microsoft NorthWind database where no image exceeds nine kilobytes, which breaks the no images in a database. Still, in this case, it's acceptable.

The developer decides whether images should be stored in a database or the file system; alternative text can be saved to a database.

Custom GPT

GPTs are custom versions of ChatGPT that users can tailor for specific tasks or topics by combining instructions, knowledge, and capabilities. They can be as simple or as complex as needed, addressing anything from language learning to technical support. Plus, Team, and Enterprise users can start creating GPTs at chatgpt.com/create.

how to create a GPT.

Generate text for image alt attribute

Using the instructions above and the following instructions, is the finished product.

alternative text custom GPT

Example

Given the following image.

Image description

Produces

<img src="sample.png" alt="Bridge crossing a river with fall foliage on both sides and a snow-covered mountain in the distance." />
Enter fullscreen mode Exit fullscreen mode

For generating images to stored in the database, upload all images at once.

screen shot for above

Note
In some cases ChatGPT will not use the file names for the uploaded images, if this happens re-prompt with "Use the file name for each img"

Save results to SQL-Server

Copy the results

<img src="Beverages.png" alt="Table with a wine bottle, cups, a teapot, and a decorative plate in the background." />

<img src="Condiments.png" alt="Assorted jars, cups, and a salt shaker arranged in front of a plate." />

<img src="Confections.png" alt="Whole pie with a golden crust and sliced pieces on a wooden table." />

<img src="DairyProducts.png" alt="Blocks of cheese on a plate with wrapped cheese packages behind them." />

<img src="GrainsCereals.png" alt="Baked bread loaves with a sliced portion on a red and white cloth." />

<img src="MeatPoultry.png" alt="Cooked meat on a white plate with corn and other vegetables around it." />

<img src="Produce.png" alt="Bowls of various dumplings and a tray of packaged frozen dumplings." />

<img src="Seafood.png" alt="Blue floral dishware with crepes or pancakes on a platter." />

<img src="Wine.png" alt="Glass of red wine on a white background." />
Enter fullscreen mode Exit fullscreen mode

🛑 Create the database WorkingImages, followed by running CreateDatabase.sql under DataScripts in the AlterateImageApp project.

Paste into a text file in the console project.

The following class

public partial class FileOperations
{

    public static List<ImageAltText> ReadAltTexts(string fileName) =>
    (
        from line in File.ReadLines(fileName) 
        select ImageAltTextRegex().Match(line) into match 
        where match.Success select new ImageAltText
        {
            Src = match.Groups[1].Value, 
            Alt = match.Groups[2].Value
        }).ToList();


    [GeneratedRegex(@"<img\s+src=""([^""]+)""\s+alt=""([^""]+)""\s*/?>", RegexOptions.IgnoreCase, "en-US")]
    private static partial Regex ImageAltTextRegex();
}
Enter fullscreen mode Exit fullscreen mode

Which produces.

output from using the method ReadAltTexts

The next step uses Dapper to first truncate the table and reset the identity column using the method below, AddRange.

Next, to validate that the images can be read back, the method Write creates each image in a local folder. This is optional and can be commented out.

using System.Text.RegularExpressions;
using AlternateImageApp.Models;
using ConsoleConfigurationLibrary.Classes;
using Dapper;
using Microsoft.Data.SqlClient;

namespace AlternateImageApp.Classes;

/// <summary>
/// Provides operations for managing and manipulating image alt text data within a database.
public partial class DataOperations
{
    public static void AddRange(List<ImageAltText> list, string filePath)
    {

        TruncateTable("Categories");

        using var db = new SqlConnection(AppConnections.Instance.MainConnection);

        foreach (var item in list)
        {
            var bytes = File.ReadAllBytes(Path.Combine(filePath, item.Src));

            db.Execute(
                """
                INSERT INTO Categories (Name, Ext, AltText, Photo) 
                VALUES (@Name, @Ext, @AltText, @Photo)
                """, 
                new
                {
                    item.Name,
                    Ext = item.Ext,
                    AltText = item.Alt, 
                    Photo = bytes
                });
        }
    }

    public static void Write()
    {
        using var db = new SqlConnection(AppConnections.Instance.MainConnection);
        var results = db.Query<ImageAltText>(
            """
            SELECT Id, Name as Src, Ext, AltText as Alt, Photo 
            FROM dbo.Categories
            """).ToList();

        if (!Directory.Exists("Result"))
        {
            Directory.CreateDirectory("Result");
        }

        foreach (var item in results)
        {
            File.WriteAllBytes(Path.Combine("Result", item.FileName), item.Photo);
        }

    }

    public static void TruncateTable(string tableName)
    {
        if (string.IsNullOrWhiteSpace(tableName) || !IsValidSqlIdentifier(tableName))
            throw new ArgumentException("Invalid table name.", nameof(tableName));

        var sql = 
            $"""
             TRUNCATE TABLE dbo.{tableName};
             DBCC CHECKIDENT ('dbo.{tableName}', RESEED, 1);
             """;

        using var cn = new SqlConnection(AppConnections.Instance.MainConnection);
        cn.Execute(sql);
    }

    private static bool IsValidSqlIdentifier(string name)
    {
        return SqlIdentifierRegex().IsMatch(name);
    }

    [GeneratedRegex(@"^[A-Za-z_][A-Za-z0-9_]*$")]
    private static partial Regex SqlIdentifierRegex();
}
Enter fullscreen mode Exit fullscreen mode

ASP.NET Core project

page displaying our images with alternate text

For true validation, create an ASP.NET Core project using EF Core to ensure the images can be displayed as shown above.

EF Power Tools, a Visual Studio extension, was used to reverse engineer the database.

The connection string is stored in appsettings.json

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "ConnectionStrings": {
    "DefaultConnection": "Data Source=.\\SQLEXPRESS;Initial Catalog=WorkingImages;Integrated Security=True;Encrypt=False"
  }
}
Enter fullscreen mode Exit fullscreen mode

Dependency injection setup in Program.cs

public class Program
{
    public static void Main(string[] args)
    {
        var builder = WebApplication.CreateBuilder(args);

        // Add services to the container.
        builder.Services.AddRazorPages();

        builder.Services.AddDbContext<Context>(options =>
            options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));

        var app = builder.Build();
...
Enter fullscreen mode Exit fullscreen mode

Index.cshtml

@page
@using CategoriesApplication1.Classes
@model IndexModel
@{
    ViewData["Title"] = "Home page";
}

<style>

</style>

<div class="container">

    <div class="row">
        <div class="col-12">
            <h1 class="fs-4">Categories</h1>
        </div>
    </div>
    @for (var index = 0; index < Model.Categories.Count; index += 3)
    {

        <div class="row mb-4">

            @for (var innerIndex = index; innerIndex < index + 3 && innerIndex < Model.Categories.Count; innerIndex++)
            {
                <div class="col-md-4">

                    <div class="rotate">

                        <img src="data:image/png;base64,@Convert.ToBase64String(@Model.Categories[innerIndex].Photo)"
                             alt="@Model.Categories[innerIndex].AltText"
                             class="img-fluid "/>

                    </div>

                    <p class="mt-3">@Model.Categories[innerIndex].Name.SplitCase()</p>

                </div>
            }
        </div>
    }

</div>

<script src="lib/payne-debugger/debugHelper.js"></script>
<script>
    /*
    * CTRL+ALT+1 toggles adding/removing debugger.css
    */
    document.addEventListener('keydown', function (event) {

        if (event.key === '1' && event.altKey && event.ctrlKey) {
            $debugHelper.toggle(true);
        }

    });
</script>
Enter fullscreen mode Exit fullscreen mode

Note
The JavaScript code, pressing CTRL + ALT, 1 produces the following which is useful to understand the structure of any web page.

shows page structure

Index.cshtml.cs

public class IndexModel(Context context) : PageModel
{
    public required IList<Categories> Categories { get; set; }

    /// <summary>
    /// Handles GET requests for the Index page.
    /// </summary>
    /// <remarks>
    /// This method asynchronously retrieves a list of categories from the database
    /// and assigns it to the <see cref="Categories"/> property.
    /// </remarks>
    /// <returns>A task that represents the asynchronous operation.</returns>
    public async Task OnGetAsync()
    {
        Categories = await context.Categories.ToListAsync();
    }
}
Enter fullscreen mode Exit fullscreen mode

CSS

See wwwroot\css\site.css

h1 {
    margin: 1em 0 0.5em 0;
    color: #343434;
    font-weight: normal;
    font-family: 'Ultra', sans-serif;
    font-size: 36px;
    line-height: 42px;
    text-transform: uppercase;
    text-shadow: 0 2px white, 0 3px #777;
}

img {
    max-width: 40px;
    box-shadow: 8px 8px 15px rgba(0, 0, 0, 0.5);
}

.rotate img {
    transition: 1s ease;
}

.rotate img:hover {
    -webkit-transform: rotateZ(-10deg);
    -ms-transform: rotateZ(-10deg);
    transform: rotateZ(-10deg);
    transition: 1s ease;
}
Enter fullscreen mode Exit fullscreen mode

Source code

Console projectsource code ASP.NET Core projectsource code

Summary

Combining a custom ChatGPT GPT to create alternative text that follows common rules, which is then saved to a database, can make a site more in line with WCAG AA compliance and ensure uniform alternative text across all applications that use these images.

Wave validator

A great tool for testing a web page is Wave.

Top comments (2)

Collapse
 
halbersc profile image
Halber A.

Do you know if is it possible to access to custom GPT by API?

Collapse
 
karenpayneoregon profile image
Karen Payne

Not possible. The only API aspect is calling an API using a GPT action