On this page

Choose encoding for the destination

The same customer name can appear in a search URL, a web page, an XML document or a JSON value. Each destination has different syntax. Keep the original value as application data, then encode it for the boundary where it is used. Case-insensitive source names and String comparison do not escape that data.

Destination Ordinary .NET operation What it produces
One URI component Uri.EscapeDataString Component data, with space as percent20; not a complete URL
HTML text or a quoted HTML attribute WebUtility.HtmlEncode Encoded data for those HTML contexts; not JavaScript, CSS or URI encoding
XML element/attribute content XmlWriter.WriteString / WriteAttributeString Content written with the writer's settings and structural context
A complete JSON value JsonSerializer.Serialize Serialized JSON including required delimiters
JSON or ordinary C# quoted-string contents DirectSharp JsonEscape / CSharpEscape profile Escaped contents without outer quotes; preserve readable valid Unicode

Imports and assembly references follow normal .NET rules. The fifteen selected text extensions live in opt-in DirectSharp.Text.TextExtensions. HtmlEncode uses guarded WebUtility.HtmlEncode; XmlEncode returns a checked XML element-text fragment with carriage-return entitization. The native examples below also show structural contexts that a text-fragment helper cannot construct.

A link has two separate data positions: its visible label and a name inside its query. Encode the name as query-component data, then encode the constructed address for the quoted HTML attribute. That is two different boundaries, not two passes of the same encoder.

public class CustomerMarkup
    public static action SearchLink(name as String)
        returns String
        require name is not nothing
        let component = Global.System.Uri.EscapeDataString(name)
        let address = "/customers?name=" + component + "&page=1"
        let attribute = Global.System.Net.WebUtility.HtmlEncode(address)
        let label = Global.System.Net.WebUtility.HtmlEncode(name)
        return '<a href="{{attribute}}">{{label}}</a>'
    end action
end class

For the input Ana & Bob, the resulting fragment is:

<a href="/customers?name=Ana%20%26%20Bob&amp;page=1">Ana &amp; Bob</a>

The ampersand belonging to the name becomes percent26 in the query component. The ampersand between query parameters remains URL syntax until HTML attribute encoding turns it into an HTML entity. The displayed name is independently encoded as HTML text. Encoding the whole address as one URI component would also escape the slash, question mark and parameter separators.

This action deliberately constructs an HTML fragment. A template system that already encodes text should normally receive the original name and structured link data instead. For example, encoding the already-encoded text &amp; again produces &amp;amp;. Use the template framework's normal text/attribute handling rather than mixing pre-encoded fragments into it.

Reference C# for the same application-level operation:

private static string BuildSearchLink(string name)
{
    ArgumentNullException.ThrowIfNull(name);
    string component = Uri.EscapeDataString(name);
    string address = "/customers?name=" + component + "&page=1";
    string attribute = System.Net.WebUtility.HtmlEncode(address);
    string label = System.Net.WebUtility.HtmlEncode(name);
    return $"<a href=\"{attribute}\">{label}</a>";
}

The native guard illustrates required input presence; it does not reproduce the source contract's exact diagnostic suffix/message. These are application rendering methods, not a DirectSharp helper implementation.

Let an XML writer own XML syntax

Pass data to the writer's element/attribute operations. Do not put a pre-escaped name into WriteString: the writer would escape those entity characters again.

public class CustomerXml
    public static action Write(
        output as Global.System.Xml.XmlWriter,
        name as String
    )
        require output is not nothing
        require name is not nothing
        output.WriteStartElement("customer")
        output.WriteAttributeString("name", name)
        output.WriteString(name)
        output.WriteEndElement()
    end action
end class

The caller supplies a writer ready to accept an element and retains its ownership. The action neither flushes nor disposes it. Element and attribute names are fixed program structure; the customer name is content, not an XML tag name.

