Scott Hanselman

Back to Basics - This is not the object you're looking...wait, oh, it is the object

July 02, 2008 Comment on this post [27] Posted in Back to Basics | Learning .NET | Windows Client
Sponsored By

Downcasting is kind of something you usually want to avoid, but sometimes it's not easily avoided. It depends on the situation. Because it's not an idiom you'll find yourself doing every day, sometimes you'll forget to do it entirely and what you're looking for is right there under your nose.

The Problem

A buddy was trying to host the WebBrowser control in his WinForms application. It was a locally run application that was using the WebBrowser control to generate part of the UI. The user might click on a link, and you'd want to do something based on the click of an link/anchor in the HTML.

In this trivial example, I add the words "Add Comment" to a page I've navigated to within my WinForms Application.

image

First, we make some new elements within the WebBrowser control itself, like this.

HtmlElement div = webBrowser1.Document.CreateElement("div");
div.SetAttribute("style", "color:blue;");
webBrowser1.Document.Body.AppendChild(div);

HtmlElement anchor = webBrowser1.Document.CreateElement("a");
anchor.InnerText = "Add Comment";
anchor.Id = "lnkAddComment";
anchor.SetAttribute("href", "#");
anchor.Click += new HtmlElementEventHandler(doit);
div.AppendChild(anchor);

Notice that we can hook up a managed event handler to the anchor, and that the anchor's href attribute points to nothing, by setting it to "#".

anchor.Click += new HtmlElementEventHandler(doit);

My friend hooked up the event to doit() that had a standard EventHandler signature like this:

public void doit(object sender, HtmlElementEventArgs e)

You've likely seen this "object sender, SomeEventArgs e" method signature before. He started digging around in the HtmlElementEventArgs object, trying to find something he could use to known if the user had clicked on the link called lnkAddComment. He was bummed to find nothing he could use.

image 

There's a couple of thing to learn here. First, just hovering over the sender object in the debugger shows us a lot. We can see that the type of sender isn't just object, but rather System.Windows.Forms.HtmlElement. This is a CLR type, and not a JavaScript type or a type you'd know about if you were familiar with the DOM and were expecting something more DOM-like.

The DOM element in this case, is there, but it's a COM Object and is part of the IE DOM object model and you'd be better off using Visual Basic if you care deeply about getting into it as we've recently learned.

image

Downcast, Damned Spot!

However, it's an HtmlElement and it has and id property with the string "lnkAddComment," and that's useful. At this point one could down-cast it. That means, telling the compiler that we know more than it does and that we're totally sure and are willing to risk our necks.

HtmlElement foo = (HtmlElement)sender;
if (foo.Id == "lnkAddComment")
{
MessageBox.Show("woot");
}

There's some interesting things we can think about here. First, what if we're wrong? Well, bad things for one:

System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.InvalidCastException: Unable to cast object of type 'Something.You.Did.Not.Plan.On' to type 'System.Windows.Forms.HtmlElement'.
   at WindowsFormsApplication1.Form1.doit(Object sender, HtmlElementEventArgs e) in C:\Yay\Form1.cs:line 32

And that sucks. However, in this case we are pretty darn sure, but we should be more defensive about it.

public void doit(object sender, HtmlElementEventArgs e)
{
if (sender is HtmlElement)
{
HtmlElement foo = (HtmlElement)sender;
if (foo != null && foo.Id == "lnkAddComment")
{
MessageBox.Show("woot");
}
}
}

But that is kind of verbose, we can try a defensive cast:

HtmlElement foo = sender as HtmlElement;
if (foo != null && foo.Id == "lnkAddComment")
{
MessageBox.Show("woot");
}

In this case, if sender isn't an HtmlElement, we'll get back null. The same code in VB is pretty clear also. See the TryCast?

