Skip to content

Integrate from .NET Framework 2.0 / 4.5 ​

Source

Mirrored from the suite checkout docs/DOTNET_FRAMEWORK_INTEGRATION.md. Prefer updating that file, then re-copy here when contracts change.

How-to for legacy .NET Framework apps that must call OxyKit.Mail or OxyKit.Render over HTTP.

Do not reference OxyKit.Mail.Client, OxyKit.Render.Client, or the Contracts NuGet packages from Framework. Those SDKs target modern .NET (net10.0 today). Call the JSON APIs directly. Prefer upgrading the host to .NET 10+ and using the typed clients when you can.

Authoritative shapes: live OpenAPI on each API in Development (/openapi/v1.json, Scalar /scalar). This doc is a minimal cookbook, not a second OpenAPI catalog.

Which sample covers which Framework? ​

Two HTTP stacks cover essentially every supported Framework app:

Your project targetsUse this sampleWhy
2.0, 3.0, 3.5, 4.02.0 (HttpWebRequest)No HttpClient in the BCL until 4.5
4.5, 4.6, 4.7, 4.84.5 (HttpClient)Same pattern through the end of Framework

Framework 1.x is out of scope. You do not need separate docs for 3.x or 4.6/4.7/4.8 — pick the row above.

ServiceDefault local baseAuth header
OxyKit.Mailhttp://localhost:5026X-Api-Key: {key} (or Authorization: Bearer {jwt})
OxyKit.Renderhttp://localhost:5066X-OxyKit-Render-Key: {key}

