Archive for May, 2009

Uploading multiple files from Silverlight using an ASP.NET Compatible HTTP POST

I was recently trying to make Silverlight upload multiple files to an ASP.NET page using a valid HTTP form POST. It was important that the HttpContext.Request.Files property worked correctly. Little did I realise just how finicky ASP.NET is with HTTP requests. It is critical that TWO line feeds are added between the content-type header in the boundary and the binary data, and ONE line feed must be included after the data prior to the next boundary.

Here’s the Silverlight code:

public static class HttpHelper
{
    public static void UploadFilesAsync(Uri address, IEnumerable<FileInfo> files, Action<string> onCompleted)
    {
        HttpWebRequest request = (HttpWebRequest)WebRequest.Create(address);
        request.Method = "POST";
        string boundary = "-----------------------------" + Guid.NewGuid().ToString().Substring(0, 8);
        request.ContentType = "multipart/form-data; boundary=" + boundary;
        request.BeginGetRequestStream((getRequestResult) =>
        {
            using (Stream requestStream = request.EndGetRequestStream(getRequestResult))
            {
                IEnumerable<FileInfo> uploadFiles = (IEnumerable<FileInfo>)getRequestResult.AsyncState;
                foreach (var uploadFile in uploadFiles)
                {
                    using (Stream fileStream = uploadFile.OpenRead())
                    {
                        WriteText("--" + boundary + Environment.NewLine, requestStream);
                        WriteText(@"Content-Disposition: form-data; name=""fileUpload""; filename=""" + uploadFile.Name + @"""" + Environment.NewLine, requestStream);
                        WriteText(@"Content-Type: application/octet-stream" + Environment.NewLine + Environment.NewLine, requestStream);

                        WriteData(fileStream, requestStream);

                        WriteText(Environment.NewLine, requestStream);
                    }
                }
                // Output the final closing boundary
                WriteText("--" + boundary + "--" + Environment.NewLine, requestStream);
            }
            
            request.BeginGetResponse((getResponseResult) =>
                {
                    var response = request.EndGetResponse(getResponseResult);
                    string responseText = null;
                    using (StreamReader reader = new StreamReader(response.GetResponseStream()))
                    {
                        responseText = reader.ReadToEnd();
                    }
                    onCompleted(responseText);
                }, null);
        }, files);
    }

    private static void WriteText(string input, Stream output)
    {
        var bytes = UTF8Encoding.UTF8.GetBytes(input);
        output.Write(bytes, 0, bytes.Length);
    }

    private static void WriteData(Stream input, Stream output)
    {
        byte[] buffer = new byte[4096];
        int bytesRead;
        while ((bytesRead = input.Read(buffer, 0, buffer.Length)) != 0)
        {
            output.Write(buffer, 0, bytesRead);
        }
    }
}

You can then use this helper in your code like so:

OpenFileDialog ofd = new OpenFileDialog();
ofd.Filter = "All Files (*.*)|*.*";
ofd.Multiselect = true;
if (ofd.ShowDialog().GetValueOrDefault())
{
    HttpHelper.UploadFilesAsync(new Uri("ReadFiles.ashx", UriKind.Relative), ofd.Files,
        (responseText) =>
        {
            Console.WriteLine("Response was: " + responseText);
        });
}

For the ASP.NET code I added a generic handler called ReadFile.ashx and just as proof that it works, I wrote out the number of files that were uploaded.

[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class ReadFile : IHttpHandler
{
    public void ProcessRequest(HttpContext context)
    {
        context.Response.ContentType = "text/xml";
        context.Response.Write("<Result>" + context.Request.Files.Count + 
            " Files Uploaded</Result>");
    }
}

Hope this helps someone else out.

Enjoy!

Leave a comment

Fast Enum.TryParse implementation

I recently saw a post asking why there wasn’t an Enum.TryParse in .NET. I’ve forgotten where the post was, but I found myself needing this so I implemented it myself. It notably doesn’t use a try { } catch { } block so performance should be pretty good.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

public static class EnumExtensions
{
    public static bool EnumTryParse<T>(this string enumText, out T enumValue) 
        where T : struct
    {
        enumValue = default(T);
        if (Enum.IsDefined(typeof(T), enumText))
        {
            enumValue = (T)Enum.Parse(typeof(T), enumText);
            return true;
        }
        return false;
    }
}

Usage:

string unparsedEnumText = "SomeText";
MyCoolEnum parsedEnum;
if (unparsedEnumText.EnumTryParse(out parsedEnum))
{
    Console.WriteLine("Parsed OK as " + parsedEnum.ToString());
}

Note: .NET doesn’t allow for System.Enum to be defined as a type constraint so be careful!

Enjoy!

Update: Changed to use Enum.IsDefined instead of Enum.GetNames

2 Comments

Flexible and Easy to Build Data Entry Forms in WPF

One of the things I find I’m often building in a WPF app is a nicely laid out Data Entry Form, with Label: TextBox combinations all down the page.

So usually you might build a form like this:

<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition />
        <ColumnDefinition />
    </Grid.ColumnDefinitions>
    <TextBlock
        Text="First Name: " />
    <TextBox Grid.Column="1"
        Text="{Binding Path=FirstName}" />
</Grid>

The problem with this comes when you need to add another field you can’t just add it to the bottom, you have to add Row Definitions to the grid and manually specify the row for each TextBlock and TextBox.

<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition />
        <ColumnDefinition />
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
        <RowDefinition />
        <RowDefinition />
    </Grid.RowDefinitions>
    <TextBlock
        Text="First Name: " />
    <TextBox Grid.Column="1"
        Text="{Binding Path=FirstName}" />
    <TextBlock Grid.Row="1"
        Text="Last Name: " />
    <TextBox
        Grid.Row="1"
        Grid.Column="1"
        Text="{Binding Path=LastName}" />
</Grid>

Now imagine you have 20 fields in this form, and you need to insert Middle Name between First and Last name… You’d have to re-number the rows for EVERY TextBlock and TextBox in the grid!!

The Better Way

What you’d like to be able to do is just add these Label/Field combinations in wherever and they all get stacked on top of each other.

So why not use a StackPanel? Well the problem with having all of your TextBLock and TextBox combinations in a StackPanel is the column widths no longer align. Here’s how you get around that:

<StackPanel
    Grid.IsSharedSizeScope="True">
    <StackPanel.Resources>
        <ControlTemplate
            x:Key="LabelledTextBox"
            TargetType="{x:Type ContentControl}">
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition
                        SharedSizeGroup="Label"
                        Width="Auto" />
                    <ColumnDefinition
                        SharedSizeGroup="Content"
                        Width="*" />
                </Grid.ColumnDefinitions>
                <TextBlock
                    Text="{TemplateBinding Content}" />
                <TextBox
                    Grid.Column="1"
                    Text="{Binding Path=.}" />
            </Grid>
        </ControlTemplate>
    </StackPanel.Resources>
    <ContentControl
        Template="{StaticResource LabelledTextBox}"
        Content="First Name: "
        DataContext="{Binding Path=FirstName}" />
    <ContentControl
        Template="{StaticResource LabelledTextBox}"
        Content="Last Name: "
        DataContext="{Binding Path=LastName}" />
</StackPanel>

Now if you need to add a Middle Name you just have to insert a content control where it belongs. These even gives you the flexibility to have different templates for different field types e.g. a LabelledDropDownList template could be added.

Enjoy!

Leave a comment