Public Sub doit(ByVal sender As Object, ByVal e As HtmlElementEventArgs)
Dim foo As HtmlElement = TryCast(sender,HtmlElement)
If ((Not foo Is Nothing) AndAlso (foo.Id = "lnkAddComment")) Then
MessageBox.Show("woot")
End If
End Sub

The IL (Intermediate Language) instructions are interesting also as it's asking explicitly if that object is what I think it is:

L_0001: ldarg.1 
L_0002: isinst [System.Windows.Forms]System.Windows.Forms.HtmlElement

I suppose that means I could just ask myself, but that's largely stylistic as the resulting IL is virtually the same, with the defensive cast being a simpler.

But Why the Downcast at All?

At this point you might be asking, Why did I have to downcast at all? Doesn't that mean the EventHandler pattern is lame? Maybe, but here's the thinking as described by Jeffrey Richter on page 228 of his most excellent CLR via C#, 2nd Edition (with [contextual edits and emphasis] by me so it makes sense in this post).

A lot of people wonder why the event pattern requires the sender parameter to always be of type Object. After all, since the [HtmlElement] will be the only type raising an event with a [HtmlElementEventArgs] object, it makes more sense for the callback method to be prototyped like this:

void MethodName(HtmlElement sender, HtmlElementEventArgs e);

The pattern requires the sender parameter to be of type Object mostly because of inheritance. What if [HtmlElement] were used as a base class for [DerivedHtmlElemen]? In this case, the calIback method should have the sender parameter prototyped as an [DerivedHtmlElement] instead of HtmlElement, but this can't happen because [DerivedHtmlElement] just inherited the Click event. So the code that was expecting an [DerivedHtmlElement] to raise the event must still have to cast the sender argument to an [DerivedHtmlElement]. In other words, the cast is still required, so the sender parameter might as well be typed as Object.

The next reason for typing the sender parameter as Object is just flexibility. It allows the delegate to be used by multiple types that offer an event that passes a [HtmlElementEventArgs] object. For example, a [PopHtmlElement] class could use the delegate even if this class were not derived from [HtmlElement].

One more thing: the event pattern also requires that the delegate definition and the callback method name the EventArgs-derived parameter e. The only reason for this is to add additional consistency to the pattern, making it easier for developers to learn and implement the pattern. Tools that spit out source code (such as Microsoft Visual Studio) also know to call the parameter e.

If you're interested in this kind of stuff, you should totally buy his book.

Can we avoid the cast completely?

Why did we downcast in the first place? We wanted to get ahold of the HtmlElement.Id property so we could do a string comparison in order to tell if an object is the one we're looking for. Perhaps we can check for that object's identity using something a little cleaner than a string.

In this case, since we were the ones that created the anchor in the first place we can check the "object sender" against a saved reference to our anchor by checking object identity. Are these the same two objects? Is the object I added to the object model the same one that is coming back to me as the sender parameter to this Event Handler?

HtmlElement anchor;
public void doit(object sender, HtmlElementEventArgs e)
{
if (sender.Equals(anchor))
{
MessageBox.Show("woot");
}
}

Is this a good idea? What about using == instead? In this case, I CAN use == because HtmlElement has explicitly created equality operators, so when I'm using == to compare these two instances I'm ACTUALLY calling a static op_Equality(HtmlElement, HtmlElement) on the HtmlElement type. It's static because both side might be null and I can't call methods on null instances.

HtmlElement anchor;
public void doit(object sender, HtmlElementEventArgs e)
{
if (anchor == sender)
{
MessageBox.Show("woot");
}
}

However, while operator overloading is common in C++ it's generally considered to be unnecessarily obscure in C# and moreover, you just can't count on folks to be consistent. For example, when I say == do I mean value equality or reference equality? In the case of HtmlElement they mean reference equality. In C++ an overloaded == is usually done for deep value comparisons.

I will get this warning if I try anyway:

Possible unintended reference comparison; to get a value comparison, cast the right hand side to type 'System.Windows.Forms.HtmlElement' 

I like to use Equals() myself for clarity's sake, but touché! It's going to just do this internally anyway:

