<img height="1" width="1" style="display:none" src="https://www.facebook.com/tr?id=1742770185958071&amp;ev=PageView&amp;noscript=1">

Inbound Marketing Blog

ASP.NET 4.0 - Strongly Typed Model on Form Uploads

I recently came across a problem in an ASP.NET MVC Web Application where it needed to upload an image with some extra form fields, but still have the controller’s post action testable. This seemed like an easy enough task, but I soon realized that in order to get the uploaded file from the view I had to grab it from the request object’s files collection. Doing this would make my testing harder, and, more drastically, decouple this action from the view model.

I scoured the Internet to find out why the magic behind binding the view model to the controller would not put the posted file in an argument alongside my strongly typed model. After coming to the conclusion that no direct articles or examples with my scenario existed, I turned to learning more about the binding that was taking place behind the scenes.

After learning quite a bit about the binding, I set out to create my own custom model binder so I could put the posted file in my strongly typed model before the controller’s action method was even called. If you have run into a similar problem, keep reading to see how I kept the view model strongly typed.

Interface with IModelBinder
The first thing I did was create a new ModelBinder class which interfaces the IModelBinder interface. By interfacing the IModelBinder you will be forced to implement this function:


public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)

By implementing this function we gain full access to the controller context and the binding context object. The most beneficial of those arguments is the binding context because it is used for retrieving the form’s post back values.

Since my view has a form with text fields and a file input field, I had two important value providers in my binding context’s value provider collection; one was the FormValueProvider and the other was the HttpFileCollectionValueProvider. This was were I could get all my form values and the newly uploaded file.

Next, I instantiated a new model, set the appropriate properties from the form value provider and file value provider on the model and return the new model out of the implemented BindModel function. I realized that in order to make this more agnostic I would have to use a little reflection.

Reflection was possible because the key names in the value providers are my model’s property names which is how .NET MVC is strongly typing the models in our views to start with. Once I was all done with my new custom model binder I ended up with this class:

Custom Model Binder

public class UploadFileInjectionModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var viewModel = Activator.CreateInstance(bindingContext.ModelType);

var modelProperties = viewModel.GetType().GetProperties();
foreach (PropertyInfo modelProperty in modelProperties)
{
try
{
var contextVal = bindingContext
.ValueProvider.GetValue(modelProperty.Name)
.ConvertTo(modelProperty.PropertyType);
modelProperty.SetValue(viewModel, contextVal, null);
}
catch{}
}

return viewModel;
}
}

The new custom model binder is the main piece to the puzzle. It is looking at the model’s properties and trying to find a matching key/value from any of the value providers. In order to have my new model binder set the uploaded file, I needed to have the appropriate property defined in my model with a matching type.

The files that are being uploaded are being stored as a HttpPostedFileBase object. That means I had to put a public property in the view model of that same type. Since the new custom model binder is using reflection based on property names, I needed the name of the HttpPostedFileBase object to be correlated with the file input’s name and id on the view. As you can see below, my model’s property name of “UploadedFile” is reflected in my view’s input attributes for id and name.

View Model:

public class UploadedImageViewModel
{
public string Name { get; set; }
public string AltText { get; set; }
public int AssociatedParentId { get; set; }
public HttpPostedFileBase UploadedFile { get; set; }
}

Razor View:

@using (Html.BeginForm(“ActionName”, "ControllerName",
FormMethod.Post, new { enctype = "multipart/form-data" }))
{

Upload A Photo

@Html.LabelFor(model => model.Name)
@Html.TextBoxFor(x => x.Name)

@Html.LabelFor(model => model.AltText)
@Html.TextBoxFor(x => x.AltText)

@Html.input id="UploadedFile" type="file" name="UploadedFile @Html.input type="submit" value="Add Photo"

}

At this point, the major work is done and I need to tell .NET to use our custom model binder instead of the default model binder. There are a few ways to do this, but I ended up overriding that behavior for the distinct action that is handling the post back.

To override the model binder for a distinct action, decorate the model argument in the action function with the attribute of [ModelBinder(typeof(YourNewModelBinder))] and the final code for the post action looks like this:

Controller Post Back Action:

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult SavePhoto(
[ModelBinder(typeof(UploadFileInjectionModelBinder))]
UploadedImageViewModel imageModel)
{
//put save logic here
}

