Tuesday, September 23, 2025

Building a Simple C++ Method Extractor in C#

 

Extracting C++ Method Bodies with C#

When working with large C++ projects, sometimes all you need is a quick way to extract specific method bodies for debugging, refactoring, or documentation. Instead of building a full-fledged parser, you can rely on C# with regex and brace matching to create a lightweight and practical utility.

In this blog, we’ll walk through a step-by-step implementation of a tool that extracts method bodies from .cpp files. The process involves a simple main program, a model class, and the core extractor that handles regex parsing and safe brace matching.

Step 1 — Main Program (Program.cs)

The entry point initializes the extractor with a .cpp file path and prints out the results:

class Program
{
    static void Main(String[] args)
    {
        var methodBody = CppRegexExtractor.ExtractMethodsBodyWithRegex(@"C:\Temp\Dummy!.cpp");
        Console.WriteLine(methodBody);
        Console.ReadLine();
    }
}

This ensures the tool can read a given C++ source file and display the extracted method bodies.

Step 2 — Model Class (ProgramBody.cs)

We define a simple model to store each method’s name (Key) and body content (Content):

using System;

namespace ExtractCppCode
{
    public class ProgramBody
    {
        public string Key { get; set; }
        public string Content { get; set; }
    }
}

This acts as a container for extracted results, making it easier to handle multiple methods.

Step 3 — Core Extractor (CppRegexExtractor.cs)

Here’s where the real logic lives. The extractor uses regex to detect method signatures, then employs a brace matching algorithm to safely capture the full method body (ignoring braces inside strings or comments).

Key steps include:

  1. Regex pattern to detect C++ method signatures.
  2. Brace matching to find the correct closing brace.
  3. Optional sub-method detection to include methods invoked inside a target method (like SaveProduct).
using ExtractCppCode;
using System.Text;
using System.Text.RegularExpressions;

public static class CppRegexExtractor
{
    public static string ExtractMethodsBodyWithRegex(string cppFilePath)
    {
        if (!File.Exists(cppFilePath))
        {
            Console.WriteLine($"Error: File not found at {cppFilePath}");
            return "";
        }

        Console.WriteLine("Start Extracting Methods..........");

        List<ProgramBody> lines = new List<ProgramBody>();
        List<ProgramBody> subLines = new List<ProgramBody>();
        StringBuilder builder = new StringBuilder();

        string cppContent = File.ReadAllText(cppFilePath);

        // Regex for method signatures
        string pattern = @"(?<retType>[\w\s\*&<>:]+)\s+(?<className>[\w:]+::)?(?<methodName>\w+)\s*\((?<params>.*?)\)\s*\{";
        Regex regex = new Regex(pattern, RegexOptions.Multiline | RegexOptions.ExplicitCapture);
        MatchCollection matches = regex.Matches(cppContent);

        foreach (Match match in matches)
        {
            string methodName = match.Groups["methodName"].Value;
            if (methodName == "if") continue; // skip keywords

            int bodyStartIdx = match.Index + match.Length - 1;
            int bodyEndIdx = FindMatchingBrace(cppContent, bodyStartIdx);

            string result = cppContent.Substring(match.Index, bodyEndIdx - match.Index + 1);
            lines.Add(new ProgramBody { Key = methodName, Content = result });
        }

        // Extract SaveProduct + its submethods
        var selectedMethod = lines.Find(x => x.Key == "PreSaveUpdate");
        if (selectedMethod != null)
        {
            builder.Append(selectedMethod.Content);

            string subPattern = @"\b(?<methodName>\w+)\s*\((?<arguments>[^)]*)\)";
            MatchCollection subMatches = Regex.Matches(selectedMethod.Content, subPattern);

            foreach (Match subMatch in subMatches)
            {
                string subMethodName = subMatch.Groups["methodName"].Value.Trim();
                if (subMethodName == selectedMethod.Key || subMethodName == "if") continue;

                var subItem = lines.Find(y => y.Key == subMethodName);
                if (subItem != null)
                {
                    subLines.Add(new ProgramBody
                    {
                        Key = subMethodName,
                        Content = subItem.Content
                    });
                }
            }

            foreach (var line in subLines)
            {
                builder.Append(line.Content);
            }
        }

        Console.WriteLine("Extracting Methods Completed..........");
        return builder.ToString();
    }

    // Finds the matching closing brace
    private static int FindMatchingBrace(string source, int openingBraceIdx)
    {
        if (source[openingBraceIdx] != '{')
            throw new ArgumentException("openingBraceIdx must point at a '{' character.");

        int depth = 0;
        bool inSingleLineComment = false;
        bool inMultiLineComment = false;
        bool inString = false;
        bool inChar = false;
        bool escaped = false;

        for (int i = openingBraceIdx; i < source.Length; i++)
        {
            char c = source[i];

            if (escaped) { escaped = false; continue; }
            if (c == '\\' && (inString || inChar)) { escaped = true; continue; }

            if (inSingleLineComment) { if (c == '\n') inSingleLineComment = false; continue; }
            if (inMultiLineComment) { if (c == '*' && i + 1 < source.Length && source[i + 1] == '/') { inMultiLineComment = false; i++; } continue; }

            if (inString) { if (c == '"') inString = false; continue; }
            if (inChar) { if (c == '\'') inChar = false; continue; }

            if (c == '/' && i + 1 < source.Length)
            {
                char next = source[i + 1];
                if (next == '/') { inSingleLineComment = true; i++; continue; }
                if (next == '*') { inMultiLineComment = true; i++; continue; }
            }
            if (c == '"') { inString = true; continue; }
            if (c == '\'') { inChar = true; continue; }

            if (c == '{') depth++;
            else if (c == '}')
            {
                depth--;
                if (depth == 0) return i;
            }
        }

        throw new InvalidOperationException("Unbalanced braces detected.");
    }
}

