2012-08-29

ASP MVC encoding route values

I’ve recently been using ASP MVC 2 to develop a business application.  As you may already know the ASP MVC routing system works with URLs which look like this

http://localhost/Client/Details/IBM

In standard ASPX apps the URL would look more like this

http://localhost/Client/Details.aspx?code=IBM

The first URL obviously looks much nicer than the 2nd, however it comes at a cost.  What if the “code” of your object is something like “N/A” for “Not applicable”?  You end up with a URL that looks like this

http://localhost/AbsenceCode/Details/N/A

What we really need is to have ASP MVC encode the value “N/A” as “N%2FA”.  The problem is that even if it did do this then by the time ASP MVC receives the value it has already been decoded, so we get a “400 bad request” error.  This is a real pain, because in business we can’t really tell a customer “You can only use characters A to Z, 0 to 9, underscore and minus in your codes” because they will tell you that they have used the code N/A for not-applicable for the past 20 years.

Now if the code had been after the ? in the URL (a query string value) it would get encoded/decoded and there would be no problem, but unfortunately ASP MVC won’t allow you to include ? in your routes.  So I spent today working on a solution to this and it looks like I’ve just cracked it.  It requires the following…

//1: Register a custom value provider in global.asax.cs
protected void Application_Start()
{
EncodedRouteValueProviderFactory.Register();
...
}

//2: Use the following code in your views instead of Html.ActionLink
//this will ensure that all values before the ? query string part of your
//URL are properly encoded

<%: Html.EncodedActionLink(.....) %>
//3: Use this special redirect action when redirecting from a method
return this.EncodedActionLink(.....);


Those are the only changes required to your web app.  In fact they are so simple that you should be able to change your app in seconds with a find/replace in files.



I’ll list all of my code below, there are two hacky bits.  In one place I type cast an IRoute to a Route object so that I can get its URL, and in another place I use reflection so that I can set two properties which have a protected setter.  The trick is to ensure that whenever we produce a URL in our website it encodes any special characters before the ?, we can’t use %xx so I use !xx.  In the above example N/A would become N!2fA, if the value N/A appears after the ? then it will be URL encoded as normal (N%2fA).  When the user navigates to a new page these route values are passed in this specially encoded format so they need to be converted back, so I register my own ValueProvider which looks at the current route (e.g. {controller}/{action}/{make}/{model}/{registration}) and decodes all values which appear in the route only.  This is done only once (the first time any value is requested) so N!2fA will be converted back to N/A as if we had been passed that value to start with.



I only finished working on this last night, but it seems to be working okay in my current app though.  If you spot any bugs in my source or add any missing features make sure to let me know on my gmail.com account (mrpmorris).



//EncodedActionLinkExtensions.cs
using System.Text;
using System.Text.RegularExpressions;
using System.Web.Routing;

namespace System.Web.Mvc.Html
{
public static class EncodedActionLinkExtensions
{
public static MvcHtmlString EncodedActionLink(this HtmlHelper htmlHelper, string linkText, string action)
{
return htmlHelper.EncodedActionLink(linkText, action, (object)null);
}

public static MvcHtmlString EncodedActionLink(this HtmlHelper htmlHelper, string linkText, string action, string controllerName)
{
return htmlHelper.EncodedActionLink(linkText, action, controllerName, (object)null);
}

public static MvcHtmlString EncodedActionLink(this HtmlHelper htmlHelper, string linkText, string action, object explicitRouteValues)
{
object routeValueObj;
if (!htmlHelper.ViewContext.RequestContext.RouteData.Values.TryGetValue("controller", out routeValueObj))
throw new InvalidOperationException("Could not determine controller");

string controllerName = (string)routeValueObj;
return htmlHelper.EncodedActionLink(linkText, action, controllerName, explicitRouteValues);
}

public static MvcHtmlString EncodedActionLink(this HtmlHelper htmlHelper, string linkText, string action, string controllerName, object explicitRouteValues)
{
return htmlHelper.EncodedActionLink(linkText, action, controllerName, new RouteValueDictionary(explicitRouteValues));
}

public static MvcHtmlString EncodedActionLink(this HtmlHelper htmlHelper, string linkText, string action, string controllerName, RouteValueDictionary explicitRouteValues)
{
string url = EncodedUrlHelper.GenerateUrl(
htmlHelper.ViewContext.RequestContext,
controllerName, action, explicitRouteValues);
string result = string.Format("<a href=\"{0}\">{1}</a>", url, linkText);
return MvcHtmlString.Create(result);
}
}
}


