51

As a fairly experienced ASP.Net developer just recently starting using MVC, I find myself struggling a bit to change my mindset from a traditional "server control and event handler" way of doing things, into the more dynamic MVC way of things. I think I am slowly getting there, but sometimes the MVC "magic" throws me off.

My current scenario is to create a web page that allows the user to browse a local file, upload it to the server and repeat this until he has a list of files to work with. When he is happy with the file list (which will be displayed in a grid on the page), he will click a button to process the files and extract some data that will be stored in a database.

The last part is not so important, right now I am struggling with something as trivial as building up a list of files, and persisting that list between requests. In the traditional approach this would be extremely simple - the data would be persisted in ViewState. But in MVC I need to pass the data between the controller and the views, and I don't fully get how this is supposed to work.

I guess I better post my rather incomplete attempt of coding this to explain the problem.

In order to keep my file list data, I have created a viewmodel that is basically a typed list of files, along with some extra metadata:

public class ImportDataViewModel
{
    public ImportDataViewModel()
    {
        Files = new List<ImportDataFile>();
    }

    public List<ImportDataFile> Files { get; set; }
...

In the view, I have a form for browsing and uploading the file:

<form action="AddImportFile" method="post" enctype="multipart/form-data">
  <label for="file">
    Filename:</label>
  <input type="file" name="file" id="file" />
  <input type="submit" />
  </form>

The view is using the viewmodel as its model:

@model MHP.ViewModels.ImportDataViewModel

This will send the file to my action:

public ActionResult AddImportFile(HttpPostedFileBase file, ImportDataViewModel importData)
    {

        if (file.ContentLength > 0)
        {
            ImportDataFile idFile = new ImportDataFile { File = file };
            importData.Files.Add(idFile);
        }

        return View("DataImport", importData);
    }

This action returns the view for the DataImport page along with the viewmodel instance containing the list of files.

This works nicely up to a certain point, I can browse a file and upload it, and I can see the viewmodel data inside the action, and then also if I put a breakpoint inside the view and debug "this.Model", everything is fine.

But then, if I try to upload another file, when putting a breakpoint inside the AddImportFile action, the importData parameter is empty. So the view is obviously not passing the current instance of its model to the action.

In the MVC samples I have been through, the model instance is "magically" passed to the action method as a parameter, so why is it empty now?

I assume the real problem is my limited understanding of MVC, and that there is probably a very simple solution to this. Anyways, I would be extremely grateful if somebody could point me in the right direction.

tereško
  • 56,151
  • 24
  • 92
  • 147
TMan
  • 1,260
  • 1
  • 13
  • 17
  • You are not storing the uploaded files anywhere on the server. How do you expect they to be persisted between the postbacks? All that you have inside your `
    ` is a single file input field. So all that you can expect to get in the controller action is this file. Nothing more. There's no magic. There's no ViewState in ASP.NET MVC.
    – Darin Dimitrov May 25 '12 at 14:38
  • Darin, thanks for your comment. It is correct that I am not storing the file on the server. I could store it in the database, but that would be a waste of database space and user patience, as it would take a long time and there is no real need for it. What I would like to do is just to keep it in server memory as a variable somehow until the user decides to process it. Then I will store the output of that processing to the database. I think either I am trying to use MVC the wrong way, or I just don't know how to do it. I just don't know which one it is yet... – TMan May 28 '12 at 08:44
  • 2
    Keeping files in server memory is one of the worst things. Avoid this at all price. Not only that this will consume lots of memory on your server but don't forget that IIS could recycle the application domain at any time, loosing all data stored in memory. Also what if you run in a server farm? If you store in memory of the server this means that only 1 server of the farm has this information and if the load balancer dispatches subsequent requests on another server of the farm you will loose the information. – Darin Dimitrov May 28 '12 at 08:49
  • Ok, so what are you suggesting then? To store everything in the database no matter what? In general, to store files in memory is usually a bad idea, but if you know that the files are small, few, and only one or a few users are performing the upload at the same time then I think it can be justified. But let's instead generalize the discussion a bit - instead of files, think of e.g. a shopping cart containing just a small amount of data. Is it still wrong to use some kind of session mechanism as an intermediate storage in MVC? What is the alternative? – TMan May 28 '12 at 09:25
  • I know this is old now, but I'm just curious why not consider the option of just temporarily writing the file to a folder, then cleaning it up? I've done that a number of times very successfully over the years. – Steve Nov 29 '17 at 21:34
  • 1
    It's funny to read these "old" questions, and think about how much the world has changed in only five years! Since then, I have dealth with file upload many times, and now I tend to think very differently about it than back then. I agree with Steves suggestion that storing the file temporarily is probably the best option, although I tend to use Azure blob storage rather than a folder on the web server. Heck, I even don't use IIS anymore, it's all just Azure web apps or Azure functions. – TMan Dec 01 '17 at 13:11
  • 1
    I also take back my previous claims about storing stuff in memory, I almost cannot believe I actually thought that was a good idea! :) I suppose, to my defense, that the application in question was a small enterprise app with few users and small files, but nevertheless, I would never think of doing that today! I guess we should all be grateful for the ability to look back at horrible mistakes from the past and conclude that we have developed a lot since then :) cheers! – TMan Dec 01 '17 at 13:18