Shared notes ​

  • Build JSON with string.Format / concatenation unless you already ship a JSON library. Do not assume System.Text.Json.
  • Prefer UTF-8 bodies and Content-Type: application/json; charset=utf-8.
  • Escape JSON string values (\, ", control chars). The 2.0 samples include a tiny helper; reuse it from 4.5 if needed.
  • HTTPS + TLS 1.2: enable before the first request (once per AppDomain) on older runtimes:
csharp
// Prefer the enum on 4.5+. On 2.0 / 4.0 use the numeric cast if the enum value is missing.
ServicePointManager.SecurityProtocol =
    (SecurityProtocolType)3072; // TLS 1.2
  • Keep API keys out of source control.

OxyKit.Mail — send email ​

POST /api/v1/email/send

Success means the message was accepted for Mail-owned delivery — do not re-send when status is sent, accepted, or scheduled.

HTTPTypical statusMeaning
200sentSMTP succeeded immediately
202acceptedQueued for Mail retry / delivery
202scheduledStored for later send

Example body:

json
{
  "to": "user@example.com",
  "cc": "",
  "bcc": "",
  "subject": "Hello",
  "body": "Plain text or HTML",
  "isHtml": false
}

Example success body:

json
{
  "emailId": "11111111-1111-1111-1111-111111111111",
  "status": "accepted",
  "message": "..."
}

Sample — Framework 2.0 (also 3.x / 4.0) ​

csharp
using System;
using System.IO;
using System.Net;
using System.Text;

public static class OxyKitMailSendFw20
{
    public static string Send(
        string baseUrl,
        string apiKey,
        string to,
        string subject,
        string body,
        bool isHtml)
    {
        string json = string.Format(
            "{{\"to\":{0},\"cc\":\"\",\"bcc\":\"\",\"subject\":{1},\"body\":{2},\"isHtml\":{3}}}",
            JsonString(to),
            JsonString(subject),
            JsonString(body),
            isHtml ? "true" : "false");

        Uri uri = new Uri(Combine(baseUrl, "api/v1/email/send"));
        HttpWebRequest req = (HttpWebRequest)WebRequest.Create(uri);
        req.Method = "POST";
        req.ContentType = "application/json; charset=utf-8";
        req.Headers["X-Api-Key"] = apiKey;

        byte[] bytes = Encoding.UTF8.GetBytes(json);
        req.ContentLength = bytes.Length;
        using (Stream s = req.GetRequestStream())
            s.Write(bytes, 0, bytes.Length);

        HttpWebResponse resp;
        try
        {
            resp = (HttpWebResponse)req.GetResponse();
        }
        catch (WebException ex)
        {
            if (ex.Response == null)
                throw;
            resp = (HttpWebResponse)ex.Response;
        }

        using (resp)
        using (StreamReader reader = new StreamReader(resp.GetResponseStream(), Encoding.UTF8))
        {
            string responseBody = reader.ReadToEnd();
            int code = (int)resp.StatusCode;
            if (code != 200 && code != 202)
                throw new ApplicationException(
                    "OxyKit.Mail send failed: HTTP " + code + " " + responseBody);

            // Treat sent / accepted / scheduled as success — do not re-send.
            if (responseBody.IndexOf("\"status\":\"sent\"") < 0
                && responseBody.IndexOf("\"status\":\"accepted\"") < 0
                && responseBody.IndexOf("\"status\":\"scheduled\"") < 0)
            {
                throw new ApplicationException(
                    "OxyKit.Mail returned unexpected body: " + responseBody);
            }

            return responseBody;
        }
    }

    private static string Combine(string baseUrl, string path)
    {
        if (baseUrl.EndsWith("/"))
            return baseUrl + path;
        return baseUrl + "/" + path;
    }

    private static string JsonString(string value)
    {
        if (value == null)
            value = "";
        StringBuilder sb = new StringBuilder();
        sb.Append('"');
        for (int i = 0; i < value.Length; i++)
        {
            char c = value[i];
            switch (c)
            {
                case '\\': sb.Append("\\\\"); break;
                case '"': sb.Append("\\\""); break;
                case '\n': sb.Append("\\n"); break;
                case '\r': sb.Append("\\r"); break;
                case '\t': sb.Append("\\t"); break;
                default:
                    if (c < 0x20)
                        sb.AppendFormat("\\u{0:x4}", (int)c);
                    else
                        sb.Append(c);
                    break;
            }
        }
        sb.Append('"');
        return sb.ToString();
    }
}

Sample — Framework 4.5+ (also 4.6 / 4.7 / 4.8) ​

Reference System.Net.Http (BCL on 4.5+). Same JSON / status rules as the 2.0 sample — share JsonString in one helper if you maintain both.

csharp
using System;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;

public static class OxyKitMailSendFw45
{
    private static readonly HttpClient Http = new HttpClient();

    public static async Task<string> SendAsync(
        string baseUrl,
        string apiKey,
        string to,
        string subject,
        string body,
        bool isHtml)
    {
        string json = string.Format(
            "{{\"to\":{0},\"cc\":\"\",\"bcc\":\"\",\"subject\":{1},\"body\":{2},\"isHtml\":{3}}}",
            JsonString(to),
            JsonString(subject),
            JsonString(body),
            isHtml ? "true" : "false");

        using (var content = new StringContent(json, Encoding.UTF8, "application/json"))
        using (var request = new HttpRequestMessage(HttpMethod.Post, Combine(baseUrl, "api/v1/email/send")))
        {
            request.Headers.TryAddWithoutValidation("X-Api-Key", apiKey);
            request.Content = content;

            using (HttpResponseMessage response = await Http.SendAsync(request).ConfigureAwait(false))
            {
                string responseBody = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
                int code = (int)response.StatusCode;
                if (code != 200 && code != 202)
                    throw new ApplicationException(
                        "OxyKit.Mail send failed: HTTP " + code + " " + responseBody);

                if (responseBody.IndexOf("\"status\":\"sent\"", StringComparison.Ordinal) < 0
                    && responseBody.IndexOf("\"status\":\"accepted\"", StringComparison.Ordinal) < 0
                    && responseBody.IndexOf("\"status\":\"scheduled\"", StringComparison.Ordinal) < 0)
                {
                    throw new ApplicationException(
                        "OxyKit.Mail returned unexpected body: " + responseBody);
                }

                return responseBody;
            }
        }
    }

    private static string Combine(string baseUrl, string path)
    {
        if (!baseUrl.EndsWith("/"))
            baseUrl += "/";
        return baseUrl + path;
    }

    private static string JsonString(string value)
    {
        if (value == null)
            value = "";
        StringBuilder sb = new StringBuilder();
        sb.Append('"');
        for (int i = 0; i < value.Length; i++)
        {
            char c = value[i];
            switch (c)
            {
                case '\\': sb.Append("\\\\"); break;
                case '"': sb.Append("\\\""); break;
                case '\n': sb.Append("\\n"); break;
                case '\r': sb.Append("\\r"); break;
                case '\t': sb.Append("\\t"); break;
                default:
                    if (c < 0x20)
                        sb.AppendFormat("\\u{0:x4}", (int)c);
                    else
                        sb.Append(c);
                    break;
            }
        }
        sb.Append('"');
        return sb.ToString();
    }
}

Sync call site only if needed: SendAsync(...).GetAwaiter().GetResult().

Create a sender API key in Mail with a role that may send (Admin or Sender).


OxyKit.Render — render a document ​

Example: table → HTML (binary formats: write the response stream to a file).

POST /render/table/html (also under /v1/render/...)

Example body:

json
{
  "sheets": [
    {
      "name": "Orders",
      "headers": ["Id", "Customer", "Total"],
      "rows": [[1, "Acme", 120.5], [2, "Beta", 80]]
    }
  ]
}

Errors return JSON { "error": "..." } with 4xx/5xx. Success returns the document bytes (text/html, application/pdf, etc.).

Sample — Framework 2.0 (also 3.x / 4.0) ​

csharp
using System;
using System.IO;
using System.Net;
using System.Text;

public static class OxyKitRenderFw20
{
    public static void RenderTableHtml(
        string baseUrl,
        string apiKey,
        string jsonBody,
        string outputFilePath)
    {
        Uri uri = new Uri(Combine(baseUrl, "render/table/html"));
        HttpWebRequest req = (HttpWebRequest)WebRequest.Create(uri);
        req.Method = "POST";
        req.ContentType = "application/json; charset=utf-8";
        req.Headers["X-OxyKit-Render-Key"] = apiKey;

        byte[] bytes = Encoding.UTF8.GetBytes(jsonBody);
        req.ContentLength = bytes.Length;
        using (Stream s = req.GetRequestStream())
            s.Write(bytes, 0, bytes.Length);

        HttpWebResponse resp;
        try
        {
            resp = (HttpWebResponse)req.GetResponse();
        }
        catch (WebException ex)
        {
            if (ex.Response == null)
                throw;
            resp = (HttpWebResponse)ex.Response;
        }

        using (resp)
        {
            int code = (int)resp.StatusCode;
            using (Stream stream = resp.GetResponseStream())
            {
                if (code < 200 || code >= 300)
                {
                    using (StreamReader reader = new StreamReader(stream, Encoding.UTF8))
                        throw new ApplicationException(
                            "OxyKit.Render failed: HTTP " + code + " " + reader.ReadToEnd());
                }

                using (FileStream file = File.Create(outputFilePath))
                    CopyTo(stream, file);
            }
        }
    }

    private static void CopyTo(Stream input, Stream output)
    {
        byte[] buffer = new byte[8192];
        int read;
        while ((read = input.Read(buffer, 0, buffer.Length)) > 0)
            output.Write(buffer, 0, read);
    }

    private static string Combine(string baseUrl, string path)
    {
        if (baseUrl.EndsWith("/"))
            return baseUrl + path;
        return baseUrl + "/" + path;
    }
}

Sample — Framework 4.5+ (also 4.6 / 4.7 / 4.8) ​

csharp
using System;
using System.IO;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;

public static class OxyKitRenderFw45
{
    private static readonly HttpClient Http = new HttpClient();

    public static async Task RenderTableHtmlAsync(
        string baseUrl,
        string apiKey,
        string jsonBody,
        string outputFilePath)
    {
        using (var content = new StringContent(jsonBody, Encoding.UTF8, "application/json"))
        using (var request = new HttpRequestMessage(HttpMethod.Post, Combine(baseUrl, "render/table/html")))
        {
            request.Headers.TryAddWithoutValidation("X-OxyKit-Render-Key", apiKey);
            request.Content = content;

            using (HttpResponseMessage response = await Http.SendAsync(request).ConfigureAwait(false))
            {
                if (!response.IsSuccessStatusCode)
                {
                    string err = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
                    throw new ApplicationException(
                        "OxyKit.Render failed: HTTP " + (int)response.StatusCode + " " + err);
                }

                using (Stream stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false))
                using (FileStream file = File.Create(outputFilePath))
                    await stream.CopyToAsync(file).ConfigureAwait(false);
            }
        }
    }

    private static string Combine(string baseUrl, string path)
    {
        if (!baseUrl.EndsWith("/"))
            baseUrl += "/";
        return baseUrl + path;
    }
}

