如何在 .NET 中表示 URN(统一资源名称),以便 Equals 按预期工作

Posted

技术标签:

【中文标题】如何在 .NET 中表示 URN(统一资源名称),以便 Equals 按预期工作【英文标题】:How do I represent URNs (Uniform Resource Names) in .NET so that Equals works as expected 【发布时间】:2018-12-19 11:15:48 【问题描述】:

RFC2141 提及:

    词法等价的例子

    以下 URN 比较突出了词法等价性 定义:

        1- URN:foo:a123,456
        2- urn:foo:a123,456
        3- urn:FOO:a123,456
        4- urn:foo:A123,456
        5- urn:foo:a123%2C456
        6- URN:FOO:a123%2c456
    

    URN 1、2 和 3 在词法上都是等价的。

随后的 RFC8141 保留了该等价性:

2.1。命名空间标识符 (NID)

NID 不区分大小写(例如,“ISBN”和“isbn”是等价的)。

我可以在 .NET 框架中轻松找到的最接近 URN 的表示形式是 URI class。但是,它似乎并不完全尊重 RFC 对等价的定义:

    [TestMethod]
    public void TestEquivalentUrnsAreBroken()
    
        Assert.AreEqual(
            new Uri("URN:foo:a123,456"),
            new Uri("urn:foo:a123,456"));

        Assert.AreEqual(
            new Uri("urn:foo:a123,456"),
            new Uri("urn:FOO:a123,456"));
    

在上面的代码示例中,第一个断言按预期工作,而第二个断言失败。

是否有任何合理的方法让 URI 类尊重等价定义?还有其他我应该使用的课程吗?

请注意,我找到了URN class,但文档中提到不应该直接使用它。

【问题讨论】:

【参考方案1】:

Uri 类不支持开箱即用的urn: 方案的特定解析器。也许可以理解,因为即使 NID 的比较规则指定它不区分大小写,比较两个 NSS 的规则也将取决于特定命名空间定义的规则,每个 RFC 8141。

对于快速而肮脏的方法,您可以尝试使用Uri.Compare() 方法。如果两个 URI 相等,它将返回零,否则返回非零。

var u1 = new Uri("URN:foo:a123,456");
var u2 = new Uri("urn:foo:a123,456");
var u3 = new Uri("urn:FOO:a123,456");
var u4 = new Uri("urn:nope:a123,456");

Console.WriteLine(Uri.Compare(u1, u2, UriComponents.AbsoluteUri, UriFormat.SafeUnescaped, StringComparison.OrdinalIgnoreCase)); // 0
Console.WriteLine(Uri.Compare(u1, u3, UriComponents.AbsoluteUri, UriFormat.SafeUnescaped, StringComparison.OrdinalIgnoreCase)); // 0
Console.WriteLine(Uri.Compare(u2, u3, UriComponents.AbsoluteUri, UriFormat.SafeUnescaped, StringComparison.OrdinalIgnoreCase)); // 0
Console.WriteLine(Uri.Compare(u3, u4, UriComponents.AbsoluteUri, UriFormat.SafeUnescaped, StringComparison.OrdinalIgnoreCase)); // -8

对于更冒险的方法,您可以按照以下方式进行操作。这需要仔细考虑才能正确实施。这段代码并不是要按原样使用,而是作为一个起点。

using System;
using System.Text.RegularExpressions;
                    
public class Program

    public static void Main()
    
        var u1 = new Urn("URN:foo:a123,456");
        var u2 = new Urn("urn:foo:a123,456");
        var u3 = new Urn("urn:foo:a123,456");
        var u4 = new Urn("urn:FOO:a123,456");
        var u5 = new Urn("urn:not-this-one:a123,456");
        Console.WriteLine(u1 == u2); // True
        Console.WriteLine(u3 == u4); // True
        Console.WriteLine(u4 == u5); // False
    

    public class Urn : Uri
    
        public const string UrnScheme = "urn";
        private const RegexOptions UrnRegexOptions = RegexOptions.Singleline | RegexOptions.CultureInvariant;
        private static Regex UrnRegex = new Regex("^urn:(?<NID>[a-z|A-Z][a-z|A-Z|-]0,30[a-z|A-Z]):(?<NSS>.*)$", UrnRegexOptions);

        public string NID  get; 
        public string NSS  get; 

        public Urn(string s) : base(s, UriKind.Absolute)
        
            if (this.Scheme != UrnScheme) throw new FormatException($"URN scheme must be 'UrnScheme'.");
            var match = UrnRegex.Match(this.AbsoluteUri);
            if (!match.Success) throw new FormatException("URN's NID is invalid.");
            NID = match.Groups["NID"].Value;
            NSS = match.Groups["NSS"].Value;
        

        public override bool Equals(object other)
        
            if (ReferenceEquals(other, this)) return true;
            return
                other is Urn u &&
                string.Equals(NID, u.NID, StringComparison.InvariantCultureIgnoreCase) &&
                string.Equals(NSS, u.NSS, StringComparison.Ordinal);
        

        public override int GetHashCode() => base.GetHashCode();

        public static bool operator == (Urn u1, Urn u2)
        
            if (ReferenceEquals(u1, u2)) return true;
            if (ReferenceEquals(u1, null) || ReferenceEquals(u2, null)) return false;
            return u1.Equals(u2);
        

        public static bool operator != (Urn u1, Urn u2)
        
            if (ReferenceEquals(u1, u2)) return false;
            if (ReferenceEquals(u1, null) || ReferenceEquals(u2, null)) return true;
            return !u1.Equals(u2);
        
    

【讨论】:

以上是关于如何在 .NET 中表示 URN(统一资源名称),以便 Equals 按预期工作的主要内容,如果未能解决你的问题,请参考以下文章

URL基本结构

URI URL URN 理解

C++学习(二九七)Android的URI URL URN Uri

URI URL URN

Spring Boot 26 -- URI 和 URL、URN 的区别

URIURLURN