HttpPostedFileBase's relationship to HttpPostedFileWrapper
Asked Answered
W

1

16

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?

enter image description here

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.

Wakerife answered 23/7, 2014 at 12:50 Comment(0)
N
22

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())
Nitz answered 23/7, 2014 at 13:2 Comment(4)
I *think I understand in terms of... implementation. But how have they "created" this? How can I create a abstract class which shows a different wrapper class when you inspect it/implement it?Wakerife
It's just polymorphism in its prime :) Well, in terms of usage, you can have an instance of derived class where you expect a base class. So ASP.NET MVC makes things easier for you, allowing to expect a base class, so that various implementations could be created for unit testing. In real life, however, HttpPostedFileWrapper is the implementation you'll most likely get.Nitz
I need to see an instance of this to understand. This is something I have never come across. Could you PLEASE post an example with code for 3 minimal classes (MyHttpPostedFile, MyHttpPostedFileBase and MyHttpPostedFileWrapper) which would explain (in code form) what is going on here? Polymorphism (as I am aware of it) requires one object to be the base of another. This isn't the case with these objects?Wakerife
Please see above response.Wakerife

© 2022 - 2024 — McMap. All rights reserved.