ASP.NET MVC – Multiple Files Upload Bug or by-design issue?

Update: Yes. Kiran confirmed that it’s by design.

This is by design. This is part of System.Web and so we are not too inclined to fix this issue currently. A similar related issue was filed against Web API. You can check the resolution information on it.

http://aspnetwebstack.codeplex.com/workitem/188

Thanks,
Kiran

The issue is that when you http-post the request that contains a file html element then you got 1 for files.Count() but null for files.First().

One of my ex-colleagues faced this issue and share me this stackoverflow question posted by someone who is having a similar issue.

Here is the code for replicating the issue. (I will upload the sample to my blog’s git repository later.)

View

@using (Html.BeginForm("Upload", "Home", FormMethod.Post,
                       new { enctype = "multipart/form-data"}))
{
    <input name="files" type="file" multiple="multiple" />
    <input type="submit" value="Upload" />
}

Controller

[HttpPost]
        public ActionResult Upload(IEnumerable<HttpPostedFileBase> files) {
            if (files.Count() > 0) Console.WriteLine(files.Count()); // display 1
            if (files.Any()) Console.WriteLine(files.Any()); // display true
            if (files.First() == null) Console.WriteLine("first null"); // display "first null"
            return View();
        }

Screenshots

asp.net mvc file upload issues

Steps to replicate the issue

  1. Run the program
  2. Click on “Upload” without selecting any file.
  3. Check the value of “files” enumerable. You will see 1 for files.Count() and null for files.First().

Ok. I am not sure whether this is the bug or by-design issue so I posted the questions in MVC codeplex forum.

Request.Files

While I am waiting the reply from MVC team, I did a few experiments on this. The first thing that I did is to use Request.Files instead of binder so I changed the code as below.


[HttpPost]
public ActionResult Upload() {
var files = Request.Files;

return View();
}

Here is what I got. I believe that MVC code is also using Request.Files in binder to do the transformation from raw request to object. The count for Request.Files is also 1 even we don’t even select any file.

Request.Files return

 

As the Request.Files has a similar logic, I captured the raw http request in fidder and here is what I found.