public override bool Equals(object obj)
{
return (this == (obj as HtmlElement));
}

Commenter Adam makes the excellent point that using Object.ReferenceEquals is explicitly clear in expressing intent. The result would be:

public void doit(object sender, HtmlElementEventArgs e)
{
if (Object.ReferenceEquals(anchor,sender))
{
MessageBox.Show("woot");
}
}

Of course, the implementation of ReferenceEquals is just this again. ;)

public static bool ReferenceEquals(object objA, object objB)
{
return (objA == objB);
}

What's the point?

Know your options, but above all, know your intentions and make sure that the code you're writing correctly expresses your intent.

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook bluesky subscribe
About   Newsletter
Hosting By
Hosted on Linux using .NET in an Azure App Service

How to Programmatically tell if an IIS AppPool is 32-bit or 64-bit

July 01, 2008 Comment on this post [2] Posted in ASP.NET | IIS
Sponsored By

I've blogged before about 32-bit vs. 64-bit .NET under IIS and how different Framework-bitnesses need different Application Pools (AppPools).

Someone asked how you find out what AppPools are on a machine and how do you find out which ones are 32-bit? There's a number of ways, depending on your needs:

If you're running a batch file or command-line script and IIS7, you might use the standard IIS7 appcmd.exe tools. You'll need to be Administrator, of course, as we're talking about Web Server configuration here.

To list all AppPools:

c:\Windows\System32\inetsrv\appcmd list apppool
APPPOOL "Joe" (MgdVersion:v2.0,MgdMode:Integrated,state:Started)
APPPOOL "Sally" (MgdVersion:v1.1,MgdMode:Classic,state:Started)
APPPOOL "Fred" (MgdVersion:v2.0,MgdMode:Integrated,state:Started)

To list just 32-bit AppPools:

c:\Windows\System32\inetsrv\appcmd list apppool /enable32BitAppOnWin64:true
APPPOOL "Fred" (MgdVersion:v2.0,MgdMode:Integrated,state:Started)
Basically, any way, be it WMI, COM, PowerShell, or AppCmd.exe that lets you look at the Enable32BitAppOnWin64 property of an AppPool will get you your answer.

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook bluesky subscribe
About   Newsletter
Hosting By
Hosted on Linux using .NET in an Azure App Service

The Spirit of Open Source - Netiquette can be subtle

July 01, 2008 Comment on this post [41] Posted in Musings
Sponsored By

I got a disturbing email today on Flo's mailing list (of Notepad2 fame). Florian Balmer is the author of the most excellent Notepad2. This is a great "it just works" editor. It's Notepad, but goes one better, I like to say. I've promoted it on my Tools List and in countless talks. Notepad2 has been developed over ten years and is in active, ongoing development. Florian has posted most recently about his adventures in Unicode.

Flo was surfing and discovered http://www.notepad3.org.

This is uncool for a number of concrete reasons.

UPDATE/CORRECTION: I screwed up the licensing so my only complaints to the author of Notepad3 is that the name makes for a tacky fork and that the changes appear to be not significant in their scope. I apologize.

Notepad3 is a fork of a older version of Notepad2, specifically the GPL'ed Notepad2 1.0.12 rather than the BSD Licensed Notepad2 2.1.19. The GPL'ed Notepad2 was released on June 25th, 2004. The BSD licensed version was released on April 7th, 2007.

See, it turns out Netiquette CAN be subtle!

  • The distributor of Notepad3 did not include Florian's license. 
    • UPDATE/CORRECTION: The license still is intact in the .c source code files, just not in the main license.txt. The full copy of the original Notepad2 license.txt also appears in Notepad3's source zip in c165/license.txt.
    • Notepad2 is distributed with this license (I snipped the copyright all caps at the end for brevity) as its main license.txt.
  • Florian's name doesn't appear anywhere within the Notepad3 root license.txt, which appears to be an aggregate of a number of licenses of the sub-components.
    • UPDATE/CORRECTION: Flo's website is referenced in the license.txt's header.
  • The Notepad3 "creator" didn't make any attempts to include his changes in Notepad2 or give Florian a heads-up.

