Silverlight not actually very web friendly. It lacks of built-in support for the current HTML form post protocol. So, we have to write it our own. I have written an extension class and two serializer classes, one for normal form post (
DataContractQueryStringSerializer
) while the other for multipart upload form post (
DataContractMultiPartSerializer
).
If you only want to have the working code, just copy the code below. Detail explanation is available at
Multi-Part Form Post in Shane's Shelf
public static class Extensions
{
public static void PostFormAsync(this HttpWebRequest request, object parameters, AsyncCallback callback)
{
request.Method = "POST";
request.ContentType = "application/x-www-form-urlencoded";
request.BeginGetRequestStream(new AsyncCallback(asyncResult =>
{
Stream stream = request.EndGetRequestStream(asyncResult);
DataContractQueryStringSerializer ser = new DataContractQueryStringSerializer();
ser.WriteObject(stream, parameters);
stream.Close();
request.BeginGetResponse(callback, request);
}), request);
}
public static void PostMultiPartAsync(this HttpWebRequest request, object parameters, AsyncCallback callback)
{
request.Method = "POST";
string boundary = "---------------" + DateTime.Now.Ticks.ToString();
request.ContentType = "multipart/form-data; boundary=" + boundary;
request.BeginGetRequestStream(new AsyncCallback(asyncResult =>
{
Stream stream = request.EndGetRequestStream(asyncResult);
DataContractMultiPartSerializer ser = new DataContractMultiPartSerializer(boundary);
ser.WriteObject(stream, parameters);
stream.Close();
request.BeginGetResponse(callback, request);
}), request);
}
}
public class DataContractQueryStringSerializer
{
public void WriteObject(Stream stream, object data)
{
StreamWriter writer = new StreamWriter(stream);
if (data != null)
{
if (data is Dictionary<string, string>)
{
foreach (var entry in data as Dictionary<string, string>)
{
writer.Write("{0}={1}&", entry.Key, entry.Value);
}
}
else
{
foreach (var prop in data.GetType().GetFields())
{
foreach (var attribute in prop.GetCustomAttributes(true))
{
if (attribute is DataMemberAttribute)
{
DataMemberAttribute member = attribute as DataMemberAttribute;
writer.Write("{0}={1}&", member.Name ?? prop.Name, prop.GetValue(data));
}
}
}
foreach (var prop in data.GetType().GetProperties())
{
if (prop.CanRead)
{
foreach (var attribute in prop.GetCustomAttributes(true))
{
if (attribute is DataMemberAttribute)
{
DataMemberAttribute member = attribute as DataMemberAttribute;
writer.Write("{0}={1}&", member.Name ?? prop.Name, prop.GetValue(data, null));
}
}
}
}
}
writer.Flush();
}
}
}
public class DataContractMultiPartSerializer
{
private string boundary;
public DataContractMultiPartSerializer(string boundary)
{
this.boundary = boundary;
}
private void WriteEntry(StreamWriter writer, string key, object value)
{
if (value != null)
{
writer.Write("--");
writer.WriteLine(boundary);
if (value is FileInfo)
{
FileInfo f = value as FileInfo;
writer.WriteLine(@"Content-Disposition: form-data; name=""{0}""; filename=""{1}""", key, f.Name);
writer.WriteLine("Content-Type: application/octet-stream");
writer.WriteLine("Content-Length: " + f.Length);
writer.WriteLine();
writer.Flush();
Stream output = writer.BaseStream;
Stream input = f.OpenRead();
byte[] buffer = new byte[4096];
for (int size = input.Read(buffer, 0, buffer.Length); size > 0; size = input.Read(buffer, 0, buffer.Length))
{
output.Write(buffer, 0, size);
}
output.Flush();
writer.WriteLine();
}
else
{
writer.WriteLine(@"Content-Disposition: form-data; name=""{0}""", key);
writer.WriteLine();
writer.WriteLine(value.ToString());
}
}
}
public void WriteObject(Stream stream, object data)
{
StreamWriter writer = new StreamWriter(stream);
if (data != null)
{
if (data is Dictionary<string, object>)
{
foreach (var entry in data as Dictionary<string, object>)
{
WriteEntry(writer, entry.Key, entry.Value);
}
}
else
{
foreach (var prop in data.GetType().GetFields())
{
foreach (var attribute in prop.GetCustomAttributes(true))
{
if (attribute is DataMemberAttribute)
{
DataMemberAttribute member = attribute as DataMemberAttribute;
WriteEntry(writer, member.Name ?? prop.Name, prop.GetValue(data));
}
}
}
foreach (var prop in data.GetType().GetProperties())
{
if (prop.CanRead)
{
foreach (var attribute in prop.GetCustomAttributes(true))
{
if (attribute is DataMemberAttribute)
{
DataMemberAttribute member = attribute as DataMemberAttribute;
WriteEntry(writer, member.Name ?? prop.Name, prop.GetValue(data, null));
}
}
}
}
}
}
writer.Write("--");
writer.Write(boundary);
writer.WriteLine("--");
writer.Flush();
}
}
The usage is as follows:
First a PHP file
<?php
print_r($_REQUEST);
$src = $_FILES['y']['tmp_name'];
$dest = "C:\\Windows\\Temp\\".$_FILES['y']['name'];
echo $src;
echo "\r\n";
echo $dest;
echo @copy($src, $dest);
?>
Then the Page control
public partial class Page : UserControl
{
public Page()
{
InitializeComponent();
// Create a request object
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(new Uri("http://localhost/rms/test.php"));
OpenFileDialog dlg = new OpenFileDialog();
if (dlg.ShowDialog().Value)
{
request.PostMultiPartAsync(new Dictionary<string, object> { { "x", "1" }, { "y", dlg.File } }, new AsyncCallback(asyncResult =>
{
HttpWebResponse response = (HttpWebResponse)request.EndGetResponse(asyncResult);
Stream responseStream = response.GetResponseStream();
StreamReader reader = new StreamReader(responseStream);
this.Dispatcher.BeginInvoke(delegate
{
// output is a TextBlock
output.Text = reader.ReadToEnd();
response.Close();
});
}));
}
}
}
Since it is able to serialize data contract, you could actually replace
new Dictionary<string, object> { { "x", "1" }, { "y", dlg.File } }
with
new Point(){X=1, Y=2}
given the point class is like this:
[DataContract]
public class Point
{
[DataMember]
public int X { get; set; }
[DataMember(Name="y")]
public int Y { get; set; }
}