//EncodedRedirectToRouteExtensions.cs
using System.Web.Routing;
namespace System.Web.Mvc
{
public static class EncodedRedirectToRouteExtensions
{
public static EncodedRedirectToRouteResult EncodedRedirectToAction(this IController controller, string actionName)
{
return controller.EncodedRedirectToAction(
actionName,
(
string)null, //controllerName,
(RouteValueDictionary)null //routeValues
);
}

public static EncodedRedirectToRouteResult EncodedRedirectToAction(this IController controller, string actionName, object routeValues)
{
return controller.EncodedRedirectToAction(
actionName,
(
string)null, //controllerName,
new RouteValueDictionary(routeValues)
);
}

public static EncodedRedirectToRouteResult EncodedRedirectToAction(this IController controller, string actionName, RouteValueDictionary routeValues)
{
return controller.EncodedRedirectToAction(
actionName,
(
string)null, //controllerName,
routeValues
);
}

public static EncodedRedirectToRouteResult EncodedRedirectToAction(this IController controller, string actionName, string controllerName)
{
return controller.EncodedRedirectToAction(
actionName,
controllerName,
(RouteValueDictionary)
null //routeValues
);
}

public static EncodedRedirectToRouteResult EncodedRedirectToAction(this IController controller, string actionName, string controllerName, object routeValues)
{
return controller.EncodedRedirectToAction(
actionName,
controllerName,
new RouteValueDictionary(routeValues)
);
}

public static EncodedRedirectToRouteResult EncodedRedirectToAction(this IController controller, string actionName, string controllerName, RouteValueDictionary routeValues)
{
RouteValueDictionary dictionary;
if (routeValues != null)
dictionary
= new RouteValueDictionary(routeValues);
else
dictionary
= new RouteValueDictionary();
dictionary[
"controller"] = controllerName;
dictionary[
"action"] = actionName;

var result
= new EncodedRedirectToRouteResult(dictionary);
return result;
}

}
}

//EncodedRedirectToRouteResult.cs
using System.Web.Mvc;
using System.Web.Routing;
namespace System.Web.Mvc
{
public class EncodedRedirectToRouteResult : ActionResult
{
readonly string RouteName;
readonly RouteValueDictionary RouteValues;

public EncodedRedirectToRouteResult(RouteValueDictionary routeValues)
:
this(null, routeValues)
{
}

public EncodedRedirectToRouteResult(string routeName, RouteValueDictionary routeValues)
{
RouteName
= routeName ?? "";
RouteValues
= routeValues != null ? routeValues : new RouteValueDictionary();
}

public override void ExecuteResult(ControllerContext context)
{
string url = EncodedUrlHelper.GenerateUrl(context.RequestContext, null, null, RouteValues);
context.Controller.TempData.Keep();
context.HttpContext.Response.Redirect(url,
false);
}
}
}

//EncodedRouteValueProvider.cs
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Web.Routing;
using System.Reflection;
namespace System.Web.Mvc
{
public class EncodedRouteValueProvider : IValueProvider
{
readonly ControllerContext ControllerContext;
bool Activated = false;

public EncodedRouteValueProvider(ControllerContext controllerContext)
{
ControllerContext
= controllerContext;
}

public bool ContainsPrefix(string prefix)
{
if (!Activated)
DecodeRouteValues();
return false;
}

public ValueProviderResult GetValue(string key)
{
if (!Activated)
DecodeRouteValues();
return null;
}

void DecodeRouteValues()
{
Activated
= true;
var route
= (Route)ControllerContext.RouteData.Route;
string url = route.Url;
var keysToDecode
= new HashSet<string>();
var regex
= new Regex(@"\{.+?\}");
foreach (Match match in regex.Matches(url))
keysToDecode.Add(match.Value.Substring(
1, match.Value.Length - 2));
foreach (string key in keysToDecode)
{
object valueObj = ControllerContext.RequestContext.RouteData.Values[key];
if (valueObj == null)
continue;
string value = valueObj.ToString();
value
= UrlValueEncoderDecoder.DecodeString(value);
ControllerContext.RouteData.Values[key]
= value;
ValueProviderResult valueProviderResult
= ControllerContext.Controller.ValueProvider.GetValue(key);
if (valueProviderResult == null)
continue;
PropertyInfo attemptedValueProperty
= valueProviderResult.GetType().GetProperty("AttemptedValue");
attemptedValueProperty.SetValue(valueProviderResult, value,
null);
PropertyInfo rawValueProperty
= valueProviderResult.GetType().GetProperty("RawValue");
rawValueProperty.SetValue(valueProviderResult, value,
null);
}
}

}
}