POST http://localhost:17002/Home/Upload HTTP/1.1
Accept: text/html, application/xhtml+xml, */*
Referer: http://localhost:17002/
Accept-Language: en-US
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; MALC; rv:11.0) like Gecko
Content-Type: multipart/form-data; boundary=—————————7de1d0220fe6
Accept-Encoding: gzip, deflate
Connection: Keep-Alive
Content-Length: 191
DNT: 1
Host: localhost:17002
Pragma: no-cache

—————————–7de1d0220fe6
Content-Disposition: form-data; name=”files”; filename=””
Content-Type: application/octet-stream
—————————–7de1d0220fe6–

You see, there is one empty item “filename=””” in form-data when we submit the request without selecting the file. This might be the reason why Request.Files is returning the count 1 instead of 0.

Request.Files (“file”) Vs HttpPostedFileBase (null)

Why do we got null for first index of “Files” enumerable?

HttpFileCollectionValueProvider.GetHttpPostedFileDictionary method create the directory based on http file request. HttpPostedFileBaseModelBinder.ChooseFileOrNull returns null value for filename=””. HttpFileCollectionValueProvider added that value to directory without checking the null value so I think it might be a by-design issue.

Here is the test code that shows null value.


[HttpPost]
public ActionResult Upload() {
var files = Request.Files;

//HttpFileCollectionValueProvider.GetHttpPostedFileDictionary
// build up the 1:many file mapping
List<KeyValuePair<string, HttpPostedFileBase>> mapping = new List<KeyValuePair<string, HttpPostedFileBase>>();
string[] allKeys = files.AllKeys;
for (int i = 0; i < files.Count; i++) {
string key = allKeys[i];
if (key != null) {
HttpPostedFileBase file = ChooseFileOrNull(files[i]);
}
}

return View();
}
internal static HttpPostedFileBase ChooseFileOrNull(HttpPostedFileBase rawFile) {
// case 1: there was no <input type="file" ... /> element in the post
if (rawFile == null) {
return null;
}

// case 2: there was an <input type="file" ... /> element in the post, but it was left blank
if (rawFile.ContentLength == 0 && String.IsNullOrEmpty(rawFile.FileName)) {
return null;
}

// case 3: the file was posted
return rawFile;
}

That’s all that I have tested.

If you are interested in this issue, you can probably check the following methods below.

  • HttpFileCollectionValueProvider.GetHttpPostedFileDictionary
  • HttpPostedFileBaseModelBinder.ChooseFileOrNull

I will update this blog post when I get any reply from MVC team.

 

“The Provider encountered an unknown error” when creating new users in EF DbMigrationsConfiguration.Seed method

The objective was to create some default users in EF Seed method so we could login without having a link for new user registration in application.

The code that we have was as below.


WebSecurity.InitializeDatabaseConnection("DbContext", "UserAccounts", "Id", "UserLogOnId", true);

 if (!WebSecurity.UserExists("Test1"))
 {
 WebSecurity.CreateAccount("Test1", "123456", false);
 }

 context.UserAccounts.AddOrUpdate(
 u => u.UserLogOnId,
 new UserAccount
 {
 UserLogOnId = "Test1",
 UserDisplayName = "Test Staff 1",
 Status = UserAccountStatus.Enabled,
 ModifiedBy = string.Empty,
 EmailAddress = "user1@gmail.com",
 LastModifiedDate = new DateTimeOffset(DateTime.UtcNow, TimeSpan.Zero),
 LastLogOnDateTime = new DateTimeOffset(DateTime.UtcNow, TimeSpan.Zero)
 },

When we called update-database from nuget console, we got this error below. It was a bit weird because we already had  all required assemblies referenced in the project and we had the correct configuration in app.config as well. Why did we get this error?

Error Message

Running Seed method.
System.Web.Security.MembershipCreateUserException: The Provider encountered an unknown error.
at WebMatrix.WebData.SimpleMembershipProvider.CreateAccount(String userName, String password, Boolean requireConfirmationToken)
at WebMatrix.WebData.WebSecurity.CreateAccount(String userName, String password, Boolean requireConfirmationToken)
at MyProject.Migrations.Configuration.Seed(MyDbContext context) in c:\Users\root\Desktop\MyProject\codes\src\Models\Migrations\Configuration.cs:line 39
at System.Data.Entity.Migrations.DbMigrationsConfiguration`1.OnSeed(DbContext context)
at System.Data.Entity.Migrations.DbMigrator.SeedDatabase()
at System.Data.Entity.Migrations.Infrastructure.MigratorLoggingDecorator.SeedDatabase()
at System.Data.Entity.Migrations.DbMigrator.Upgrade(IEnumerable`1 pendingMigrations, String targetMigrationId, String lastMigrationId)
at System.Data.Entity.Migrations.Infrastructure.MigratorLoggingDecorator.Upgrade(IEnumerable`1 pendingMigrations, String targetMigrationId, String lastMigrationId)
at System.Data.Entity.Migrations.DbMigrator.UpdateInternal(String targetMigration)
at System.Data.Entity.Migrations.DbMigrator.<>c__DisplayClassc.b__b()
at System.Data.Entity.Migrations.DbMigrator.EnsureDatabaseExists(Action mustSucceedToKeepDatabase)
at System.Data.Entity.Migrations.Infrastructure.MigratorBase.EnsureDatabaseExists(Action mustSucceedToKeepDatabase)
at System.Data.Entity.Migrations.DbMigrator.Update(String targetMigration)
at System.Data.Entity.Migrations.Infrastructure.MigratorBase.Update(String targetMigration)
at System.Data.Entity.Migrations.Design.ToolingFacade.UpdateRunner.Run()
at System.AppDomain.DoCallBack(CrossAppDomainDelegate callBackDelegate)
at System.AppDomain.DoCallBack(CrossAppDomainDelegate callBackDelegate)
at System.Data.Entity.Migrations.Design.ToolingFacade.Run(BaseRunner runner)
at System.Data.Entity.Migrations.Design.ToolingFacade.Update(String targetMigration, Boolean force)
at System.Data.Entity.Migrations.UpdateDatabaseCommand.<>c__DisplayClass2.b__0()
at System.Data.Entity.Migrations.MigrationsDomainCommand.Execute(Action command)
The Provider encountered an unknown error

EF Seed and WebSecurity - 1

Solution

Yes. The error message might be a bit misleading. But we realized about the problem after looking at the API below. You can’t create the account without creating the user. That’s why we got this error above. Its nothing related to the provider.

Here, as you can see,  this API below allows us to create both user and account at the same time and you can pass the custom properties that you can for user table.

WebSecurity.CreateUserAndAccount

Here is the correct code that you should use if you want to create a default user from Seed method. Of course, don’t forget to add the required assemblies as references in your startup project.


WebSecurity.InitializeDatabaseConnection("DbContext", "UserAccounts", "Id", "UserLogOnId", true);

 if (!WebSecurity.UserExists("Test1"))
 {
 WebSecurity.CreateAccount("Test1", "123456",
   new {
          UserDisplayName = "Test Staff 1",
          Status = UserAccountStatus.Enabled,
          ModifiedBy = string.Empty,
          EmailAddress = "user1@gmail.com",
          LastModifiedDate = new DateTimeOffset(DateTime.UtcNow, TimeSpan.Zero),
          LastLogOnDateTime = new DateTimeOffset(DateTime.UtcNow, TimeSpan.Zero)
     }
 );
 }

Hope it helps.

Problem/Solution: Error in publishing ASP.NET MVC with MvcBuildViews ON from Visual Studio 2012

Note: This post is contributed by Matthew Seng. He is one of my colleagues and we are working in same project together.

ASP.NET Publish Error

Error Message

It is an error to use a section registered as allowDefinition=’MachineToApplication’ beyond application level. This error can be caused by a virtual directory not being configured as an application in IIS.

Steps to replicate the issue

  1. Create a new ASP.NET MVC 4 project in Visual Studio 2012
  2. Set true to MvcBuildViews in csproj (<MvcBuildViews>true</MvcBuildViews>)
  3. Publish the project. (Observe: it will work fine.)
  4. Publish it again (Observe: You will see the error message and you won’t be able to publish the project anymore.)

Temporary solution:

  • Delete obj folder from your MVC project.

Permanent solution:

  • Delete obj folder from your MVC project.
  • Add the following below the MvcBuildViews property:<BaseIntermediateOutputPath>..\obj</BaseIntermediateOutputPath>

Root cause:

MvcBuildViews property (This is to allow view errors to be shown when building solution) set to true in project file and multiple web.config confuses compiler. Learn more at:

Visual Studio 2012 – Exception in adding new controller to MVC4 project

Problem: VS11 (Visual Studio 2012) throws the following exception when I add new controller in ASP.NET MVC4 project.

VS 2012 issue with MVC 4

—————————

The templates had the following 1 error(s) when running:
—————————
C:\Program Files (x86)\Microsoft Visual Studio 11.0\Common7\IDE\ItemTemplates\CSharp\Web\MVC 4\CodeTemplates\AddController\Controller.tt(-1,-1) : error : There was a problem getting an AppDomain to run the transformation from the host. The process cannot continue.

—————————
OK
—————————

Environment

  • Windows 7 Professional (Version 6.1 – Build 7601: Service Pack 1)
  • Microsoft Visual Studio Ultimate 2012 (Version 11.0.60610.01 Update 3)
  • Microsoft .NET Framework Version 4.5.50709

Workaround

Just restart the Visual Studio.

Note: There is a bug report VS 11 – Error when Attempting to Add New Controller to Project in MS connect but as of now, there is no official word on this issue. I will update this post again if I get any input from somewhere.

Update #1.

I got another exception when I try to add new view to the same project.

VS 2012 issue with MVC 4-2

—————————
The templates had the following 1 error(s) when running:
—————————
C:\Program Files (x86)\Microsoft Visual Studio 11.0\Common7\IDE\ItemTemplates\CSharp\Web\MVC 4\CodeTemplates\AddView\CSHTML\Empty.tt(-1,-1) : error : There was a problem getting an AppDomain to run the transformation from the host. The process cannot continue.
—————————
OK
—————————

MvcMailer

Problem

A lot of people recommended that MvcMailer is a great package for those who want to send email from ASP.NET MVC app but seems like the author of that package really didn’t get time to update the dependencies of his package.

Here is what you will see when you try installing MvcMailer in VS 2010.

PM> Install-Package mvcmailer
Attempting to resolve dependency ‘T4Scaffolding (≥ 0.9.7)’.
Attempting to resolve dependency ‘EntityFramework (≥ 4.1.10311.0)’.
You are downloading EntityFramework from Microsoft, the license agreement to which is available at http://go.microsoft.com/fwlink/?LinkId=224682. Check the package for additional dependencies, which may come with their own license agreement(s). Your use of the package and dependencies constitutes your acceptance of their license agreements. If you do not accept the license agreement(s), then delete the relevant components from your device.
Successfully installed ‘EntityFramework 4.1.10715.0’.
Successfully installed ‘T4Scaffolding 0.9.9’.
Successfully installed ‘MvcMailer 1.1’.
Successfully removed ‘EntityFramework 4.1.10331.0’ from MvcApplication3.
Successfully added ‘EntityFramework 4.1.10715.0’ to MvcApplication3.
Successfully added ‘T4Scaffolding 0.9.9’ to MvcApplication3.
Set-DefaultScaffolder : Unable to cast object of type ‘NuGet.SemanticVersion’ to type ‘System.Version’.
At C:\users\michael.sync\documents\visual studio 2010\Projects\MvcApplication3\packages\T4Scaffolding.0.9.9\tools\install.ps1:22 char:22
+ Set-DefaultScaffolder <<<< -Name DbContext -Scaffolder T4Scaffolding.EFDbContext -SolutionWide -DoNotOverwriteExistingSetting
+ CategoryInfo : NotSpecified: (:) [Set-DefaultScaffolder], InvalidCastException
+ FullyQualifiedErrorId : T4Scaffolding.Cmdlets.SetDefaultScaffolderCmdlet

Set-DefaultScaffolder : Unable to cast object of type ‘NuGet.SemanticVersion’ to type ‘System.Version’.
At C:\users\michael.sync\documents\visual studio 2010\Projects\MvcApplication3\packages\T4Scaffolding.0.9.9\tools\install.ps1:23 char:22
+ Set-DefaultScaffolder <<<< -Name Repository -Scaffolder T4Scaffolding.EFRepository -SolutionWide -DoNotOverwriteExistingSetting
+ CategoryInfo : NotSpecified: (:) [Set-DefaultScaffolder], InvalidCastException
+ FullyQualifiedErrorId : T4Scaffolding.Cmdlets.SetDefaultScaffolderCmdlet

Set-DefaultScaffolder : Unable to cast object of type ‘NuGet.SemanticVersion’ to type ‘System.Version’.
At C:\users\michael.sync\documents\visual studio 2010\Projects\MvcApplication3\packages\T4Scaffolding.0.9.9\tools\install.ps1:24 char:22
+ Set-DefaultScaffolder <<<< -Name CustomTemplate -Scaffolder T4Scaffolding.CustomTemplate -SolutionWide -DoNotOverwriteExistingSetting
+ CategoryInfo : NotSpecified: (:) [Set-DefaultScaffolder], InvalidCastException
+ FullyQualifiedErrorId : T4Scaffolding.Cmdlets.SetDefaultScaffolderCmdlet

Set-DefaultScaffolder : Unable to cast object of type ‘NuGet.SemanticVersion’ to type ‘System.Version’.
At C:\users\michael.sync\documents\visual studio 2010\Projects\MvcApplication3\packages\T4Scaffolding.0.9.9\tools\install.ps1:25 char:22
+ Set-DefaultScaffolder <<<< -Name CustomScaffolder -Scaffolder T4Scaffolding.CustomScaffolder -SolutionWide -DoNotOverwriteExistingSett
ing
+ CategoryInfo : NotSpecified: (:) [Set-DefaultScaffolder], InvalidCastException
+ FullyQualifiedErrorId : T4Scaffolding.Cmdlets.SetDefaultScaffolderCmdlet

Successfully added ‘MvcMailer 1.1’ to MvcApplication3.
Set-DefaultScaffolder : Unable to cast object of type ‘NuGet.SemanticVersion’ to type ‘System.Version’.
At C:\users\michael.sync\documents\visual studio 2010\Projects\MvcApplication3\packages\MvcMailer.1.1\tools\Install.ps1:23 char:22
+ Set-DefaultScaffolder <<<< -Name Mailer -Scaffolder $mailerScaffolder -SolutionWide -DoNotOverwriteExistingSetting
+ CategoryInfo : NotSpecified: (:) [Set-DefaultScaffolder], InvalidCastException
+ FullyQualifiedErrorId : T4Scaffolding.Cmdlets.SetDefaultScaffolderCmdlet
—————————READ ME—————————————————

Your default Mailer Scaffolder is set to Mailer.Razor

You can generate your Mailers and Views using the following Scaffolder Command

PM> Scaffold Mailer UserMailer Welcome,GoodBye

Edit the smtp configuration at web.config file before you send an email

You can find more at: https://github.com/smsohan/MvcMailer/wiki/MvcMailer-Step-by-Step-Guide

————————————————————————————-
Successfully uninstalled ‘EntityFramework 4.1.10331.0’.

PM>

Solution

As you can see, MvcMailer has the dependency on “T4Scaffolding (v0.9.9)” package which has dependency on “EFCodeFirst” package which is the outdated version of EF. All you have to do to make MvcMailer works is very simple. Just update “T4Scaffolding” package by using “Update-Package” command from Nuget Console.

PM> Update-Package T4Scaffolding
Updating ‘T4Scaffolding’ from version ‘0.9.9’ to ‘1.0.6’ in project ‘MvcApplication3’.
Successfully removed ‘T4Scaffolding 0.9.9’ from MvcApplication3.
Successfully installed ‘T4Scaffolding 1.0.6’.

A different version of T4Scaffolding is already running in this instance of Visual Studio
Please restart Visual Studio to avoid unexpected behavior.
You won’t be able to use scaffolding until you restart Visual Studio.

Successfully added ‘T4Scaffolding 1.0.6’ to MvcApplication3.
Successfully uninstalled ‘T4Scaffolding 0.9.9’.

After updating the package, you need to restart the Visual Studio.

Testing

Now, you can test MvcMailer’s Scaffold by using the command below. If you want to know more details, you can read “MvcMaler: Step by Stp Guide“.

PM> Scaffold Mailer.Razor UserMailer Welcome,PasswordReset
Added MvcMailer output ‘Mailers\IUserMailer.cs’
Added MvcMailer output ‘Mailers\UserMailer.cs’
Added MyScaffolder output ‘Views\UserMailer\_Layout.cshtml’
Added MyScaffolder output ‘Views\UserMailer\Welcome.cshtml’
Added MyScaffolder output ‘Views\UserMailer\PasswordReset.cshtml’
PM>

Tips! ASP.NET MVC – JavaScriptSerializer – 3 Questions and 3 Answers

Thanks to Darin Dimitrov (MVP)  for answering questions in very details!

Test Project Download ~

  • git clone: git@github.com:michaelsync/Michael-Sync-s-blog-sample.git
  • AjaxJsonTest

Scenario

I have the following model, view and controller.

Model

public class Person {
 public string Name;// { get; set; }
 public string Occupation { get; set; }
 public int Salary { get; set; }
 public int[] NumArr { get; set; }
}

View

<input id="Test" type="button" value="Test" />

@section Javascript{
<script type="text/javascript">// <![CDATA[
 $(function () {
 $("#Test").click(function () {
 var data = { Name: "Michael Tom", Occupation: "Programmer", Salary: 8500, NumArr: [2,6,4,9] };
 var url = "Home/GetJson";
 $.ajax({
 url: url,
 dataType: "json",
 type: "POST",
 data: data,
 traditional: true,
 success: function (result) {

}
 });

});
 });
// ]]></script>
}

Controller

public class HomeController : Controller
 {
 public ActionResult Index()
 {
 return View();
 }

public JsonResult GetJson(Person person) {

return Json(......);
 }
 }

Questions

1) Why can’t we use the public field instead of properties in Model? 

Because the model binder works only with properties. It is by design. Normally fields should be private in your classes. They are implementation detail. Use properties to expose some behavior to the outer world.

2) If I changed the type of NumArr property to List<int> then it doesn’t work. How can we use List instead of the int[]? I know I’m passing the array from JS. Can we pass List<T> from JS as well?

It should work. No matter whether you use List<int>, IEnumerable<int> or int[], it’s the same and it works. What wouldn’t work is if you wanted to use a collection of some complex object like for example List<SomeComplexType> (see my answer below for a solution to this).

3) I’m using “traditional: true” in Javascript code block of View because serialization doesn’t work with “traditional: false”. Does ASP.NET MVC’s serializer support only old version?

Yes, the traditional parameter was introduced in jQuery 1.4 when they changed the way jQuery serializes parameters. The change happened to be non-compatible with the standard convention that the model binder uses in MVC when binding to lists.

So download a javascript debugging tool such as FireBug and start playing. You will see the differences in the way jQuery sends the request when you set this parameter.

All this being said I would recommend you to send JSON encoded requests to your controller actions. This will work with any complex objects and you don’t have to ask yourself which version of jQuery you are using or whatever because JSON is a standard interoperable format. The advantage of a standard interoperable format is that no matter which system you use, you should be able to make them talk the same language (unless of course there are bugs in those systems and they do not respect the defined standard).

But now you could tell me that application/x-www-form-urlencoded is also a standard and inetroperable format. And that’s true. The problem is that the model binder uses a convention when binding the name of the input fields into properties of your models. And this convention is far from something being interoperable or industry standard.

So for example you could have an hierarchy of models:

public class Person
{
 public string Name { get; set; }
 public string Occupation { get; set; }
 public int Salary { get; set; }
 public List SubObjects { get; set; }
}

public class SubObject
{
 public int Id { get; set; }
 public string Name { get; set; }
}

You could imagine any complex hierarchy you like (at the exception of circular references which are not supported by the JSON format). and then:

var data = {
 name: "Michael Tom",
 occupation: "Programmer",
 salary: 8500,
 subObjects: [
 { id: 1, name: 'sub 1' },
 { id: 2, name: 'sub 2' },
 { id: 3, name: 'sub 3' }
 ]
};

$.ajax({
 url: "Home/GetJson",
 type: "POST",
 data: JSON.stringify({ person: data }),
 contentType: 'application/json',
 success: function (result) {

}
});

We set the Content-Type request HTTP header to application/json to indicate the built-in JSON value provider factory that the request is JSON encoded (using the JSON.stringify method) and it will parse it back to your strongly typed model. The JSON.stringify method is natively built into modern browsers but if you wanted to support legacy browsers you could include the json2.js script to your page.

Reference: