在 C# 中验证 URL 比 try-catch 更好的方法?

Posted

技术标签:

【中文标题】在 C# 中验证 URL 比 try-catch 更好的方法?【英文标题】:A better way to validate URL in C# than try-catch? 【发布时间】:2011-03-14 19:50:26 【问题描述】:

我正在构建一个应用程序来从互联网上检索图像。即使它工作正常,但在应用程序中使用 try-catch 语句时它很慢(在错误的 URL 上)。

(1) 这是验证 URL 和处理错误输入的最佳方法 - 还是我应该改用正则表达式(或其他方法)?

(2) 为什么我没有在文本框中指定http://,应用程序会尝试在本地查找图片?

private void btnGetImage_Click(object sender, EventArgs e)

    String url = tbxImageURL.Text;
    byte[] imageData = new byte[1];

    using (WebClient client = new WebClient())
    
        try
        
            imageData = client.DownloadData(url);
            using (MemoryStream ms = new MemoryStream(imageData))
            
                try
                
                    Image image = Image.FromStream(ms);
                    pbxUrlImage.Image = image;
                
                catch (ArgumentException)
                
                    MessageBox.Show("Specified image URL had no match", 
                        "Image Not Found", MessageBoxButtons.OK, 
                        MessageBoxIcon.Error);
                
            
        
        catch (ArgumentException)
        
            MessageBox.Show("Image URL can not be an empty string", 
                "Empty Field", MessageBoxButtons.OK, 
                MessageBoxIcon.Information);
        
        catch (WebException)
        
            MessageBox.Show("Image URL is invalid.\nStart with http:// " +
                "and end with\na proper image extension", "Not a valid URL",
                MessageBoxButtons.OK, MessageBoxIcon.Information);
        
     // end of outer using statement
 // end of btnGetImage_Click

编辑: 我尝试了 Panagiotis Kanavos 建议的解决方案(感谢您的努力!),但如果用户输入 http://,它只会在 if-else 语句中被捕获,仅此而已。更改为 UriKind.Absolute 也会捕获空字符串!越来越近 :) 目前的代码:

private void btnGetImage_Click(object sender, EventArgs e)

    String url = tbxImageURL.Text;
    byte[] imageData = new byte[1];
    Uri myUri;

    // changed to UriKind.Absolute to catch empty string
    if (Uri.TryCreate(url, UriKind.Absolute, out myUri))
    
        using (WebClient client = new WebClient())
        
            try
            
                imageData = client.DownloadData(myUri);
                using (MemoryStream ms = new MemoryStream(imageData))
                
                    imageData = client.DownloadData(myUri);
                    Image image = Image.FromStream(ms);
                    pbxUrlImage.Image = image;
                
            
            catch (ArgumentException)
            
                MessageBox.Show("Specified image URL had no match",
                    "Image Not Found", MessageBoxButtons.OK, 
                    MessageBoxIcon.Error);
            
            catch (WebException)
            
                MessageBox.Show("Image URL is invalid.\nStart with http:// " +
                    "and end with\na proper image extension", 
                    "Not a valid URL",
                    MessageBoxButtons.OK, MessageBoxIcon.Information);
            
        
    
    else
    
        MessageBox.Show("The Image Uri is invalid.\nStart with http:// " +
            "and end with\na proper image extension", "Uri was not created",
            MessageBoxButtons.OK, MessageBoxIcon.Information);
    

我一定是在这里做错了什么。 :(

【问题讨论】:

您怎么知道ArgumentExceptionWebException 表示网址有问题? 这是我调试时遇到的异常。但我同意 - 从 Internet 下载的异常类型可能更多。 【参考方案1】:

我遇到了一个非常相似的案例,所以我编写了一个静态类,它可以很容易地与 xUnit 测试一起使用,以验证逻辑通过了几个案例。

用法(返回ValidationModel):

var message = UrlValidator.Validate(input).ValidationMessage;

var result = UrlValidator.Validate(input).IsValid;

ValidationModel.cs

    public class ValidationModel
    
        public const string InvalidScheme = "Invalid URI scheme.";
        public const string EmptyInputValue = "Empty input value.";
        public const string InvalidUriFormat = "Invalid URI format.";
        public const string PassedValidation = "Passed validation";
        public const string HttpScheme = "http://";
        public const string HttpsScheme = "https://";

        public bool IsValid  get; set; 
        public string ValidationMessage  get; set; 
        
    

UrlValidator.cs

    public static class UrlValidator
    
        public static ValidationModel Validate(string input)
        
            var validation = new ValidationModel();

            if (input == string.Empty)
            
                validation.IsValid = false;
                validation.ValidationMessage = ValidationModel.EmptyInputValue;
                return validation;
            

            try
            
                var uri = new Uri(input);
                var leftPart = uri.GetLeftPart(UriPartial.Scheme);

                if (leftPart.Equals(ValidationModel.HttpScheme) || leftPart.Equals(ValidationModel.HttpsScheme))
                
                    validation.IsValid = true;
                    validation.ValidationMessage = ValidationModel.PassedValidation;
                    return validation;
                
                
                validation.IsValid = false;
                validation.ValidationMessage = ValidationModel.InvalidScheme;
            
            catch (UriFormatException)
            
                validation.IsValid = false;
                validation.ValidationMessage = ValidationModel.InvalidUriFormat;
            
            
            return validation;
        
    

UrlValidatorTests.cs

    public class UrlValidatorTests
    
        [Theory]
        [InlineData("http://intel.com", true, ValidationModel.PassedValidation)]
        [InlineData("https://intel.com", true, ValidationModel.PassedValidation)]
        [InlineData("https://intel.com/index.html", true, ValidationModel.PassedValidation)]
        [InlineData("", false, ValidationModel.EmptyInputValue)]
        [InlineData("http://", false, ValidationModel.InvalidUriFormat)]
        [InlineData("//intel.com", false, ValidationModel.InvalidScheme)]
        [InlineData("://intel.com", false, ValidationModel.InvalidUriFormat)]
        [InlineData("f://intel.com", false, ValidationModel.InvalidScheme)]
        [InlineData("htttp://intel.com", false, ValidationModel.InvalidScheme)]
        [InlineData("intel.com", false, ValidationModel.InvalidUriFormat)]
        [InlineData("ftp://intel.com", false, ValidationModel.InvalidScheme)]
        [InlineData("http:intel.com", false, ValidationModel.InvalidUriFormat)]
        public void Validate_Input_ExpectedResult(string input, bool expectedResult, string expectedInvalidMessage)
        
            //Act
            var result = UrlValidator.Validate(input);

            //Assert
            Assert.Equal(expectedResult, result.IsValid);
            Assert.Equal(expectedInvalidMessage, result.ValidationMessage);
        
    

测试结果:

【讨论】:

【参考方案2】:

我想检查 url 是否还包含域扩展,它必须是有效的网站 url。

这是我想出的:

 public static bool IsValidUrl(string url)
        
            if (string.IsNullOrEmpty(url))  return false;

            if (!url.StartsWith("http://"))
            
                url = "http://" + url;    
            

            Uri outWebsite;

            return Uri.TryCreate(url, UriKind.Absolute, out outWebsite) && outWebsite.Host.Replace("www.", "").Split('.').Count() > 1 && outWebsite.HostNameType == UriHostNameType.Dns && outWebsite.Host.Length > outWebsite.Host.LastIndexOf(".") + 1 && 255 >= url.Length;
        

我已经用 linqpad 测试过代码:

    void Main()

        // Errors
        IsValidUrl("www.google/cookie.png").Dump();
        IsValidUrl("1234").Dump();
        IsValidUrl("abcdef").Dump();
        IsValidUrl("abcdef/test.png").Dump();
        IsValidUrl("www.org").Dump();
        IsValidUrl("google").Dump();
        IsValidUrl("google.").Dump();
        IsValidUrl("google/test").Dump();
        IsValidUrl("User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:17.0) Gecko/20100101 Firefox/17.0").Dump();
        IsValidUrl("</script><script>alert(9)</script>").Dump();
        IsValidUrl("Accept: application/json, text/javascript, */*; q=0.01").Dump();
        IsValidUrl("DNT: 1").Dump();

        Environment.NewLine.Dump();

        // Success
        IsValidUrl("google.nl").Dump();
        IsValidUrl("www.google.nl").Dump();
        IsValidUrl("http://google.nl").Dump();
        IsValidUrl("http://www.google.nl").Dump();

结果:

假 假 假 假 假 假 假 假 假 假 假 假的

真真真真假

【讨论】:

【参考方案3】:

仅当您的 url 字符串是有效的 URL 时,才使用 Uri.TryCreate 创建新的 Uri 对象。如果字符串不是有效的 URL,TryCreate 将返回 false。

string myString = "http://someUrl";
Uri myUri;
if (Uri.TryCreate(myString, UriKind.RelativeOrAbsolute, out myUri))

    //use the uri here

更新

TryCreate 或 Uri 构造函数将愉快地接受可能看起来无效的字符串,例如“Host: www.***.com”、“Host:%20www.***.com”或“chrome:about”。事实上,这些都是完全有效的 URI,它们指定了自定义方案而不是“http”。

Uri.Scheme 属性的文档提供了更多示例,例如“gopher:”(有人记得吗?)、“news”、“mailto”、“uuid”。

应用程序可以将自己注册为自定义协议处理程序,如 MSDN 或其他 SO 问题中所述,例如 How do I register a custom URL protocol in Windows?

TryCreate 不提供将自身限制为特定方案的方法。代码需要检查 Uri.Scheme 属性以确保它包含可接受的值

更新 2

传递像"&gt;&lt;/script&gt;&lt;script&gt;alert(9)&lt;/script&gt; 这样的奇怪字符串将返回true 并构造一个相对的Uri 对象。不过,调用 Uri.IsWellFormedOriginalString 将返回 false。因此,如果您想确保相对 Uris 的格式正确,您可能需要致电 IsWellFormedOriginalString

另一方面,在这种情况下,用UriKind.Absolute 调用TryCreate 将返回false。

有趣的是,Uri.IsWellFormedUriString 在内部调用 TryCreate,如果创建了相对 Uri,则返回 IsWellFormedOriginalString 的值。

【讨论】:

感谢您的快速回答! BR 如果传入诸如“主机:www.***.com”之类的标头字符串,则会失败。我的意思是失败,无法将其检测为无效的 URL 这不是失败。 "Host: www.***.com" 或等效的 "Host:%20www.***.com" 是有效的 URI,其方案是 "host",就像 "chrome:about" 是一个有效的 URI。这是指定自定义 URL 协议的方式。有很多关于如何使用自定义协议的QA,例如***.com/questions/80650/… 如果 url ="\">",结果为真? 在这种情况下,结果是true,但是当您调用myUri.IsWellFormedOriginalString()时,生成的relative uri 将返回false【参考方案4】:

使用它.....

string myString = http//:google.com;
Uri myUri;
Uri.TryCreate(myString, UriKind.RelativeOrAbsolute, out myUri);
 if (myUri.IsAbsoluteUri == false)
 
  MessageBox.Show("Please Input Valid Feed Url");
 

【讨论】:

@ShivamShrivastava。伟大的!如果我有 10 条记录,并且它包含工作和非工作 url,我怎样才能只打印 gridview 或数据表中的非工作 url? 这仅测试有效 URL 是否是有效的相对 URL,并且仍然会传递许多无效输入。【参考方案5】:

你可以使用Uri.TryCreate函数Panagiotis Kanavos,如果你想测试和创建一个url,或者你可以使用Uri.IsWellFormedUriString函数Todd Menier,如果你只是想测试Url的有效性。如果您现在只是验证用户输入并且需要在应用程序生命周期的某个时间后创建 url,这可以很方便。

**但是我的帖子是为人民写的,就像我自己一样 :( ,他们仍然对 .net 1.1 大发雷霆 **

上述两种方法都是在.net 2.0 中引入的,所以你们还是得使用try catch 方法,在我看来,这仍然比使用正则表达式好得多。

private bool IsValidHTTPURL(string url)

    bool result = false;

    try
    
        Uri uri = new Uri(url);

        result = (uri.Scheme == "http" || uri.Scheme == "https");
    
    catch (Exception ex) 
     
        log.Error("Exception while validating url", ex); 
    

    return result;

【讨论】:

【参考方案6】:

使用 Uri 测试有效 URL 失败的一些示例

Uri myUri = null;
if (Uri.TryCreate("Host: www.***.com", UriKind.Absolute, out myUri))



  myUri = null;
if (Uri.TryCreate("Accept: application/json, text/javascript, */*; q=0.01", UriKind.Absolute, out myUri))



  myUri = null;
if (Uri.TryCreate("User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:17.0) Gecko/20100101 Firefox/17.0", UriKind.Absolute, out myUri))



  myUri = null;
if (Uri.TryCreate("DNT: 1", UriKind.Absolute, out myUri))


在验证上述内容后,我很惊讶所有这些废话都出现在我的列表视图中。但这一切都通过了验证测试。

现在我在上述验证后添加以下内容

url = url.ToLower();
if (url.StartsWith("http://") || url.StartsWith("https://")) return true;

【讨论】:

除了有效的 uri 可以以任何方案开头,如 ftp 或文件,甚至是 URN。你不会抓住那些。此外,MS 没有声称TryCreate 需要有效的 INPUT,相反,它最好接受无意义的输入并仍然使一些有用的东西。使用 IsWellFormedOriginalString 进行 RFC 级别验证。虽然它只适用于绝对 uris。【参考方案7】:

您好验证 https http,ftp,sftp,ftps,任何以 www 开头的东西。

string regular = @"^(ht|f|sf)tp(s?)\:\/\/[0-9a-zA-Z]([-.\w]*[0-9a-zA-Z])*(:(0-9)*)*(\/?)([a-zA-Z0-9\-\.\?\,\'\/\\\+&amp;%\$#_]*)?$";
string regular123 = @"^(www.)[0-9a-zA-Z]([-.\w]*[0-9a-zA-Z])*(:(0-9)*)*(\/?)([a-zA-Z0-9\-\.\?\,\'\/\\\+&amp;%\$#_]*)?$";

string myString = textBox1.Text.Trim();
if (Regex.IsMatch(myString, regular))

    MessageBox.Show("It is valide url  " + myString);

else if (Regex.IsMatch(myString, regular123))

    MessageBox.Show("Valide url with www. " + myString);

else 

    MessageBox.Show("InValide URL  " + myString);

【讨论】:

这不应该用在现实世界的代码中,大多数无效的 URI 会通过,大多数有效的 URI 会失败(如“***.com”、“mailto:x”、“urn:test”和任何相对 URI)。【参考方案8】:

我的解决方案:

string regular = @"^(ht|f|sf)tp(s?)\:\/\/[0-9a-zA-Z]([-.\w]*[0-9a-zA-Z])*(:(0-9)*)*(\/?)([a-zA-Z0-9\-\.\?\,\'\/\\\+&amp;%\$#_]*)?$";
string myString = textBox1.Text.Trim();
if (Regex.IsMatch(myString, regular))

    MessageBox.Show("it is valide url  " + myString);

else

    MessageBox.Show("InValide url  " + myString);

【讨论】:

【参考方案9】:

或者这个源码好图有效优化:

 public static string ValidateImage(string absoluteUrl,string defaultUrl)
         
           Uri myUri=null; 
           if (Uri.TryCreate(absoluteUrl, UriKind.Absolute, out myUri))
            
                using (WebClient client = new WebClient())
                
                    try
                    
                        using (Stream stream = client.OpenRead(myUri))
                        
                            Image image = Image.FromStream(stream);
                            return (image != null) ? absoluteUrl : defaultUrl;
                        
                    
                    catch (ArgumentException)
                    
                        return defaultUrl;
                    
                    catch (WebException)
                    
                        return defaultUrl;
                    
                
            
            else
            
                return defaultUrl;
            
        

Sou and demo asp.net mvc source image created:

<img src="@ValidateImage("http://example.com/demo.jpg","nophoto.png")"/>

【讨论】:

【参考方案10】:

一个捷径是使用Uri.IsWellFormedUriString:

if (Uri.IsWellFormedUriString(myURL, UriKind.RelativeOrAbsolute))
...

【讨论】:

如果您只是想检查 url,那就更好了。 TryCreate 还将创建一个 Uri 以在同一步骤中使用。 这是对实际提出的问题的最佳答案。干得好。 其实IsWellFormedUriString实际上调用了TryCreate来检查url是否有效,然后检查返回的uri是否从uri.IsWellFormedOriginalString()返回true。 这仅适用于绝对 URI,对于相对 URI,此方法通常会返回误报。但这与文档一致,文档解释了它仅适用于绝对 URI。

以上是关于在 C# 中验证 URL 比 try-catch 更好的方法?的主要内容,如果未能解决你的问题,请参考以下文章

C#中关于try-catch的异常捕获问题

为什么C#中应该用Try-Catch?

C# 无法避免嵌套的 try-catch

C#中如何处理异常?怎么使用try-catch语句?

C# try-catch

编写高质量代码改善C#程序的157个建议——建议64:为循环增加Tester-Doer模式而不是将try-catch置于循环内