Now, these next reasons are my own opinions, and possibly subtle. You tell me. These are my opinions.

  • Mildly Tacky: They made up a new name and registered a new domain for the project. The new source can be only by found by digging around the main site. Can you find it?
    UPDATE/CORRECTION: I think the source should be linked directly to from the main page. Rather, it's under download/src using the navigation on the left of their site.
  • Tacky: The new entity, Notepad3, arguably doesn't contain substantive improvements, certainly not enough to be called an entirely new version, and definitely not Notepad3. You can see the sum total of the changed lines on Flo's post.
  • Rather Tacky: They took the time to put the new entity has been put up on Softpedia and promote it without a single reference to Florian or Notepad2.

It's not that Florian doesn't appreciate improvements. He has put a number of modified versions his side, including a very minor update by Wesner Moise and I. If he wanted to incorporate these changes into the mainline he could easily. I included a source diff for him, but most importantly I didn't make it look like his work was mine!

Modified Versions of Notepad2

When people do Open Source work, one of the things that is almost universal is the natural human need for appreciation. For attribution. One overarching intent of the whole Creative Commons with Attribution license and most Open Source Licenses is "just don't remove the part that says *I did this!*"

Flo says it well, albeit with some understandable passion:

Nonetheless, the author has chosen the name "Notepad3". Compared with the original Notepad, Notepad2 represents an evolution with new features. "Notepad3" implies the same, but with even less features, without any new development. Seems that the play on words is used to fool users into believing that "Notepad3" is something more advanced. The popularity of Notepad2 is being taken advantage of to attract some attention ― I have no idea what else might be the motivation of the author.

The author justifies the publication of "Notepad3" with the added installation and documentation (which is actually my documentation, and has always been there). Innovation equals zero. This causes confusion about the different versions available, and the reputation of my software is harmed when associated with that kind of sloppy, offending descendants (and, it makes me think how indiscriminate some well-known software sites are concerning additions to their repositories).

I have no problem with new versions of my software being released, even with minor changes ― my licensing conditions are fully met. The problem here is that the author made minor changes to the code and branded it in a very disingenuous and deceiving manner.

Perhaps this is just a faux pas, a minor misunderstanding. However, I would encourage Readers who are interested in getting into Open Source to think about these issues when getting involved in projects.

My Own Experiences in Open Source

I've had a lot of great experiences and one speed bump.

WatirMaker

When I wrote WatirMaker and released the source, I had written it from scratch. Each time it got forked or written again or modified, the folks who did it let me know! They asked for help! I offered help! It was a lovefest - the kind of lovefest that I think Open Source should be.

The email conversations usually went like this:

Dude, heads up, I want to do _____

Sweet! Rock on, let me know if you want help with ____ or if/when/how we can put _____ back into the source. Wanna join the project!

Sweet! Dude! Rock on!

Yay! <Hugs.>

And the results were cool.

Notice how everyone is genial, chatting, and attributing? Even in Richard's WatinRecorder post, by now likely a complete re-write of my stuff, was thoughtful enough to thank a bunch of folks.

DasBlog

DasBlog, now the work of dozens, originally came from Clemens Vasters, who originally got it from Chris Anderson's BlogX had a similar issue happen when a gentleman forked our Source Code and created an entirely different project out of it with an all new name. This happened two years ago and he's since changed/refactored the project pretty substantially, but it still felt crappy at the time and the team was rightfully torqued. At the time, all that had been done was a recompile under VS2005 and a check-in as a new project with a new name.

Justice Gray blogged about it soon after:

Saying that "in the long term, ThinkJot will move away from the original dasBlog source code",  implies that all you've done is make some minor project ports, throw in a new DatePicker and call this your own project.  For sure, it's probably legally fine given the terms of the open-source license, but it leaves a bad taste in my mouth.  How would you feel if you worked on something for a long time and then someone just took all the work you did, rebranding it and called it their own?  I mean, c'mon!

Fortunately the author posted about this later and admitted this most important point:

"My biggest mistake has been that I never talked to the dasBlog team previously about this. I admit it, and apologize."

And I give him full credit for that simple admission. Folks that copy/fork are rarely evil or malicious. We're all just trying to move the ball forward, and perhaps that's the same in the case of Notepad3. However, that doesn't change the fact that talking to folks up front can make all the difference.

Unfortunately, at this point, this issue has soured Florian considerably:

I have been working on Notepad2 over more than 10 years, altogether. The above hacks may have been done in a few minutes.

There have been similar cases in the past, already, but this one really beats everything. I'm not sure if I'm going to release any more open source versions of Notepad2, in the future. Ain't fun like that.

Let's encourage Florian to keep up the great work and as we go about our lives as members of the Open Source community, let's remember to appreciate all the hard work that folks put into their various projects and that just because Copy/Paste is easy doesn't make it right.

My Tips? When in Doubt...

If you're doing Open Source and you're unclear about Netiquette, then:

  • Just ask.
    • Ask someone working on a project how they do it.
  • Err on the side of attribution.
    • You can't attribute or thank too many people, especially if you've copy/pasted something.
  • Reach Out.
    • Email the authors. Talk to people, join mailing lists and for goodness sake, avoid working in a vacuum.
    • I still get emails about GlucoPilot, a shareware app I wrote almost 8 years ago. If I had the rights to the source, I'd give to the world. (Still working on that, actually)
  • Appreciate your peers and everyone that contributes to the community.

What do you think?

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook bluesky subscribe
About   Newsletter
Hosting By
Hosted on Linux using .NET in an Azure App Service

A Back To Basics Case Study: Implementing HTTP File Upload with ASP.NET MVC including Tests and Mocks

June 28, 2008 Comment on this post [23] Posted in ASP.NET | ASP.NET MVC | Back to Basics
Sponsored By

A number of folks have asked me how to "implement the ASP.NET File Upload Control" except using ASP.NET MVC. This is a really interesting question for a number of reasons and a great opportunity to explore some fundamentals.

First, ASP.NET MVC is different since we don't get to use ASP.NET Server Controls as we're used to them. There's no "server controls" in the way that we're used to them.

Second, it'd be important to write Unit Tests for something like File Upload, and since ASP.NET MVC tries to be Unit Test friendly, it's an interesting problem to do tests. Why is it interesting? Well, ASP.NET MVC sits on top of ASP.NET. That means ASP.NET MVC didn't do any special work for File Upload support. It uses whatever stuff is built into ASP.NET itself. This may or not be helpful or interesting or even easy to test.

It seems then, that this is a good exercise in understanding a number of things:

  • HTTP and How File Upload works via HTTP
  • What ASP.NET offers for to catch File Uploads
  • How to Mock things that aren't really Mock Friendly
  • And ultimately, How to do File Upload with ASP.NET MVC

Here we go.

HTTP and How File Upload works via HTTP

It's always better, for me, to understand WHY and HOW something is happening. If you say "just because" or "whatever, you just add that, and it works" then I think that's sad. For some reason while many folks understand FORM POSTs and generally how form data is passed up to the server, when a file is transferred many just conclude it's magic.

Why do we have to add enctype="multipart/form=data" on our forms that include file uploads? Because the form will now be POSTed in multiple parts.

If you have a form like this:

<form action="/home/uploadfiles" method="post" enctype="multipart/form-data">
<label for="file">Filename:</label>
<input type="file" name="file" id="file" /><br />
<input type="submit" name="submit" value="Submit" />
</form>

The resulting Form POST will look like this (slightly simplified):