The writer's settings remain significant. For exact text round trips, a caller can use NewLineHandling.Entitize so a carriage return is represented in a form the XML reader preserves. CheckCharacters controls native XML character validation; some valid Unicode scalars are still invalid XML 1.0 characters. Ordinary String storage does not impose XML's validity rules globally.

Writing is incremental. A later failure can leave partially written output and the writer's native failure state; the action supplies no rollback. A caller needing to publish only a complete document can write into a private buffer and publish it after successful completion.

Reference C# member fragment:

private static void WriteCustomerXml(System.Xml.XmlWriter output, string name)
{
    ArgumentNullException.ThrowIfNull(output);
    ArgumentNullException.ThrowIfNull(name);
    output.WriteStartElement("customer");
    output.WriteAttributeString("name", name);
    output.WriteString(name);
    output.WriteEndElement();
}

For an XML document, continue using the writer's structural methods for the surrounding elements. For an independently encoded element-text fragment, XmlEncode uses the fixed settings shown below. Its output is not an XML attribute encoder: text-mode handling does not preserve every attribute whitespace value through reader normalization. Use WriteAttributeString for attributes.

Serialize a value or escape its contents

Inside an action with a supplied String name, ordinary .NET serialization creates a complete JSON String value:

let jsonValue = Global.System.Text.Json.JsonSerializer.Serialize(name)

The serializer supplies delimiters and its own encoder/options. Serialize an application DTO for a complete object; do not manually concatenate object syntax around arbitrary property values. Serializer defaults can choose different escape spellings and non-ASCII handling from DirectSharp's selected contents profile while reconstructing the same value.

JsonEscape/CSharpEscape instead return unquoted contents under the selected strict profile. Quote/backslash/control characters are escaped, malformed UTF-16 is rejected and other valid Unicode remains readable. CSharpEscape targets an ordinary double-quoted C# String, not raw/verbatim/interpolated C# source. Neither helper supplies an HTML-script or JavaScript-expression encoding context. They are ordinary extensions in DirectSharp.Text.TextExtensions; explicitly import Global.DirectSharp.Text to use extension syntax.

Reference helper patterns

These private C# member fragments implement the selected native profiles. They assume ordinary System, System.Net, System.Text and System.Xml imports. A public library implementation must also expose the selected extension declarations; these methods remain isolated reference evidence.

private static string EncodeHtml(string source)
{
    ArgumentNullException.ThrowIfNull(source);
    return WebUtility.HtmlEncode(source);
}
private static string EncodeXmlText(string source)
{
    ArgumentNullException.ThrowIfNull(source);
    var buffer = new StringBuilder();
    var settings = new XmlWriterSettings
    {
        ConformanceLevel = ConformanceLevel.Fragment,
        OmitXmlDeclaration = true,
        CheckCharacters = true,
        NewLineHandling = NewLineHandling.Entitize
    };
    using (var writer = XmlWriter.Create(buffer, settings))
        writer.WriteString(source);
    return buffer.ToString();
}

The buffer is private: a failed WriteString does not return partial encoded text. Closing the writer completes its buffered output before ToString. It writes no wrapper element or XML declaration. Source data containing &amp; is encoded again, and A followed by CR and B becomes A&#xD;B so parsing the fragment inside an element recovers that CR.

Use the original value with WriteAttributeString for an attribute. XML readers normalize literal attribute whitespace differently from element text; an encoded element-text fragment is not interchangeable with attribute output.

Boundary checks

The isolated EncodingContextChecks fixture verifies the native C# link/writer patterns, query-versus-attribute encoding, repeated HTML encoding, XML text/attribute round trips and JSON value reconstruction. XML cases include quotes, ampersands, markup-looking text, tabs/newlines and supplementary Unicode, using caller-owned writers and explicit native settings. Invalid XML-character checks cover that native context, not a global String rule.

The additional private helper cases verify null/empty/repeated encoding, standalone XML fragment round trips, CR entitization, supplementary Unicode and native rejection of invalid XML characters/isolated surrogates. Text helper C# patterns separately check the selected JSON/C# contents profile.