C # Come posso verificare se un URL esiste / è valido?


117

Sto realizzando un semplice programma in visual c # 2005 che cerca un simbolo di borsa su Yahoo! Finance, scarica i dati storici e quindi traccia la cronologia dei prezzi per il simbolo ticker specificato.

Conosco l'URL esatto di cui ho bisogno per acquisire i dati e se l'utente inserisce un simbolo ticker esistente (o almeno uno con dati su Yahoo! Finance) funziona perfettamente. Tuttavia, ho un errore di runtime se l'utente compone un simbolo ticker, poiché il programma tenta di estrarre dati da una pagina Web inesistente.

Sto usando la classe WebClient e sto usando la funzione DownloadString. Ho esaminato tutte le altre funzioni membro della classe WebClient, ma non ho visto nulla che potessi usare per testare un URL.

Come posso fare questo?


1
aggiornato per mostrare l'utilizzo di C # 2.0 (VS2005)
Marc Gravell

Risposte:


110

Potresti inviare una richiesta "HEAD" piuttosto che "GET"?

(modifica) - lol! Sembra che l'abbia già fatto prima ! cambiato in wiki per evitare accuse di guadagno di reputazione. Quindi, per testare un URL senza il costo del download del contenuto:

// using MyClient from linked post
using(var client = new MyClient()) {
    client.HeadOnly = true;
    // fine, no content downloaded
    string s1 = client.DownloadString("http://google.com");
    // throws 404
    string s2 = client.DownloadString("http://google.com/silly");
}

Si farebbe try/ catchintorno al DownloadStringper controllare gli errori; nessun errore? Esiste...


Con C # 2.0 (VS2005):

private bool headOnly;
public bool HeadOnly {
    get {return headOnly;}
    set {headOnly = value;}
}

e

using(WebClient client = new MyClient())
{
    // code as before
}

FWIW - Non sono sicuro che questo risolva davvero il problema (a parte forse un diverso comportamento lato client) poiché stai semplicemente cambiando il metodo HTTP. La risposta del server dipenderà fortemente da come la logica è codificata e potrebbe non funzionare bene per un servizio dinamico come il prezzo delle azioni. Per le risorse statiche (ad esempio immagini, file, ecc.) HEAD di solito funziona come pubblicizzato poiché è integrato nel server. Molti programmatori non fanno esplicitamente richieste HEAD poiché il focus è normalmente su POST e GET. YMMV
David Taylor

Scusa per aver impiegato così tanto tempo per scegliere una risposta ... Sono stato distratto dalla scuola e dal lavoro e mi sono quasi dimenticato di questo post. Come nota a margine, non sono riuscito a far funzionare la tua soluzione perché sto usando Visual Studio 2005 che non ha il tipo "var". Non lavoro a questo progetto da mesi, ma c'è una soluzione semplice per questo fatto? Inoltre, quando ho provato a implementare la tua soluzione, ricordo di essermi arrabbiato con me per aver cercato di definire la proprietà HeadOnly senza codice nelle definizioni "get" e "set". O forse stavo solo facendo qualcosa di sbagliato. Grazie per l'aiuto però!
Daniel Waltrip

Cos'è MyClient ?
Kiquenet

@Kiquenet c'è un link nel corpo, a qui: stackoverflow.com/questions/153451/...
Marc Gravell

136

Ecco un'altra implementazione di questa soluzione:

using System.Net;

///
/// Checks the file exists or not.
///
/// The URL of the remote file.
/// True : If the file exits, False if file not exists
private bool RemoteFileExists(string url)
{
    try
    {
        //Creating the HttpWebRequest
        HttpWebRequest request = WebRequest.Create(url) as HttpWebRequest;
        //Setting the Request method HEAD, you can also use GET too.
        request.Method = "HEAD";
        //Getting the Web Response.
        HttpWebResponse response = request.GetResponse() as HttpWebResponse;
        //Returns TRUE if the Status code == 200
        response.Close();
        return (response.StatusCode == HttpStatusCode.OK);
    }
    catch
    {
        //Any exception will returns false.
        return false;
    }
}

Da: http://www.dotnetthoughts.net/2009/10/14/how-to-check-remote-file-exists-using-c/


2
Sto usando questo codice per verificare se esistono molte immagini ed è piuttosto lento (un paio di secondi per URL). Qualcuno sa se questo è un problema con questo codice o solo un dato di fatto quando si effettuano questi tipi di chiamate?
ssmith

@ssmith Un modo per velocizzare il codice è eseguire il controllo in un ciclo Parallel.Foreach se non lo avessi ancora provato. Ha reso la mia app di test degli URL MOLTO più veloce.
Jack Fairfield

3
Questa roba genera DisposedObject in cambio (response.StatusCode == HttpStatusCode.OK); wrap in using
Lapenkov Vladimir

1
C'è un problema con il codice sopra. se rispondi.Chiudi (); quindi non puoi controllare la risposta.StatusCode poiché è vicino genererà un'eccezione.
Rinascita