The action’s model is now strongly typed, so I can reference the posted file right from the model instead of accessing the controller’s context request object. The action function is then coupled to just a view model instead of the model and controller.

Unit Testing the New Model Binder
Next we need to unit test our new creation. Since the custom model binder requires a controller context object and a binding context object, I created a binding context object that will hold fake value providers containing test data and a fake controller context.

Creating the fake controller context is as simple as creating a class in the test project that inherits from ControllerContext. No other functionality is necessary since we are only using it as a valid object to pass to the BindModel function.

Next we need to build up the binding context object. To do so, set it to the model type which it used for creating our new model in the model binder and also give it the properties to find from the value providers.

The model type is inferred from the model meta data object, so we actually have to create that object instead of setting the model type. Create a model meta data object for our view model by using Current.GetMetaDataForType from the static class ModelMetadataProviders.

Now we create the fake value providers which the model binder uses for getting the property values to store into the newly created model. After a few trials and tribulations, it was apparent that using the DictionaryValueProvider for each specific type in my model was the best way to setup fake value providers. Each of the new value providers are created with data that I can use for testing and the only thing left is to call the new model binder with the fake controller context and the binding context object as its arguments.

You can see below that I reference a FakeHttpPostedFileBase class which I created the same as the fake controller context mentioned above. Unlike the fake controller context I had to override two properties of the HttpPostedFileBase class. The first one was ContentType and the second one was InputStream. By overriding these properties we can later assert test conditions from the new model that the bind model function returns.

The test and fake testing posted file object are shown here:

Unit Test Function:

[Test]
public void BindModelTest()
{
//arrange
m_bindingContext = new ModelBindingContext();
Stream resourceStream =
Assembly.GetExecutingAssembly().GetManifestResourceStream(
"Web.Test.Images.ToastedAppleCinnamonCereal.jpg");
var fakePostedFile = new FakeHttpPostedFileBase(resourceStream, "image/jpeg");

var fileValueProvider =
new DictionaryValueProvider(
new Dictionary , null);
var stringValueProvider =
new DictionaryValueProvider(
new Dictionary , null);
m_valueProvider = new ValueProviderCollection {fileValueProvider, stringValueProvider};

m_bindingContext.ValueProvider = m_valueProvider;
m_bindingContext.ModelMetadata = ModelMetadataProviders
.Current.GetMetadataForType(null, typeof (UploadedImageViewModel));

m_fakeControllerContext = new FakeControllerContext();

//act
var result = m_binder.BindModel(m_fakeControllerContext, m_bindingContext);

//assert
Assert.IsInstanceOf(result);
var viewResult = result as UploadedImageViewModel;
Assert.NotNull(viewResult);

Assert.AreEqual("SomeName", viewResult.Name, "Wrong View Name Returned");
Assert.AreEqual("SomeAltText", viewResult.AltText, "wrong alt text");
Assert.NotNull(viewResult.UploadedFile, "File Did not get moved to model");
}

Fake Posted File:

public class FakeHttpPostedFileBase : HttpPostedFileBase
{
private readonly Stream m_stream;
private readonly string m_contentType;

public FakeHttpPostedFileBase(Stream stream, string contentType)
{
m_stream = stream;
m_contentType = contentType;
}

public override string ContentType
{
get
{
return m_contentType;
}
}

public override Stream InputStream
{
get
{
return m_stream;
}
}
}

Conclusion

This implementation provides separation of logic from controller actions to the underlying .NET objects with the additional benefit of becoming highly reusable. Any time I need to have a file uploaded with a form I can just reuse my custom model binder and not worry about getting the file out of the underlying request object. That keeps my controller actions decoupled from the controller context and each respective unit test simpler.

The unit tests are simpler for the post actions since we don’t have to test how the posted file arrived into our model; all that logic is being handled by the custom model binder. This will keep our unit tests for actions cleaner and quicker to implement.

If you have found other ways to do this, then please feel free to share!

Eric Patterson is a developer for MINDSCAPE at Hanon McKendry. He is also a Google Analtyics qualified individual.

Topics: Development

MINDSCAPE

Written by MINDSCAPE

We work with companies and organizations who want to get the most out of their digital marketing: more leads, more sales, more profit. Our success is measured by the hundreds of millions of dollars we help our clients generate each year.

Join the conversation

Subscribe to Email Updates

Let's Talk