Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Nov 1, 2025

Uri.Query and Uri.Fragment include the leading ? and # delimiters respectively, which differs from RFC 3986's definition where query and fragment components exclude these delimiters. This behavior has existed since .NET Framework 1.1 and cannot change without breaking existing code.

Changes

  • Uri.Fragment: Added NOTE in Remarks clarifying the # delimiter is included, whereas RFC 3986 defines fragment without the delimiter
  • Uri.Query: Added NOTE in Remarks clarifying the ? delimiter is included, whereas RFC 3986 defines query without the delimiter

Context

var uri = new Uri("http://example.com/path?name=value#section");
Console.WriteLine(uri.Query);     // ?name=value (includes '?')
Console.WriteLine(uri.Fragment);  // #section (includes '#')

This documentation enhancement helps developers understand the behavior when working with URIs, particularly when integrating with systems that follow RFC 3986 strictly.

Original prompt

This section details on the original issue you should resolve

<issue_title>Uri.Query should not start with a question mark</issue_title>
<issue_description>### Description

Currently, the Query property of a Uri starts with a question mark ?:

var uri = new Uri("foo://example.com:8042/over/there?name=ferret#nose");
Console.WriteLine(uri.Query);
// Result: ?name=ferret

This is incorrect according to RFC 3986 - Uniform Resource Identifier (URI): Generic Syntax. The question mark is a delimiter, and query does not include the question mark. .NET is the outlier for how it handles Uri.Query (see below), and it's been 18 years since the RFC 3986 standard. This inconsistent usage of the question mark is likely a causative factor for the common inconsistent usage of the question mark in Azure SAS tokens. Uri.Fragment has this same issue too (it also incorrectly starts with a delimiter #).

HTTP/1.1 [RFC 3986]

1.2.3. Hierarchical Identifiers
The generic syntax uses the slash ("/"), question mark ("?"), and number sign ("#") characters to delimit components that are significant to the generic parser's hierarchical interpretation of an identifier.

3. Syntax Components

  foo://example.com:8042/over/there?name=ferret#nose
  \_/   \______________/\_________/ \_________/ \__/
   |           |            |            |        |
scheme     authority       path        query   fragment
   |   _____________________|__
  / \ /         \
  urn:example:animal:ferret:nose

Notice that in Java, Go, Python, and PHP, the canonical URI implementations do NOT include the ? in the query:

Java

import java.net.URI;
import java.net.URISyntaxException;

public class Main {
    public static void main(String[] args) {
        try {
            URI uri = new URI("foo://example.com:8042/over/there?name=ferret#nose");
            System.out.println(uri.getQuery());
        } catch (URISyntaxException e) {
            System.out.println("Error: " + e.getMessage());
        }
    }
}

Result: name=ferret

Go

package main

import (
	"fmt"
	"net/url"
)

func main() {
	uriString := "foo://example.com:8042/over/there?name=ferret#nose"
	uri, err := url.Parse(uriString)
	
	if err != nil {
		fmt.Println("Error parsing URI:", err)
		return
	}

	fmt.Println(uri.Query().Encode())
}

Result: name=ferret

Python

from urllib.parse import urlparse
url = urlparse("foo://example.com:8042/over/there?name=ferret#nose")
print(url.query)

Result: name=ferret

PHP

<?php
$url = "foo://example.com:8042/over/there?name=ferret#nose";
$query = parse_url($url, PHP_URL_QUERY);
print_r($query);
?>

Result: name=ferret

Reproduction Steps

var uri = new Uri("foo://example.com:8042/over/there?name=ferret#nose");

if (uri.Query.StartsWith('?'))
{
    throw new Exception("According to RFC 3986, a URI's query segment shall not include the question mark");
}

Expected behavior

It's expected that the question mark ? is not included in the string returned by uri.Query, so we should expect:

var uri = new Uri("foo://example.com:8042/over/there?name=ferret#nose");
Console.WriteLine(uri.Query);
// name=ferret

However, a breaking change is not the correct solution to this (please see the Proposed Solution section below).

Actual behavior

Currently, the question mark is included in uri.Query:

var uri = new Uri("foo://example.com:8042/over/there?name=ferret#nose");
Console.WriteLine(uri.Query);
// ?name=ferret

Regression?

No, Uri.Query in .NET Framework 1.1 (February 2002) pre-dates RFC 3986 (January 2005). Although it's been a considerable amount of time, .NET is the outlier for how it handles Uri.Query, and it's been 18 years since the RFC 3986 standard. This inconsistent usage of the question mark is likely a causative factor for the common inconsistent usage of the question mark in Azure SAS tokens.

Known Workarounds

uri.Query.TrimStart('?');

Configuration

No response

Other information

The root cause is here:
https://github.com/dotnet/runtime/blob/9aefa9daa141bb7d9ba3f2b373d4b050c9b243fe/src/libraries/System.Private.Uri/src/System/Uri.cs#L1091

| UriComponents.KeepDelimiter causes the delimiter to be included in both Uri.Query and Uri.Fragment.

Proposed Solution

  1. Add `Uri.Q...

💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

…tion

Co-authored-by: MihaZupan <25307628+MihaZupan@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix Uri.Query to not start with a question mark Document Uri.Query and Uri.Fragment include leading delimiters Nov 1, 2025
Copilot AI requested a review from MihaZupan November 1, 2025 06:06
Copilot finished work on behalf of MihaZupan November 1, 2025 06:06
@MihaZupan MihaZupan marked this pull request as ready for review November 1, 2025 06:10
@MihaZupan MihaZupan requested a review from a team as a code owner November 1, 2025 06:11
Copilot AI review requested due to automatic review settings November 1, 2025 06:11
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR adds clarifying documentation notes to the Uri.Fragment and Uri.Query properties in the System namespace. The changes inform users that these properties include leading delimiters (# for fragments and ? for queries), which differs from the RFC 3986 URI specification's definition of these components without delimiters.

  • Added NOTE sections to Uri.Fragment and Uri.Query property documentation
  • Clarified the inclusion of leading delimiters in property values

@MihaZupan MihaZupan merged commit 87ac518 into main Nov 3, 2025
12 checks passed
@MihaZupan MihaZupan deleted the copilot/fix-uri-query-formatting branch November 3, 2025 13:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Uri.Query should not start with a question mark

3 participants