Sample Input — Dummy.cpp

Here’s a test C++ file containing various methods, including comments and tricky braces inside strings:

#include <iostream>
#include <string>

// A helper method with braces inside strings and comments
int helper1(int x)
{
    // This brace } in a comment should be ignored
    std::string s = "example with brace } inside string";
    if (x > 0) {
        return x + 1;
    }
    return 0;
}

/* Multiline comment with { and } that should be ignored */

void helper2()
{
    for (int i = 0; i < 3; ++i)
    {
        std::cout << "loop " << i << std::endl;
    }
}

// The main method we want to extract
bool SaveProduct(int id)
{
    std::cout << "SaveProduct called" << std::endl;
    int r = helper1(id);
    if (r > 0)
    {
        helper2();
        return true;
    }
    return false;
}

// Another unrelated method
double unrelated(double a, double b)
{
    return a * b;
}

Expected Extracted Output

When the extractor runs, it captures SaveProduct along with its submethods helper1 and helper2:

bool SaveProduct(int id)
{
    std::cout << "SaveProduct called" << std::endl;
    int r = helper1(id);
    if (r > 0)
    {
        helper2();
        return true;
    }
    return false;
}

int helper1(int x)
{
    // This brace } in a comment should be ignored
    std::string s = "example with brace } inside string";
    if (x > 0) {
        return x + 1;
    }
    return 0;
}

void helper2()
{
    for (int i = 0; i < 3; ++i)
    {
        std::cout << "loop " << i << std::endl;
    }
}

Sunday, September 14, 2025

Building a ChatGPT Chatbot in ASP.NET WebForms

 

Build a Simple ChatGPT WebForm App in ASP.NET (C#)

If you want to integrate ChatGPT into your ASP.NET WebForms project, here’s a step-by-step guide with complete code. We’ll create a small app where users can type prompts, send them to the OpenAI API, and display the assistant’s response — just like a mini chat interface.

Step 1: Define Models (Models.cs)

Create a folder named Models and add a file called MoModels.cs..Models.cs

.
This file defines the data structures for requests and responses.
using System;

namespace ChatGPT.Models
{
    public class ChatMessage
    {
        public string Role { get; set; } = string.Empty;
        public string Content { get; set; } = string.Empty;
    }

    public class Usage
    {
        public int? Prompt_Tokens { get; set; }
        public int? Completion_Tokens { get; set; }
        public int? Total_Tokens { get; set; }
    }

    public class Choice
    {
        public int Index { get; set; }
        public ChatMessage? Message { get; set; }
        public string? Finish_Reason { get; set; }
    }

    public class ChatResponse
    {
        public string? Id { get; set; }
        public string? Object { get; set; }
        public int Created { get; set; }
        public string? Model { get; set; }
        public Choice[]? Choices { get; set; }
        public Usage? Usage { get; set; }
    }

    public class ChatRequest
    {
        public string model { get; set; } = string.Empty;
        public ChatMessage[]? messages { get; set; }
        public int? max_tokens { get; set; }
    }
}

Step 2: Create OpenAI Client (OpenAiClient.cs)

Now, add a Controller folder and create OpenAiClient.cs.
This class handles communication with the OpenAI API.

using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
using ChatGPT.Models;

namespace ChatGPT.Controller
{
    public class OpenAiClient : IDisposable
    {
        private readonly HttpClient _httpClient;
        private readonly string _apiKey;
        private bool _disposed;

        public OpenAiClient(string apiKey)
        {
            _apiKey = apiKey ?? throw new ArgumentNullException(nameof(apiKey));
            _httpClient = new HttpClient();
            _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _apiKey);
        }

        public async Task<ChatMessage> GetChatCompletionAsync(List<ChatMessage> conversationHistory)
        {
            if (conversationHistory == null) throw new ArgumentNullException(nameof(conversationHistory));

            var requestData = new ChatRequest
            {
                model = "gpt-3.5-turbo",   // Change to your preferred model
                messages = conversationHistory.ToArray(),
                max_tokens = 150
            };

            var jsonPayload = JsonConvert.SerializeObject(requestData);
            var content = new StringContent(jsonPayload, Encoding.UTF8, "application/json");
            var apiUrl = "https://api.openai.com/v1/chat/completions";

            try
            {
                HttpResponseMessage response = await _httpClient.PostAsync(apiUrl, content).ConfigureAwait(false);
                response.EnsureSuccessStatusCode();
                string responseBody = await response.Content.ReadAsStringAsync().ConfigureAwait(false);

                var chatResponse = JsonConvert.DeserializeObject<ChatResponse>(responseBody);

                if (chatResponse?.Choices?.Length > 0)
                {
                    return chatResponse.Choices[0].Message ?? new ChatMessage { Role = "assistant", Content = string.Empty };
                }

                return new ChatMessage { Role = "assistant", Content = "No response received." };
            }
            catch (HttpRequestException ex)
            {
                return new ChatMessage { Role = "assistant", Content = $"Error calling OpenAI API: {ex.Message}" };
            }
        }

        public void Dispose()
        {
            if (!_disposed)
            {
                _httpClient?.Dispose();
                _disposed = true;
            }
        }
    }
}