//EncodedRouteValueProviderFactory.cs
namespace System.Web.Mvc
{
public class EncodedRouteValueProviderFactory : ValueProviderFactory
{
public override IValueProvider GetValueProvider(ControllerContext controllerContext)
{
return new EncodedRouteValueProvider(controllerContext);
}

public static void Register()
{
ValueProviderFactories.Factories.Insert(
0, new EncodedRouteValueProviderFactory());
}
}
}

//EncodedUrlHelper.cs
using System.Text;
using System.Text.RegularExpressions;
using System.Web.Mvc;
namespace System.Web.Routing
{
public static class EncodedUrlHelper
{
public static string GenerateUrl(
RequestContext requestContext,
string controllerName,
string action,
RouteValueDictionary explicitRouteValues)
{
if (requestContext == null)
throw new ArgumentNullException("RequestContext");

var newRouteValues
= RouteHelper.GetRouteValueDictionary(
requestContext, controllerName, action, explicitRouteValues);
var route
= RouteHelper.GetRoute(requestContext, controllerName, action, newRouteValues);
string url = route.Url;
//Replace the {values} in the main part of the URL with request values
var regex = new Regex(@"\{.+?\}");
url
= regex.Replace(url,
match
=>
{
string key = match.Value.Substring(1, match.Value.Length - 2);
object value;
if (!newRouteValues.TryGetValue(key, out value))
throw new ArgumentNullException("Cannot reconcile value for key: " + key);
string replaceWith;
if (value == UrlParameter.Optional)
replaceWith
= "";
else
replaceWith
= UrlValueEncoderDecoder.EncodeObject(value);
explicitRouteValues.Remove(key);
return replaceWith;
});

//2: Add additional values after the ?
explicitRouteValues.Remove("controller");
explicitRouteValues.Remove(
"action");
var urlBuilder
= new StringBuilder();
urlBuilder.Append(
"/" + url);
string separator = "?";
foreach (var kvp in explicitRouteValues)
{
if (kvp.Value != UrlParameter.Optional)
{
urlBuilder.AppendFormat(
"{0}{1}={2}", separator, kvp.Key, kvp.Value == null ? "" : HttpUtility.UrlEncode(kvp.Value.ToString()));
separator
= "&";
}
}
return urlBuilder.ToString();
}
}
}

//RouteHelper.cs
namespace System.Web.Routing
{
public static class RouteHelper
{
public static RouteValueDictionary GetRouteValueDictionary(
RequestContext requestContext,
string controllerName,
string action,
RouteValueDictionary explicitRouteValues)
{
var newRouteValues
= new RouteValueDictionary();
var route
= GetRoute(requestContext, controllerName, action, explicitRouteValues);
MergeValues(route.Defaults, newRouteValues);
MergeValues(requestContext.RouteData.Values, newRouteValues);
if (explicitRouteValues != null)
MergeValues(explicitRouteValues, newRouteValues);
if (controllerName != null)
newRouteValues[
"controller"] = controllerName;
if (action != null)
newRouteValues[
"action"] = action;
return newRouteValues;
}

public static Route GetRoute(
RequestContext requestContext,
string controllerName,
string action,
RouteValueDictionary explicitRouteValues
)
{
var routeValues
= new RouteValueDictionary(requestContext.RouteData.Values);
if (explicitRouteValues != null)
MergeValues(explicitRouteValues, routeValues);
if (controllerName != null)
routeValues[
"controller"] = controllerName;
if (action != null)
routeValues[
"action"] = action;
var virtualPath
= RouteTable.Routes.GetVirtualPath(requestContext, routeValues);
return (Route)virtualPath.Route;
}

static void MergeValues(RouteValueDictionary routeValues, RouteValueDictionary result)
{
foreach (var kvp in routeValues)
{
if (kvp.Value != null)
result[kvp.Key]
= kvp.Value;
else
{
object value;
if (!result.TryGetValue(kvp.Key, out value))
result[kvp.Key]
= null;
}
}
}
}
}

//UrlValueEncoderDecoder.cs
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using System.Text.RegularExpressions;
namespace System.Web.Mvc
{
public static class UrlValueEncoderDecoder
{
static HashSet<char> ValidChars;

static UrlValueEncoderDecoder()
{
string chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-.";
ValidChars
= new HashSet<char>(chars.ToCharArray());
}

public static string EncodeObject(object value)
{
if (value == null)
return null;
return EncodeString(value.ToString());
}

public static string EncodeString(string value)
{
if (value == null)
return null;
var resultBuilder
= new StringBuilder();
foreach (char currentChar in value.ToCharArray())
if (ValidChars.Contains(currentChar))
resultBuilder.Append(currentChar);
else
{
byte[] bytes = System.Text.UnicodeEncoding.UTF8.GetBytes(currentChar.ToString());
foreach (byte currentByte in bytes)
resultBuilder.AppendFormat(
"${0:x2}", currentByte);
}
string result = resultBuilder.ToString();
//Special case, use + for spaces as it is shorter and spaces are common
return result.Replace("$20", "+");
}

public static string DecodeString(string value)
{
if (value == null)
return value;
//Special case, change + back to a space
value = value.Replace("+", " ");
var regex
= new Regex(@"\$[0-9a-fA-F]{2}");
value
= regex.Replace(value,
match
=>
{
string hexCode = match.Value.Substring(1, 2);
byte byteValue = byte.Parse(hexCode, NumberStyles.AllowHexSpecifier);
string decodedChar = System.Text.UnicodeEncoding.UTF8.GetString(new byte[] { byteValue });
return decodedChar;
});
return value;
}
}
}

ECO and distributed transactions

My current app is a website, each client gets their own unique database in order to help prevent possible data access from other clients (and meet various legal requirements too.)  One of the features of this application is that there is some core data (such as countries/currencies) which is maintained on behalf of the client.  This data will be identical for all clients, and only maintained by a system administrator.

Obviously expecting the admin to log into each client to perform updates is unreasonable, so I have structured my app so that there is a separate Admin website which updates a master database and propagates the changes to all of the clients.

When the master website does an update it performs it within an IUnitOfWork, and then tells the same update command to execute for each client database too.  If any of these updates fail for whatever reason then the whole thing needs to be cancelled whether one of the child databases updated or not.

using (var distributedTransaction = new Transaction())
{
var validationErrors = PerformActionInMasterDatabase(action);
if (!validationErrors.Any())
{
foreach(var connectionString in clientDatabases)
{
var clientValidationErrors =
PerformActionInClientDatabase(connectionString, action);
validationErrors.AddRange(clientValidationErrors);
if (validationErrors.Any())
break;
}//for each client
}//No errors on master DB
if (!validationErrors.Any())
distributedTransaction.Complete();
}


The problem was that if an exception was thrown during this process then the master database was still updated whereas the client databases were not.  I’ve been tracking this problem down during most of Sunday and this morning I had an eureka moment!



I’ve opted to have Enterprise Core Objects store my mapping information in the database, so the first thing that happens when my application runs is Enterprise Core Objects will connect to the DB and read that mapping information.  This is a description of the steps involved…





    1. Application runs


    2. Database connection is created


    3. Mapping information is retrieved


    4. Connection is returned to the pool


    5. Distributed transaction is started


    6. Object updates are made


    7. Connection is retrieved from the pool




Now because the connection is retrieved from a pool it is not created after the distributed transaction starts, so it is not enlisted in the transaction.  SqlConnection’s connection pool does handle this, but because in the past Enterprise Core Objects has had to deal with connections which do not pool the developers added their own pooling and it does not handle this scenario.



Thankfully the solution once you know the problem is very simple.  The developers added a way of disabling their own pooling, simply set PersistenceMapperSql1.MaxPoolSize to zero.  Now it won’t pool connections itself, and SqlConnection will still provide connection pooling for performance and also ensure that the connection is enlisted into the distributed transaction!

Tightly coupling generic types

Update: Instead of decorating responses with IResponseFor<T> I now instead decorate the command/query with IExpectResponse<T> - Each command/query should only have one response type, and this way it makes it possible to specify the return type should be something simple like a GUID.

My server application works purely on a request/response pattern, like so
var query = new GetCustomerQuery(customerUniqueID); var response = AppServer.Execute<GetCustomerQuery, GetCustomerQueryResponse>(query);


What I wanted to avoid though was the possibility that the user (me writing the client app) would do something silly like the following code and use the wrong pair combination


var query = new GetCustomerQuery(customerUniqueID); var response = AppServer.Execute<GetCustomerQuery, GetEmployeeQueryResponse>(query);