@ssmith qualche metodo molto più veloce?
Kiquenet

36

Queste soluzioni sono abbastanza buone, ma stanno dimenticando che potrebbero esserci altri codici di stato oltre a 200 OK. Questa è una soluzione che ho utilizzato negli ambienti di produzione per il monitoraggio dello stato e simili.

Se c'è un reindirizzamento URL o qualche altra condizione sulla pagina di destinazione, il ritorno sarà vero utilizzando questo metodo. Inoltre, GetResponse () genererà un'eccezione e quindi non otterrai uno StatusCode per questo. È necessario intercettare l'eccezione e verificare la presenza di un ProtocolError.

Qualsiasi codice di stato 400 o 500 restituirà false. Tutti gli altri tornano veri. Questo codice può essere facilmente modificato in base alle proprie esigenze per codici di stato specifici.

/// <summary>
/// This method will check a url to see that it does not return server or protocol errors
/// </summary>
/// <param name="url">The path to check</param>
/// <returns></returns>
public bool UrlIsValid(string url)
{
    try
    {
        HttpWebRequest request = HttpWebRequest.Create(url) as HttpWebRequest;
        request.Timeout = 5000; //set the timeout to 5 seconds to keep the user from waiting too long for the page to load
        request.Method = "HEAD"; //Get only the header information -- no need to download any content

        using (HttpWebResponse response = request.GetResponse() as HttpWebResponse)
        {
            int statusCode = (int)response.StatusCode;
            if (statusCode >= 100 && statusCode < 400) //Good requests
            {
                return true;
            }
            else if (statusCode >= 500 && statusCode <= 510) //Server Errors
            {
                //log.Warn(String.Format("The remote server has thrown an internal error. Url is not valid: {0}", url));
                Debug.WriteLine(String.Format("The remote server has thrown an internal error. Url is not valid: {0}", url));
                return false;
            }
        }
    }
    catch (WebException ex)
    {
        if (ex.Status == WebExceptionStatus.ProtocolError) //400 errors
        {
            return false;
        }
        else
        {
            log.Warn(String.Format("Unhandled status [{0}] returned for url: {1}", ex.Status, url), ex);
        }
    }
    catch (Exception ex)
    {
        log.Error(String.Format("Could not test url {0}.", url), ex);
    }
    return false;
}

1
Vorrei aggiungere che alcuni codici di stato nell'intervallo 3xx causeranno effettivamente un errore, ad esempio 304 Non modificato, nel qual caso dovresti
gestirlo

3
Ho appena avuto un problema con questo approccio: HttpWebRequestnon gli piace se non fai .Close()l' responseoggetto prima di provare a scaricare qualcos'altro. Ci sono volute ore per trovarlo!
jbeldock

4
HttpWebResponseoggetto dovrebbe essere racchiuso in un usingblocco poiché implementa il IDisposableche garantirà anche la chiusura della connessione. Ciò potrebbe causare problemi come ha dovuto affrontare @jbeldock.
Habib

2
Sta lanciando 404 Not Founds su URL che funzionano bene in un browser ...?
Michael Tranchida

@MichaelTranchida I server Web sono notoriamente noti per 404 quando si emette un metodo che non è supportato. Nel tuo caso Headpotrebbe non essere supportato su quella risorsa, anche se Getpotrebbe esserlo. Avrebbe dovuto invece lanciare 405.
Sriram Sakthivel

9

Se capisco correttamente la tua domanda, potresti utilizzare un piccolo metodo come questo per darti i risultati del tuo test URL:

WebRequest webRequest = WebRequest.Create(url);  
WebResponse webResponse;
try 
{
  webResponse = webRequest.GetResponse();
}
catch //If exception thrown then couldn't get response from address
{
  return 0;
} 
return 1;

È possibile racchiudere il codice precedente in un metodo e utilizzarlo per eseguire la convalida. Spero che questo risponda alla domanda che stavi facendo.


1
Sì, forse puoi perfezionare la soluzione distinguendo tra i diversi casi (errore di connessione TCP - l'host rifiuta la connessione, 5xx - È successo qualcosa di fatale, 404 - Risorsa non trovata ecc.). Dai un'occhiata alla proprietà Status di WebException;)
David Taylor,

Ottimo punto David! Questo ci darebbe un feedback più dettagliato in modo da poter gestire l'errore in modo più astuto.
Calendar Software

1
Grazie. Il punto è che ci sono diversi livelli in questa cipolla, ognuno dei quali può gettare una chiave nel lavoro (.Net Framework, Risoluzione DNS, Connettività TCP, Server Web di destinazione, applicazione di destinazione, ecc.). Secondo me un buon progetto dovrebbe essere in grado di discriminare tra le diverse condizioni di guasto per fornire feedback informativo e diagnostica utilizzabile. Non dimentichiamo inoltre che HTTP ha codici di stato per un motivo;)
David Taylor,

6