POST /home/uploadfiles HTTP/1.1
Content-Type: multipart/form-data; boundary=---------------------------7d81b516112482
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; WOW64)
Content-Length: 324

-----------------------------7d81b516112482
Content-Disposition: form-data; name="file"; filename="\\SERVER\Users\Scott\Documents\test.txt"
Content-Type: text/plain

foo
-----------------------------7d81b516112482
Content-Disposition: form-data; name="submit"

Submit
-----------------------------7d81b516112482--

Notice a few things about this POST. First, notice the content-type and boundary="" and how the boundary is used later, as exactly that, a boundary between the multiple parts. See how the first part shows that I uploaded a single file, of type text/plain. You can interpolate from this how you'd expect multiple files to show up if they were all POSTed at once.

And of course, look at how different this would look if it were just a basic form POST without the enctype="multipart/form=data" included:

POST /home/uploadfiles HTTP/1.1
Content-Type: application/x-www-form-urlencoded
UA-CPU: x86
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; WOW64)
Content-Length: 13

submit=Submit

See how the content type is different? This is a regular, typical form POST. Perhaps atypical in that it includes only a Submit button!

The point is, when folks add a ASP.NET FileUpload Control to their designer, it's useful to remember that you're buying into an abstraction over something. In this case, you're using a control that promises to hide the whole multipart MIME way of looking at things, and that's totally cool.

Back To Basics Tip
Know what your library is hiding from you and why you chose it.

As an aside, if you looked at an email of yours with multiple attached files, it would look VERY similar to the body of the first HTTP message as multipart MIME encoding is found everywhere, as is common with most good ideas.

What ASP.NET offers for to catch File Uploads

The FileUpload control is just a control that sits on top of a bunch of support for FileUploads in ASP.NET, starting with the classes Request.Files and HttpPostedFile. Those are the things that actually do the hold on to the parsed Files from an HTTP Request. You can use them to get a hold of a stream (a bunch of bytes in memory that are the file) or just save the file.

Since we can't use ASP.NET Server Controls in ASP.NET MVC, we'll use these classes instead. Here's how you usually grab all the files from an upload and save them:

foreach (string file in Request.Files)
{
HttpPostedFile hpf = Request.Files[file] as HttpPostedFile;
if (hpf.ContentLength == 0)
continue;
string savedFileName = Path.Combine(
AppDomain.CurrentDomain.BaseDirectory,
Path.GetFileName(hpf.FileName));
hpf.SaveAs(savedFileName);
}

Of course, you might want to change the directory and filename, maybe check the mimeType to allow only certain kinds of files, or check the length to limit your uploads, but this is the general idea.

Note that Request.Files has been around since 1.x and isn't a strongly typed collection of anything, so the GetEnumerator() of .Files that we're using in the foreach returns strings that are then used as keys into the Files[] indexer. It's a little wonky as it's old.

However, don't let me get ahead of myself, let's write the tests first!

How to Mock things that aren't really Mock Friendly

After creating a new ASP.NET MVC Project and making sure to select a test framework, I'll drop into a Controller Test and make a new TestMethod that kind of looks like I expect my method to be used.

[TestMethod]
public void FakeUploadFiles()
{
HomeController controller = new HomeController();

ViewResult result = controller.UploadFiles() as ViewResult;
var uploadedResult = result.ViewData.Model as List<ViewDataUploadFilesResult>;
Assert.AreEqual(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "foo.doc"), uploadedResult[0].Name);
Assert.AreEqual(8192, uploadedResult[0].Length);
}

This is incomplete, though, as I'm writing the tests before I the implementation exists. I need to think about how this should be implemented, and as I learn what should be mocked, I need to go back and forth between the tests and the implementation.

If we tried to compile this test, it won't, until I add a few types and methods. Once it actually compiles, but before I write the method itself, I'll want to see it FAIL. If you get a test to PASS on the first try, you don't really know yet if it CAN fail. Making it fail first proves that it's broken. Then you get to fix it.