2 Answers2

49

It's been some time since I posted the question, which was quite colored by my little experience and knowledge of MVC. Still I received some quite useful input, that eventually led me to find a solution and also gain some insight of MVC.

What threw me off in the first place, was that you could have a controller with a strongly typed object as a parameter, like this:

public ActionResult DoSomething(MyClass myObject)...

This object originated from the same controller:

...
return View(myObject);
...

This lead me to believe that the object lived throughout these two steps, and that I somehow could expect that you could send it to the view, do something and then "magically" get it back to the controller again.

After reading up about model binding, I understood that this is of course not the case. The view is completely dead and static, and unless you store the information somewhere, it is gone.

Going back to the problem, which was selecting and uploading files from the client, and building up a list of these files to be displayed, I realized that in general there are three ways to store information between requests in MVC:

  1. You can store information in form fields in the view, and post it back to the controller later
  2. You can persist it in some kind of storage, e.g. a file or a database
  3. You can store it in server memory by acessing objects that lives throughout requests, e.g. session variables

In my case, I had basically two types of information to persist: 1. The file metadata (file name, file size etc.) 2. The file content

The "by-the-book" approach would probably be to store the metadata in form fields, and the file contents in a file or in db. But there is also another way. Since I know my files are quite small and there will be only a few of them, and this solution will never be deployed in a server farm or similar, I wanted to explore the #3 option of session variables. The files are also not interesting to persist beyond the session - they are processed and discarded, so I did not want to store them in my db.

After reading this excellent article: Accessing ASP.NET Session Data Using Dynamics

I was convinced. I simply created a sessionbag class as described in the article, and then I could do the following in my controller:

    [HttpPost]
    public ActionResult AddImportFile(HttpPostedFileBase file)
    {

        ImportDataViewModel importData = SessionBag.Current.ImportData;
        if (importData == null) importData = new ImportDataViewModel();

        if (file == null)
            return RedirectToAction("DataImport");

        if (file.ContentLength > 0)
        {
            ImportDataFile idFile = new ImportDataFile { File = file };
            importData.Files.Add(idFile);
        }

        SessionBag.Current.ImportData = importData;

        return RedirectToAction("DataImport");
    }

I am fully aware that in most cases, this would be a bad solution. But for the few kb of server memory the files occupy and with the simplicity of it all, I think it worked out very nicely for me.

The additional bonus of using the SessionBag is that if the user entered a different menu item and then came back, the list of files would still be there. This would not be the case e.g. when choosing the form fields/file storage option.

As a final remark, I realize that the SessionBag is very easy to abuse, given the simplicity of use. But if you use it for what it is meant for, namely session data, I think it can be a powerful tool.

TMan
  • 1,260
  • 1
  • 13
  • 17
11

Regarding Uploading

1) Maybe consider an AJAX uploader with HTML to allow your user to select multiple files before they are sent to the server. This BlueImp Jquery AJAX file uploader is pretty amazing with a pretty great api: Blueimp Jquery File Upload. It'll allow users to drag and drop or multi select several files and edit the file order, include/exclude etc.. Then when they are happy they can press upload to sent to your controller or upload handler for processing on the server side.

2) You could make every upload persist to the database, though you would be reloading the entire page and writing some extra view model and razor code to achieve the listing effect. This is probably not going to be responsive...

Regarding Keeping State WebForms/MVC

Keeping state between requests is somewhat black magic and voodoo. When going into ASP.NET MVC, go in it understanding that web applications communicate using request and responses. So go in embracing the web as stateless and develop from there! When your model is posted through your controller, it's gone along with any variables in the controller! However before it goes away, you can store its contents in a database for retrieval later.

Web applications cannot keep true state like desktop applications. There are many ways ajax frameworks, and some voodoo tools that people employ to simulate state in the HTTP environment. And a simulation of state is really only a false mimicry of statefulness. ASP.NET Web Forms tries to simulate state as best it can by kind of hiding the stateless nature of HTTP from the developer. You can run into a lot of headache when trying to employ your own AJAX code in tandem with Web Forms markup code and its own Ajax framework.

I'm really glad you're learning MVC

All jokes aside, if you get the MVC/HTTP/Statelessness mentality, it'll be very easy to apply the patterns to other super popular frameworks like Ruby on Rails, SpringMVC (java), Django (python), CakePHP etc... This easy transfer of knowledge will help you become a much better developer and get REALLY good at Ajax.

I'm really glad you're learning MVC 3, I've been with a few internship at some very large firms that had these crazy large ASP.NET Web Forms projects with code flying everywhere just to change a few numerical values in the database (-_-') Felt like I was using scissors to knit a baby's sock. One easy wrong move, and everything broke. It felt like developing in PHP, you come out sweating and not really sure what happened and on what line. It was almost impossible to debug and update.

Max Alexander
  • 5,029
  • 6
  • 35
  • 48
  • though slightly obvious, i would add 2)a) above to use hidden fields to maintain the list of files between request. Ultimately the viewstate abstraction in web form uses hidden field, so its not such a bad idea after all and will be easier to implement than the database option. – Kiran May 25 '12 at 16:12
  • Max, thanks for a thorough answer. As for 1) - Yes, I probably will try to implement a multiple file upload functionality at a later stage to make it more user friendly, but that's not really the core issue. I think the core issue here is more about stateful vs stateless. I have come across this discussion before, but I don't fully get this whole 'embrace the stateless nature of MVC' thing. Are you saying we should not implement state in our web apps? Not everything should be stored in the db. Take the shopping cart for example. Humans are by nature stateful, so why shouldnt web apps be? – TMan May 28 '12 at 08:37
  • Kiran, I think you are right. I am now reading about the model binding, which seems to represent a large part of the perceived "magic" in my case - how form fields in the view are suddenly transformed into strongly typed class instances in the controller. When trying to apply the "magic" in a different scenario without understanding the underlying principles, it will of course go wrong. Think I am on the right track now. – TMan May 28 '12 at 09:14
  • I don't think it's possible to make web applications truly stateful like WinForms or Desktop Applications. Everything about ASP.NET state and other forms of AJAX state preservation is really an honorable mimicry of state :-\. Sure it might act like a stateful application, but when debugging "almost stateful" web apps you have to be honest to yourself and realize that there is no state. That's what I think it means by "embracing the stateless nature". – Max Alexander Jun 06 '12 at 21:01
  • This confirmed some things about MVC for me, which was helpful. Thanks! Good advice. – Steve Nov 29 '17 at 21:38