Toto je dokončení první části příspěvku.
Minule jsme si připravili metodu pro čtení textového souboru po jednotlivých řádcích a metodu pro umožnění načtení hlavičky souboru ještě předtím, než začneme procházet a zpracovávat řádky ostatní. Pro vyřešení našeho konkrétního zadání potřebujeme ještě umět to samé, ale pro soubor umístěný někde na webu.
Začneme nejprve pouze zjištěním co a jak je potřeba všechno udělat pro vlastní download souboru:
var request = WebRequest.Create(url);
//If required by the server, set the credentials.
request.Credentials = CredentialCache.DefaultCredentials;
using (var response = request.GetResponse())
{
using (var reader = new StreamReader(response.GetResponseStream(), encoding))
{
//Read response stream
}
}
V zásadě potřebujeme vytvořit objekt WebRequest, pro předané url. Pak případně nastavit potřebná práva (zde by jsme mohli předpokládat pouze taková použití naší třídy, kdy nejsou pro stažení souboru žádné certifikáty, hesla a podobné záležitosti vyžadovány – v takových případech by jsme asi použili objekt WebRequest přímo). Získáme response a dále jeho obsah v podobě streamu, který pro určený encoding načteme. Po ukončení čtení je potřeba stream i response uzavřít.
Část pro čtení obsahu souboru pro předaný objekt StreamReader (implementaci enumerátoru) již máme připravenou z minula, nyní ale potřebujeme, aby byl kromě readeru uzavřen i objekt WebResponse. K tomu si napíšeme jednoduchou pomocnou třídu, do které zároveň zapouzdříme použití web requestu, a kterou podědíme ze třídy TextReader. Třída bude private a umístěna uvnitř třídy FileUtilities.
#region member types definition
private sealed class WebResponseReader : TextReader, IDisposable
{
#region member varible and default property initialization
private WebResponse response;
private StreamReader reader;
#endregion
#region constructors and destructors
public WebResponseReader(WebRequest request, Encoding encoding)
{
this.response = request.GetResponse();
try
{
this.reader = new StreamReader(response.GetResponseStream(), encoding);
}
catch
{
response.Close();
throw;
}
}
#endregion
#region action methods
public override string ReadLine()
{
return this.reader.ReadLine();
}
#endregion
#region private member functions
protected override void Dispose(bool disposing)
{
if (disposing)
{
reader.Close();
response.Close();
}
}
#endregion
}
#endregion
Kromě metody Dispose() zde potřebujeme pouze jedinou další metodu a sice ReadLine(), protože ta bude jako jediná využívána při “podhození” tohoto objektu pomocné metodě ReadLinesInternal() (která má právě vstupní argument typu TextReader, vzpomínáte?).
Tím máme asi již vše připravené a můžeme dokončit implementace naší třídy. Nejprve metodu ReadLines() (doplníme nové dvě varianty se vstupním argumentem typu Uri):
public static IEnumerable<string> ReadLines(Uri url, Encoding encoding)
{
if (url == null)
{
throw new ArgumentNullException("url");
}
if (encoding == null)
{
throw new ArgumentNullException("encoding");
}
return ReadLinesInternal(new WebResponseReader(WebRequest.Create(url), encoding));
}
public static IEnumerable<string> ReadLines(Uri url)
{
if (url == null)
{
throw new ArgumentNullException("url");
}
return ReadLinesInternal(new WebResponseReader(WebRequest.Create(url), Encoding.UTF8));
}
A také metodu ReadHeaderAndLines():
public static IEnumerable<string> ReadHeaderAndLines(Uri url, Encoding encoding, out string header)
{
if (url == null)
{
throw new ArgumentNullException("url");
}
if (encoding == null)
{
throw new ArgumentNullException("encoding");
}
var reader = new WebResponseReader(WebRequest.Create(url), encoding);
try
{
header = reader.ReadLine();
if (header == null)
{
throw new EndOfStreamException("Header row not found");
}
}
catch
{
reader.Dispose();
throw;
}
return ReadLinesInternal(reader);
}
public static IEnumerable<string> ReadHeaderAndLines(Uri url, out string header)
{
return ReadHeaderAndLines(url, Encoding.UTF8, out header);
}
Tím je třída FileUtilities dokončena, zde naleznete její kompletní implementaci ve finální podobě.
A jak třídu použít, resp. musíme ještě splnit naše původní zadání pro stažení a zpracování (výpis) kurzů:
public static void Main()
{
const string url = "http://www.cnb.cz/cs/financni_trhy/devizovy_trh/kurzy_devizoveho_trhu/denni_kurz.txt";
string header;
var lines = FileUtilities.ReadHeaderAndLines(new Uri(url), out header);
DateTime datum = DateTime.ParseExact(header.Substring(0, 10), "dd.MM.yyyy", System.Globalization.CultureInfo.InvariantCulture);
foreach (var kurz in from line in lines.Skip(1) //Vynechání prvního řádku
let values = line.Split('|')
select new
{
Datum = datum,
KodMeny = values[3],
Zeme = values[0],
Mena = values[1],
Mnozstvi = int.Parse(values[2], System.Globalization.CultureInfo.InvariantCulture),
Kurz = double.Parse(values[4], new System.Globalization.CultureInfo("cs-CZ", false))
})
{
Console.WriteLine(kurz);
}
}
Ukázka výstupu:
{ Datum = 3.2.2011 0:00:00, KodMeny = AUD, Zeme = Austrálie, Mena = dolar, Mnozstvi = 1, Kurz = 17,74 }
{ Datum = 3.2.2011 0:00:00, KodMeny = BRL, Zeme = Brazílie, Mena = real, Mnozstvi = 1, Kurz = 10,512 }
{ Datum = 3.2.2011 0:00:00, KodMeny = BGN, Zeme = Bulharsko, Mena = lev, Mnozstvi = 1, Kurz = 12,317 }
...
(Pozn.: Všimněte si jak pro parsování hodnoty kurzu konstruujeme konkrétní kulturu “cs-CZ”. Druhý parametr v konstruktoru říká, že se nepoužijí žádné lokální nastavení z OS v případě, že je v OS nastavena stejná tj. česká kultura.)
Doufám že tento příspěvek alespoň trochu splní svůj účel a sice ukázat, že i u relativně jednoduché úlohy je dobré k řešení přistupovat zodpovědně a s rozmyslem a snažit se o dosažení kvalitního výsledku.
(Podobným tématem se také zabývá např. i tento článek, kterým byl ten můj volně inspirován.)