Back To Basics Tip
Remember, in TDD, if it ain't broke, you don't get to fix it.

image

There's a bit of a chicken and the egg because it's unclear what will need to be mocked out until I start the implementation. However, this draft method above generally says what I want to do. I want to my controller to have a method called UploadFiles() that will grab the uploaded files from Request.Files, save them, then put a type in the ViewData saying which files were saved and how large they were.

Ok, take a breath. The following code may look freaky, but it's really cool actually. You can use any Mock Framework you like, but I like Moq for it's fluency.

We're having to "mock" things because we need to lie to our controller, who's expecting an HTTP Post, remember? It's going to go and spin through Request.Files and try to save each file. Since we want to test this without the web server or web browser, we'll want to tell the Moq framework about our expectations.

Back To Basics Tip
Be careful to mock context and assert outputs but don't mock away the whole test!

I've commented the code to explain...

[TestMethod]
public void FakeUploadFiles()
{
//We'll need mocks (fake) of Context, Request and a fake PostedFile
var request = new Mock<HttpRequestBase>();
var context = new Mock<HttpContextBase>();
var postedfile = new Mock<HttpPostedFileBase>();

//Someone is going to ask for Request.File and we'll need a mock (fake) of that.
var postedfilesKeyCollection = new Mock<HttpFileCollectionBase>();
var fakeFileKeys = new List<string>() { "file" };

//OK, Mock Framework! Expect if someone asks for .Request, you should return the Mock!
context.Expect(ctx => ctx.Request).Returns(request.Object);
//OK, Mock Framework! Expect if someone asks for .Files, you should return the Mock with fake keys!
request.Expect(req => req.Files).Returns(postedfilesKeyCollection.Object);

//OK, Mock Framework! Expect if someone starts foreach'ing their way over .Files, give them the fake strings instead!
postedfilesKeyCollection.Expect(keys => keys.GetEnumerator()).Returns(fakeFileKeys.GetEnumerator());

//OK, Mock Framework! Expect if someone asks for file you give them the fake!
postedfilesKeyCollection.Expect(keys => keys["file"]).Returns(postedfile.Object);

//OK, Mock Framework! Give back these values when asked, and I will want to Verify that these things happened
postedfile.Expect(f => f.ContentLength).Returns(8192).Verifiable();
postedfile.Expect(f => f.FileName).Returns("foo.doc").Verifiable();

//OK, Mock Framework! Someone is going to call SaveAs, but only once!
postedfile.Expect(f => f.SaveAs(It.IsAny<string>())).AtMostOnce().Verifiable();

HomeController controller = new HomeController();
//Set the controller's context to the mock! (fake)
controller.ControllerContext = new ControllerContext(context.Object, new RouteData(), controller);

//DO IT!
ViewResult result = controller.UploadFiles() as ViewResult;

//Now, go make sure that the Controller did its job
var uploadedResult = result.ViewData.Model as List<ViewDataUploadFilesResult>;
Assert.AreEqual(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "foo.doc"), uploadedResult[0].Name);
Assert.AreEqual(8192, uploadedResult[0].Length);

postedfile.Verify();
}

How to do File Upload with ASP.NET MVC

Now, what is the least amount of code in our Controller do we need to write to make this test pass? Here we get to use the Request.Files method that ASP.NET (not ASP.NET MVC) has had for years, and use it as advertised. It works in the tests and it works in production.

Important Note: We have to use the HttpPostedFileBase class, rather than the HttpPostedFile because every Request, Response, HttpContext and all related ASP.NET intrinsic abstractions are one layer farther way in ASP.NET MVC. If you get an HttpRequest in ASP.NET, then in ASP.NET MVC at runtime...

  • you'll get an HttpRequestWrapper while running under a Webserver
  • you'll get a dynamically generated derived Mock of an HttpRequestBase while running outside a Webserver (like inside a test) when you've made your own ControllerContext.