Usage (local Dev key from Render appsettings.Development.json):

csharp
string body =
    "{\"sheets\":[{\"name\":\"Orders\",\"headers\":[\"Id\",\"Customer\",\"Total\"],"
    + "\"rows\":[[1,\"Acme\",120.5],[2,\"Beta\",80]]}]}";

// 2.0–4.0:
OxyKitRenderFw20.RenderTableHtml(
    "http://localhost:5066", "dev-local-key", body, @"C:\temp\orders.html");

// 4.5+:
await OxyKitRenderFw45.RenderTableHtmlAsync(
    "http://localhost:5066", "dev-local-key", body, @"C:\temp\orders.html");

Other transforms: change the path (render/html/pdf, render/table/pdf, render/template/pdf, …) and the JSON shape. See each product README / OpenAPI.


When to use the typed clients instead ​

HostRecommendation
.NET Framework 2.0–4.0This HOW-TO — 2.0 sample
.NET Framework 4.5–4.8This HOW-TO — 4.5 sample (raw HTTP; Client NuGet still not for Framework)
.NET Framework 4.6.2+ laterPossible same-package netstandard2.0 multi-target — not shipped yet
.NET 10+OxyKit.Mail.Client / OxyKit.Render.Client

Smoke without code ​

Import each product’s hoppscotch/ collection + Local env, select OxyKit.Mail Local or OxyKit.Render Local, run folders in order. Match headers and bodies from those requests when debugging Framework callers.

Render · Mail · Flow