I understand the relationship between HttpPostedFileBase
and HttpPostedFileWrapper
, in terms of the need for both of them (i.e. in unit testing/mocking). But why, when I put a breakpoint on the return for HttpPostedFileBase
, does it show it as HttpPostedFileWrapper
?
Furthermore, HttpPostedFileBase
doesn't implement the ContentType property. So why does it return a value when my code only references HttpPostedFileBase
, and not HttpPostedFileWrapper
? What kind of trickery is this?
Edit #1:
Thanks for the great reply @lawliet29. I have written out the structure as suggested.
public sealed class MyHttpPostedFile
{
public string ContentType { get { return "123"; } }
}
public abstract class MyHttpPostedFileBase
{
}
public class MyHttpPostedFileWrapper : MyHttpPostedFileBase
{
private MyHttpPostedFile _myHttpPostedFile;
public MyHttpPostedFileWrapper(MyHttpPostedFile myHttpPostedFile) {
_myHttpPostedFile = myHttpPostedFile;
}
public string ContentType { get { return _myHttpPostedFile.ContentType; } }
}
In order for this to work though, I would need to pass the parameter like this:
GetFiles(new MyHttpPostedFileWrapper(new MyHttpPostedFile());
This seems to be where the trickery I am questioning exists. How does .NET know that the bytes being passed to it is a class of type MyHttpPostedFile
and that it should take that object and pass it into my constructor's as a parameter?
Edit #2:
I didn't realise the ASP.NET MVC binder would do more than just pass bytes by passing these higher level objects. This is the trickery I was wondering about! Thanks for the great responses.
It's really simple, actually. HttpPostedFileBase is an abstract class, used solely for the purpose of being derived from. It was used so that certain things in sealed class HttpPostedFile would be mockable.
In real life, however, HttpPostedFile is what you have for handling posted files, and to be consistent, HttpPostedFileWrapper was created. This class provides implementation for HttpPostedFileBase by wrapping HttpPostedFile.
So HttpPostedFileBase is a unified abstraction, HttpPostedFile is a class representing posted files, and HttpPostedFileWrapper is implementation of HttpPostedFileBase that wraps HttpPostedFile.
Implementation of ContentType property for HttpPostedFileWrapper reads content type from underlaying HttpPostedFile.
EDIT: some kind of explanation
ASP.NET MVC recieved a file and somewhere deep down below it has created an instance of HttpPostedFile, because this is how things worked since .NET Framework 1.0. The definition of HttpPostedFile looks like this:
public sealed class HttpPostedFile
which basically means it can't be inherited and can't be mocked for unit testing.
To resolve this issue ASP.NET MVC developers created a mockable abstraction - HttpPostedFileBase, which is defined like this:
public abstract class HttpPostedFileBase
So now, you can define your MVC actions so that they accept HttpPostedFileBase and not un-mockable HttpPostedFile:
[HttpPost]
public ActionResult PostFile(HttpPostedFileBase file)
{
// some logic here...
}
The problem is, somewhere deep down below, the only way to represent a posted file is good old rigid HttpPostedFile. So in order to support this abstraction, MVC developers created a decorator called HttpPostedFileWrapper that looks roughly like this:
public class HttpPostedFileWrapper : HttpPostedFileBase
{
private HttpPostedFile _httpPostedFile;
public HttpPostedFileWrapper(HttpPostedFile httpPostedFile) {
_httpPostedFile = httpPostedFile;
}
public string ContentType { get { return _httpPostedFile.ContentType; } }
// implementation of other HttpPostedFileBase members
}
So now HttpPostedFileWrapper is what you actually get when performing a real HTTP POST request with posted file. Thanks to polymorphism, you can pass an instance of derived class - HttpPostedFileWrapper - to method accepting base class - HttpPostedFileBase.
All the while, you can create your own mock implementation that would, say, look like a video file being posted. You'd do it like this
public class MockPostedVideoFile : HttpPostedFileBase
{
public string ContentType { get { return "video/mp4"; } }
// rest of implementation here
}
ANOTHER EDIT:
The actual instantiation of HttpPostedFile is all handled by System.Web for you. ASP.NET MVC binder is quite intelligent about posted form data. It automatically detects that certain post values are actually bytes of a file, so in order to properly represent them it can use something old from System.Web framework to create an instance HttpPostedFile.
The main point of this is - you don't need to worry about it. There are a lot of things going on behind the scenes here and we really need to be grateful to ASP.NET MVC team for abstacting away all those low-level things.
The only place where you do need to worry about this is unit testing. In your test you can just call your action with a mock implementation, like this:
myController.PostFile(new MockPostedVideoFile())