In each case, the instances you'll get are both (ultimately) of type HttpRequestBase, but it's this extra layer of abstraction that makes ASP.NET MVC easy to test and ASP.NET WebForms less so. I hope these Wrappers will be included in a future release of WebForms. The fact that they live in the System.Web.Abstractions.dll and not System.Web.Mvc.Abstractions.dll tells me someone has their eye on that particular ball.

At any rate, here's the Controller that takes File Upload requests:

public class ViewDataUploadFilesResult
{
public string Name { get; set; }
public int Length { get; set; }
}

public class HomeController : Controller
{
public ActionResult UploadFiles()
{
var r = new List<ViewDataUploadFilesResult>();

foreach (string file in Request.Files)
{
HttpPostedFileBase hpf = Request.Files[file] as HttpPostedFileBase;
if (hpf.ContentLength == 0)
continue;
string savedFileName = Path.Combine(
AppDomain.CurrentDomain.BaseDirectory,
Path.GetFileName(hpf.FileName));
hpf.SaveAs(savedFileName);

r.Add(new ViewDataUploadFilesResult()
{ Name = savedFileName,
Length = hpf.ContentLength });
}
return View("UploadedFiles",r);
}
}

At the bottom where I ask for the "UploadedFiles" view, and I pass in my list of ViewDataUploadFilesResults. This will appea in the ViewData.Model property. The View then displays them, and that's ALL the View does.

<ul>
<% foreach (ViewDataUploadFilesResult v in this.ViewData.Model) { %>
<%=String.Format("<li>Uploaded: {0} totalling {1} bytes.</li>",v.Name,v.Length) %>
<% } %>
</ul>

Conclusion

I always encourage people to take the little bit of time to use Fiddler or SysInternals or look at your call stack or just to take a breath and remind oneself, "so how is this supposed to work?" Otherwise, one is just cargo-cult programming.

This post was a long answer to the question "How do I do FileUpload with ASP.NET MVC?" but I feel better having written in this way.

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook bluesky subscribe
About   Newsletter
Hosting By
Hosted on Linux using .NET in an Azure App Service

Hanselminutes Podcast 119 - What Is Done? with Scrum Co-Creator Ken Schwaber

June 28, 2008 Comment on this post [6] Posted in Podcast
Sponsored By

Photo of Ken Schwaber My one-hundred-and-nineteeth podcast is up. In this episode, I chat with Ken Schwaber. Ken bends my reality by helping me define the word "Done." In the process, I learn more about the right questions to ask when managing an Agile Project.

"Ken Schwaber co-developed the Scrum process with Jeff Sutherland in the early 1990s to help organizations struggling with complex development projects. One of the signatories to the Agile Manifesto in 2001, he subsequently founded the AgileAlliance, a nonprofit organization dedicated to the creation of Agile software. He then founded the ScrumAlliance, a nonprofit organization dedicated to expanding the understanding of Scrum."

Subscribe: Subscribe to Hanselminutes Subscribe to my Podcast in iTunes

If you have trouble downloading, or your download is slow, do try the torrent with µtorrent or another BitTorrent Downloader.

Do also remember the complete archives are always up and they have PDF Transcripts, a little known feature that show up a few weeks after each show.

Telerik is our sponsor for this show.

Telerik's new stuff is pretty sweet, check out the ONLINE DEMO of their new ASP.NET AJAX suite. RadGrid handles sorting, filtering, and paging of hundreds of thousands of records in milliseconds, and the RadEditor loads up to 4 times faster and the navigation controls now support binding to web services on the client.

As I've said before this show comes to you with the audio expertise and stewardship of Carl Franklin. The name comes from Travis Illig, but the goal of the show is simple. Avoid wasting the listener's time. (and make the commute less boring)

Enjoy. Who knows what'll happen in the next show?

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook bluesky subscribe
About   Newsletter
Hosting By
Hosted on Linux using .NET in an Azure App Service

Disclaimer: The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.