Step 3: Build the Frontend (Home.aspx)

Create a new WebForm page named Home.aspx.
This will provide the UI for sending prompts and displaying responses.

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Home.aspx.cs" Inherits="ChatGPT.Home" %>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>ChatGPT WebForm</title>
</head>
<body>
    <form id="form1" runat="server">
        <div>
            <table width="99%">
                <tr>
                    <td>
                        <asp:TextBox ID="PromptTextBox" runat="server" 
                                     TextMode="MultiLine" Rows="5" Width="400" style="width:100%;"></asp:TextBox>
                    </td>
                </tr>
                <tr>
                    <td>
                        <asp:Button ID="SendButton" runat="server" 
                                    Text="Get AI Response" OnClick="SendButton_Click" />
                    </td>
                </tr>
                <tr>
                    <td>
                        <div id="chatWindow" runat="server" 
                             style="height: 400px; overflow-y: scroll; border: 1px solid #ccc; padding: 10px;">
                            <!-- Chat history will appear here -->
                        </div>
                    </td>
                </tr>
            </table>
        </div>
    </form>
</body>
</html>

Step 4: Handle Backend Logic (Home.aspx.cs)

Finally, implement the backend logic in Home.aspx.cs.

using System;
using System.Collections.Generic;
using System.Web.UI;
using ChatGPT.Models;
using ChatGPT.Controller;

namespace ChatGPT
{
    public partial class Home : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
        }

        protected async void SendButton_Click(object sender, EventArgs e)
        {
            string apiKey = "YOUR_API_KEY";   // Replace with your API key

            using (var openAiClient = new OpenAiClient(apiKey))
            {
                if (ViewState["ConversationHistory"] == null)
                {
                    ViewState["ConversationHistory"] = new List<ChatMessage>();
                }

                var conversationHistory = (List<ChatMessage>)ViewState["ConversationHistory"];
                string promptText = PromptTextBox.Text;

                if (!string.IsNullOrWhiteSpace(promptText))
                {
                    conversationHistory.Add(new ChatMessage { Role = "user", Content = promptText });
                    var aiResponse = await openAiClient.GetChatCompletionAsync(conversationHistory);
                    conversationHistory.Add(aiResponse);

                    // Save state
                    ViewState["ConversationHistory"] = conversationHistory;

                    // Clear textbox
                    PromptTextBox.Text = string.Empty;

                    // Render chat
                    RenderChatWindow(conversationHistory);
                }
            }
        }

        private void RenderChatWindow(List<ChatMessage> conversationHistory)
        {
            chatWindow.InnerHtml = string.Empty;

            foreach (var message in conversationHistory)
            {
                string cssClass = (message.Role == "user") ? "user-message" : "assistant-message";
                chatWindow.InnerHtml += $"<div class=\"{cssClass}\"><b>{message.Role}:</b><pre>{System.Web.HttpUtility.HtmlEncode(message.Content)}</pre></div>";
            }
        }
    }
}

If you are using.net framework < 4.6 then use below method to call api


public string GetChatCompletion(string promptText, string selectedModel)
{
    var requestData = new ChatRequest
    {
        model = selectedModel,
        messages = new ChatMessage[]
        {
            new ChatMessage { role = "user", content = promptText }
        }
    };

    var jsonPayload = JsonConvert.SerializeObject(requestData);
    var apiUrl = "https://api.openai.com/v1/chat/completions";

    try
    {
        HttpWebRequest request = (HttpWebRequest)WebRequest.Create(apiUrl);
        request.Method = "POST";
        request.ContentType = "application/json";
        request.Headers["Authorization"] = "Bearer " + _apiKey;

        byte[] bytes = Encoding.UTF8.GetBytes(jsonPayload);

        using (Stream stream = request.GetRequestStream())
        {
            stream.Write(bytes, 0, bytes.Length);
            stream.Close();
        }

        WebResponse response = request.GetResponse();
        using (var reader = new StreamReader(response.GetResponseStream()))
        {
            var responseBody = reader.ReadToEnd();
            var chatResponse = JsonConvert.DeserializeObject<ChatResponse>(responseBody);

            if (chatResponse?.Choices?.Length > 0)
            {
                return pichatResponse.Choices[0].Message.Content.Trim();
            }
            return "No response received.";
        }
    }
    catch (HttpRequestException ex)
    {
        return $"Error calling OpenAI API: {ex.Message}";
    }
}