Prova questo (assicurati di utilizzare System.Net):

public bool checkWebsite(string URL) {
   try {
      WebClient wc = new WebClient();
      string HTMLSource = wc.DownloadString(URL);
      return true;
   }
   catch (Exception) {
      return false;
   }
}

Quando la funzione checkWebsite () viene chiamata, cerca di ottenere il codice sorgente dell'URL passato al suo interno. Se ottiene il codice sorgente, restituisce true. In caso contrario, restituisce false.

Esempio di codice:

//The checkWebsite command will return true:
bool websiteExists = this.checkWebsite("https://www.google.com");

//The checkWebsite command will return false:
bool websiteExists = this.checkWebsite("https://www.thisisnotarealwebsite.com/fakepage.html");

3

Ecco un'altra opzione

public static bool UrlIsValid(string url)
{
    bool br = false;
    try {
        IPHostEntry ipHost = Dns.Resolve(url);
        br = true;
    }
    catch (SocketException se) {
        br = false;
    }
    return br;
}

3
Potrebbe essere utile per verificare se esiste un host. La domanda ovviamente non è preoccupata se l'host esiste o meno. Si occupa di gestire un percorso HTTP errato dato che l'host è noto per esistere e funzionare bene .
binki

3

Questa soluzione sembra facile da seguire:

public static bool isValidURL(string url) {
    WebRequest webRequest = WebRequest.Create(url);
    WebResponse webResponse;
    try
    {
        webResponse = webRequest.GetResponse();
    }
    catch //If exception thrown then couldn't get response from address
    {
        return false ;
    }
    return true ;
}

1
non dimenticare di chiudere webResponse, altrimenti il ​​tempo di risposta aumenterà ogni volta che chiami il tuo metodo
Madagaga

3
WebRequest request = WebRequest.Create("http://www.google.com");
try
{
     request.GetResponse();
}
catch //If exception thrown then couldn't get response from address
{
     MessageBox.Show("The URL is incorrect");`
}

1
Per favore aggiungi qualche spiegazione alla tua risposta. Le risposte di solo codice tendono a creare confusione e non sono utili ai lettori futuri e possono attirare voti negativi in ​​questo modo.
Jesse

2

ho un modo più semplice per determinare se un URL è valido.

if (Uri.IsWellFormedUriString(uriString, UriKind.RelativeOrAbsolute))
{
   //...
}

4
No, questo metodo non controlla se l'URL è realmente accessibile. Restituisce vero anche quando Uri.IsWellFormedUriString (" 192.168.1.421 ", ...), che utilizza un URL ovviamente errato
zhaorufei

2

Ho sempre riscontrato che le eccezioni sono molto più lente da gestire.

Forse un modo meno intenso produrrebbe un risultato migliore e più veloce?

public bool IsValidUri(Uri uri)
{

    using (HttpClient Client = new HttpClient())
    {

    HttpResponseMessage result = Client.GetAsync(uri).Result;
    HttpStatusCode StatusCode = result.StatusCode;

    switch (StatusCode)
    {

        case HttpStatusCode.Accepted:
            return true;
        case HttpStatusCode.OK:
            return true;
         default:
            return false;
        }
    }
}

Quindi usa:

IsValidUri(new Uri("http://www.google.com/censorship_algorithm"));

1

I server Web rispondono con un codice di stato HTTP che indica il risultato della richiesta, ad esempio 200 (a volte 202) significa successo, 404 - non trovato ecc. (Vedere qui ). Supponendo che la parte dell'indirizzo del server dell'URL sia corretta e non stai ricevendo un timeout del socket, l'eccezione molto probabilmente ti dice che il codice di stato HTTP era diverso da 200. Suggerirei di controllare la classe dell'eccezione e vedere se l'eccezione porta il codice di stato HTTP.

IIRC: la chiamata in questione genera un'eccezione WebException o un discendente. Controllare il nome della classe per vedere quale e racchiudere la chiamata in un blocco try per intercettare la condizione.


2
In realtà, qualsiasi cosa nella gamma 200-299 significa successo, IIRC
Marc Gravell

Marc, hai assolutamente ragione. Ho intenzionalmente evitato di entrare nel concetto di "classe di errore" (ad esempio 5xx, 4xx, 3xx, 2xx ecc.) Poiché questo apre un'intera altra lattina di worm. Anche la gestione dei codici standard (200, 302, 404, 500 ecc.) È molto meglio che ignorare completamente i codici.
David Taylor

1

Seguendo gli esempi già forniti, direi che è buona norma racchiudere anche la risposta in un utilizzo come questo

    public bool IsValidUrl(string url)
    {
         try
         {
             var request = WebRequest.Create(url);
             request.Timeout = 5000;
             request.Method = "HEAD";

             using (var response = (HttpWebResponse)request.GetResponse())
             {
                response.Close();
                return response.StatusCode == HttpStatusCode.OK;
            }
        }
        catch (Exception exception)
        { 
            return false;
        }
   }
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.