Up until now I had the server interface defined like this, so that I can at least ensure the generic parameters are a Query and Response…


TResponse Execute<TRequest, TResponse>(TRequest request) where TRequest : Request where TResponse : Response;


But a very simple addition ensured that the response type specified is the right type for the request.


TResponse Execute<TRequest, TResponse>(TResponse response) where TRequest : Request where TResponse : Response, IResponseFor<TRequest>;


Then when I create my GetCustomerQueryResponse class I merely need to declare it like so


public class GetCustomerQueryResponse : Response, IResponseFor<GetCustomerQuery> { //etc }


Now the client code above won’t compile because GetEmployeeQueryResponse does not implement IResponseFor<GetCustomerQuery>.

ECO Persistence Mapper per connection

The Enterprise Core Objects Persistence Mapper is a singleton which is used by all EcoSpace instances.  It’s purpose is to load mapping information from the DB when your app starts and to cache it, improving performance.

I needed a connection per client, all running within a single application.  This was a problem because once the PMP is created its connection string is tied to a single database.  So I had to come up with a new PersistenceMapperDynamicSharer component.  It is used on the EcoSpace to specify the PMapper type; additionally you can specify a connection string to use.

It works by dynamically creating a descendant class of your PersistenceMapperProvider at runtime, one for each connection string.


public class PersistenceMapperDynamicSharer : PersistenceMapperSharer
{
static ReaderWriterLockSlim Locker = new ReaderWriterLockSlim();
static Dictionary
<string, PersistenceMapperProvider> MapperProviders = new Dictionary<string, PersistenceMapperProvider>();

[Browsable(false)]
public string ConnectionString { get; set; }

public override IPersistenceMapper GetPersistenceMapper(ITypeSystemService typeSystemService)
{
return GetPersistenceMapperProvider().GetPersistenceMapper(typeSystemService);
}

public override void ReturnPersistenceMapper(IPersistenceMapper persistenceMapper)
{
GetPersistenceMapperProvider().ReturnPersistenceMapper(persistenceMapper);
}

PersistenceMapperProvider GetPersistenceMapperProvider()
{
//No connection string = default mapper provider
if (string.IsNullOrEmpty(ConnectionString))
return PersistenceMapperProvider.GetInstance(MapperProviderType);

PersistenceMapperProvider result;
Locker.EnterUpgradeableReadLock();
try
{
if (MapperProviders.TryGetValue(ConnectionString, out result))
return result;
Locker.EnterWriteLock();
try
{
var mapperType = CreateNewPersistenceMapperProviderType();
result = (PersistenceMapperProvider)Activator.CreateInstance(mapperType);
((IDynamicallySharedPersistenceMapper)result).SetConnectionString(ConnectionString);
MapperProviders[ConnectionString] = result;
}
finally
{
Locker.ExitWriteLock();
}
}
finally
{
Locker.ExitUpgradeableReadLock();
}
return result;
}

Type CreateNewPersistenceMapperProviderType()
{
string guid = Guid.NewGuid().ToString();
var assemblyName = new AssemblyName(guid);
var assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(
assemblyName,
System.Reflection.Emit.AssemblyBuilderAccess.Run);
var moduleBuilder = assemblyBuilder.DefineDynamicModule(guid);
var typeBuilder = moduleBuilder.DefineType(
guid,
TypeAttributes.Class | TypeAttributes.Public,
MapperProviderType);
return typeBuilder.CreateType();
}
}


On my EcoSpace I have a method called ActivateWithConnectionString which I can use whenever I need to activate the EcoSpace connecting to a connection string OTHER than the default…



public void ActivateWithConnectionString(string connectionString)
{
persistenceMapperDynamicSharer1.ConnectionString = connectionString;
Active = true;
}


And on my PersistenceMapperProvider I need to implement IDynamicallySharedPersistenceMapper like so…



void IDynamicallySharedPersistenceMapper.SetConnectionString(string connectionString)
{
sqlConnection1.ConnectionString = connectionString;
}


Now I can benefit from the performance gains from the singleton-pattern implemented by PersistenceMapperProvider and connect to different databases.

PropertyChangedEventHandler is not marked as serializable

My data-transfer-objects implement INotifyPropertyChanged, which was giving me a problem whenever I tried to serialise them over a Remoting session.  If you try to add [NonSerialized] to the event you get an error informing you that you can only apply this attribute to fields, and not properties.

The solution is pretty simple, I think the .NET compiler should do this by default.

[NonSerialized]
PropertyChangedEventHandler propertyChanged;
public event PropertyChangedEventHandler PropertyChanged
{
add { propertyChanged += value; }
remove { propertyChanged -= value; }
}

Partial declarations of must not specify different base classes

I wanted to use a common base class for a set of UserControls in my WPF application, but when I changed the class’s ancestor in the code file and compiled I would get the error

“Partial declarations of must not specify different base classes”

This is because when you compile a WPF application Visual Studio generates a partial class in a code-behind file automatically, the base type specified is always “UserControl”.  To solve this problem change your XAML from this

 

<UserControl
x:Class="MyApp.MyControl"
/>


To this



<local:SomeBaseTypeYouWantToUse
x:Class="MyApp.MyControl"
xmlns:local
="clr-namespace:NameSpace.To.Your.BaseClass"
/>

A WebServer for MonoTouch that also parses posted form data (HttpListenerRequest)

I found a few examples of web servers for MonoTouch but none of them parsed the data sent in a POST request.  I looked around the web and was unable to find any examples of how to achieve this.  So now that I’ve written it myself I’ve decided to share my own implementation.  This includes not only the code for processing the form post data but also for registering request handlers etc.

Here is an example of how you would use the web server

public BookUploadViewController()
:
base("BookUploadViewController", null)
{
RequestHandler
= new DefaultRequestHandler();
var defaultActionHandlerFactory
= new DefaultActionHandlerFactory();
RegisterActionHandlers(defaultActionHandlerFactory);
RequestHandler.AddActionHandlerFactory(defaultActionHandlerFactory);

WebServer
= new EmbeddedWebServer(RequestHandler);
}

void RegisterActionHandlers(DefaultActionHandlerFactory factory)
{
factory.RegisterHandler(
request
=> request.RawUrl == "/",
request
=> new IndexActionHandler(request)
);
factory.RegisterHandler(
request
=>
string.Compare(request.RawUrl, "/Upload", true) == 0 &&
string.Compare(request.HttpMethod, "POST", true) == 0,
request
=> new UploadActionHandler(request)
);
}

public override void ViewDidAppear(bool animated)
{
base.ViewDidAppear(animated);
StatusLabel.Text
= string.Format("Server listening on\r\nhttp://{0}:8080", GetIPAddress ());
WebServer.Start(
8080);
}

public override void ViewDidDisappear (bool animated)
{
base.ViewDidDisappear(animated);
WebServer.Stop();
}



And here are two app specific examples of request handlers



class IndexActionHandler : DefaultActionHandler
{
public IndexActionHandler(HttpListenerRequest request)
:
base(request)
{
}

public override ActionResult Execute()
{
var result
= new HtmlResult();
result.AppendLine(
"<html>");
result.AppendLine(
"<body>");
result.AppendLine(
"<h1>Upload an image</h1>");
result.AppendLine(
"<form action='/Upload' enctype='multipart/form-data' method='post'>");
result.AppendLine (
"<input name='Image' type='file'/><br/>");
result.AppendLine(
"<input name='Upload' type='submit' text='Upload'/>");
result.AppendLine(
"</form>");
result.AppendLine(
"</body>");
result.AppendLine(
"</html>");
return result;
}
}

class UploadActionHandler : DefaultActionHandler
{
public UploadActionHandler(HttpListenerRequest request)
:
base(request)
{
}

public override ActionResult Execute()
{
string errorMessage = null;
var file
= FormData.GetFile("Image");
if (file == null
|| file.FileData == null
|| file.FileData.Length == 0
|| string.IsNullOrEmpty(file.FileName))
errorMessage
= "No image uploaded";

if (errorMessage == null)
ProcessFile(file);

var result
= new HtmlResult();
result.AppendLine(
"<html>");
result.AppendLine(
"<body>");
if (errorMessage == null)
result.AppendLine(
"<h1>File uploaded successfully</h1>");
else
{
result.AppendLine(
"<h1>Error</h1>");
result.AppendLine(
"<h2>" + errorMessage + "</h2>");
}
result.AppendLine(
"</body>");
result.AppendLine(
"</html>");
return result;
}

void ProcessFile(MultiPartStreamFileValue postedFile)
{
string fileName = "Where to save the file";
using (var fileStream =
new FileStream(fileName, FileMode.Create, FileAccess.Write, FileShare.None))
{
fileStream.Write(postedFile.FileData,
0, postedFile.FileData.Length);
}
}
}


You can download the source code here