Wow, this was unexpectedly unpleasant. After a few months of using C#, I'd grown quite fond of it and .NET - that fondness got a battering tonight.
. But first, the code.
1 using System;
2 using System.Collections.Generic;
3 using System.Text;
4 using System.Net;
5 using System.Web;
6 using System.IO;
7 using System.Runtime.Serialization;
8 using System.Runtime.Serialization.Formatters.Binary;
9 using StringHashTable = System.Collections.Generic.Dictionary<string, string>;
10 using StringList = System.Collections.Generic.List<string>;
11
12 namespace firstCookie
13 { 14 class HttpRequestWrapper
15 { 16 HttpWebRequest request;
17 StringHashTable postData;
18
19 public HttpRequestWrapper(string uri)
20 { 21 request = (HttpWebRequest)WebRequest.Create(uri);
22
23 }
24
25 public HttpWebResponse Post()
26 { 27 if (request.Method != "POST") request.Method = "POST";
28
29 /*Prepare data - If I haven't actually set the data,
then I deserve an exception*/
30 byte[] preppedData = prepareData(postData);
31 request.ContentType = "application/x-www-form-urlencoded";
32 request.ContentLength = preppedData.Length;
33
34 //Now POST
35 Stream postStream = request.GetRequestStream();
36 postStream.Write(preppedData, 0, preppedData.Length);
37 return (HttpWebResponse)request.GetResponse();
38 }
39
40 private byte[] prepareData(StringHashTable postData)
41 { 42 //Firstly, convert data to usual query string form.
43 string formatString = "{0}={1}"; 44 StringList pairsList = new StringList();
45 foreach (string key in postData.Keys)
46 { 47 string encodedKey = HttpUtility.UrlEncode(key);
48 string encodedValue = HttpUtility.UrlEncode(postData[key]);
49 string encodedString = String.Format(formatString,
encodedKey,
encodedValue);
50 pairsList.Add(encodedString);
51 }
52 string queryString = String.Join("&", pairsList.ToArray()); 53
54 /*Now, convert string to byte[] - it's needed in this format,
can't quite say why.*/
55 ASCIIEncoding byteEncoder = new ASCIIEncoding();
56 byte[] encodedData = byteEncoder.GetBytes(queryString);
57 return encodedData;
58 }
59
60 public StringHashTable formData
61 { 62 //Futureproof this, my Python side just wants to make postData public.
63 get { return postData; } 64 set { postData = value; } 65 }
66
67 public string Method
68 { 69 get { return request.Method; } 70 set { request.Method = value; } 71 }
72
73 public string UserAgent
74 { 75 get { return request.UserAgent; } 76 set { request.UserAgent = value; } 77 }
78
79 public CookieContainer CookieContainer
80 { 81 get { return request.CookieContainer; } 82 set { request.CookieContainer = value; } 83 }
84
85 public bool AllowAutoRedirect
86 { 87 get { return request.AllowAutoRedirect; } 88 set { request.AllowAutoRedirect = value; } 89 }
90
91 }
92
93 class RequestFunctions
94 { 95 public static HttpRequestWrapper CreateRequest(string uri, string userAgent)
96 { 97 HttpRequestWrapper req = new HttpRequestWrapper(uri);
98 req.UserAgent = userAgent;
99 req.Method = "GET";
100 return req;
101 }
102
103 public static HttpRequestWrapper CreateRequest(string uri,
StringHashTable data,
string userAgent)
104 { 105
106 HttpRequestWrapper req = CreateRequest(uri, userAgent);
107 req.Method = "POST";
108 req.formData = data;
109 return req;
110 }
111
112
113 }
114
115
116
117 class CookieFunctions
118 { 119 public static CookieCollection GetCookie(string user,
string pwd,
string uri,
string ua)
120 { 121 StringHashTable postData = new StringHashTable();
122 postData["id"] = user;
123 postData["pwd"] = pwd;
124 postData["dologin"] = "yes";
125
126 //Create request and prepare Data
127 HttpRequestWrapper request = RequestFunctions.CreateRequest(uri,
postData,
ua);
128
129 //Last tweaks to the request
130 CookieContainer ckCont = new CookieContainer();
131 request.CookieContainer = ckCont;
132 request.AllowAutoRedirect = true;
133 HttpWebResponse response = request.Post();
134 return ckCont.GetCookies(new Uri(uri));
135 }
136
137 public static void StoreCookie(CookieCollection ck, string filePath)
138 { 139 IFormatter formatter = new BinaryFormatter();
140 Stream outStream = new FileStream(filePath,
141 FileMode.Create,
142 FileAccess.Write,
143 FileShare.None);
144 formatter.Serialize(outStream, ck);
145 outStream.Close();
146 }
147
148 public static CookieCollection RetrieveCookie(string filePath)
149 { 150 IFormatter formatter = new BinaryFormatter();
151 Stream iStream = new FileStream(filePath,
152 FileMode.Open,
153 FileAccess.Read,
154 FileShare.Read);
155 CookieCollection ck = (CookieCollection)formatter.Deserialize(iStream);
156 return ck;
157 }
158
159
160
161
162
163 }
164
165
166 }
.NET offers two ways of retrieving remote websites via HTTP.
.
is very high level and handy for simple tasks; unfortunately, storing cookies isn't something it can do. This is where
comes in.
It can perform the tasks needed, but not overly well. To be honest, I was a bit appalled at how limited
was.
can't do this. The only functionality for encoding uri strings is located in
- System.Web is aimed at server side ASP.NET more than client side stuff like this and requires that you add a reference to the assembly. It's somewhat limited - it can only encode a string - so passing in mappings of names to values and getting an encoded string returned is not possible.
doesn't know how to do this. I'm sure there are very good reasons why not, but it's a bit frustrating. So, once I've taken my data, and uri encoded it, I then have to convert it to a series of bytes to POST to the server.
And this is where I decided I was going to subclass. All I wanted to add was a method to encode a generic
passed to it, and a method to handle the minor details of actually POSTing the data. And that's when I learnt about the
keyword.
has multiple constructors. The one I needed to call was marked
- that is, it can only be called by code residing in the same assembly. One of the
, but it's an obsolete one that provides no required functionality. I ended up going with the wrapper approach - which feels like an ugly hack, but will save me from replicating the same code over and over as I proceed.
t in some places - in others, I was pleased, for example, AllowAutoRedirect is a nice touch, in Python's urllib2, for example, you have no choice. Serialising objects was straightforward, and the documentation, overall, was reasonably thorough. Some more relevant examples would always be nice, but that's MSDN for you.
I'm still not happy about having to wrap a class when subclassing it would've been a lot simpler and neater - it's a bit of a horrible hack, because each time I need another property of