1

I have a form where a user can put in a date and view a table of data depending on the date. If the user wants to, he can download the data as a CSV file. This is my controller:

public class SomeController : Controller
{
    [HttpGet]
    public ActionResult Index()
    {            
        ModelClass model = new ModelClass();
        return View(model);
    }

    [HttpPost]
    public ActionResult Index(ModelClass data)
    {
        if (data != null)
        {
            data.ReadData();

            return View(data);
        }
        else
        {
            return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
        }
    }

    [HttpPost]
    public FileContentResult DownloadCSV(ModelClass data)
    {
        if (data != null)
        {
            // generate CSV     
        }
        else
        {
           // ...
        }
    }
}

Basicly my GET-Index generates an empty model with default values (default date and data = null). Then via a form the user changes the date and submits the model to the POST-Index. There the function ReadData is executed, which generates the data within the model from a database-request and displays it in the View. If data is not null in the model the View displays a table with another form, which links to DownloadCSV. I would expect that the same model with my data would be posted to the DownloadCSV-site. But instead i get an empty model, which is not null, but without data. How can i accomplish my desired behavior and why is the described behavior to be expected?

Here is the View:

@using System.Data

@model Project.Models.ModelClass

@{
    ViewBag.Title = "Index";
    Layout = "~/Views/Shared/_Layout.cshtml";
}

<h2>Title</h2>


@using (Html.BeginForm())
{
    @Html.AntiForgeryToken()

    <div class="form-horizontal">
        <h4>SubTitle</h4>
        <hr />
        @Html.ValidationSummary(true, "", new { @class = "text-danger" })

            <div class="form-group">
                @Html.LabelFor(model => model.Date, htmlAttributes: new { @class = "control-label col-md-4" })
                <div class="col-md-8">
                    <div class="input-group date">
                        @Html.EditorFor(model => model.Date, new { htmlAttributes = new { @class = "form-control form-date datecontrol", id = "datecontrol1" } })
                        <label class="input-group-addon btn" for="datecontrol1">
                            <i class="glyphicon glyphicon-calendar"></i>
                        </label>
                    </div>
                    @Html.ValidationMessageFor(model => model.Date, "", new { @class = "text-danger" })
                </div>
            </div>
        </div>

        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <input type="submit" value="Submit" class="btn btn-default" />
            </div>
        </div>

    </div>
}

@if (Model.Data != null)
{
    <hr />

    <div class="pre-scrollable">
        <table class="table table-bordered table-responsive table-hover">
            <tr>
                <th style="white-space: nowrap; width: 1%;">Zeit</th>
                @foreach (DataColumn col in Model.Data.Columns)
                {
                    <th>@col.ColumnName</th>
                }
            </tr>

            @foreach (DataRow row in Model.Data.Rows)
            {
                <tr>
                    @foreach (DataColumn field in Model.Data.Columns)
                    {
                        if (field.ColumnName != "Zeit")
                        {
                            <td>@(row[field.ColumnName] != DBNull.Value ? Convert.ToDouble(row[field.ColumnName]).ToString("F1") : "0")</td>
                        }
                    }
                </tr>
            }
        </table>  
    </div>

    using (Html.BeginForm("DownloadCSV", "Some"))
    {
        @Html.AntiForgeryToken()

        <div class="form-group">
            <div class="col-md-offset-9 col-md-12">
                <input type="submit" value="Export CSV" class="btn btn-default" />
            </div>
        </div>
    }
}


@*<div>
    @Html.ActionLink("Back to List", "Index")
</div>*@

@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
}

And the model:

public class ModelClass
{
    [Required]
    [DataType(DataType.Date)]
    [DisplayFormat(DataFormatString = "{0:dd.MM.yyyy}", ApplyFormatInEditMode = true)]        
    public DateTime Date { get; set; }

    public DataTable Data { get; private set; }

    public ModelClass()
    {
        Date = DateTime.Now;
    }

    public void ReadData()
    {
        Data = new DataTable();

        // ... read data from database into datatable
    }
}

As you can see, Data should only be null if ReadData wasn't executed. So what i get in DownloadCSV is a just created instance of ModelClass.

Update:

I could solve the transfer of the Data field from the POST-Index to DownloadCSV without requesting the data again using TempData following this question: C# MVC pass data from one action method to another

  • Your 2nd form does not contain any form controls - just a submit button, so there is nothing sent to the server when you submit it –  Nov 02 '17 at 09:00
  • And your `if (data != null)` in the `Index()` POST method makes no sense since it will never be `null` –  Nov 02 '17 at 09:02
  • Should i put in a hidden field with the model to post the whole model? – Viktor Verne Nov 02 '17 at 09:03
  • 1
    Sorry to be harsh, but there is some much bad stuff here its hard to believe you could get anything to work. And you have stated _user can download the data as a CSV file_ - where is the code for that (you cannot do it in a POST method)? It appears that the data your displaying is filtered by the `Date` property - is that correct? And why are you making a POST instead of a GET to get the data? –  Nov 02 '17 at 09:12
  • It's correct that i'm new to asp.net, so this is a mockup for many other dialogs that will be embedded in a different web application. The data is generated based on the date (and some other criterias and form fields that i left out for simplicity) from an SQL request to a big database. It shows energy consumption for a given plant based on the date. I left out the code for generating the CSV because it works fine, but i can updated my post with it, if it helps. I'm doing a POST because i don't want the server to request the data again in `DownloadCSV`. – Viktor Verne Nov 02 '17 at 09:21
  • Yes, but you cannot download a file from a POST - it needs to be a GET (although you could generate the file in the POST method. And what do you mean _don't want the server to request the data again_ - are you storing the data in `Session`? –  Nov 02 '17 at 09:28
  • That's sort of what i do, i generate the CSV in `DownloadCSV` based on `Data` from the model as a string `csvText` and do a `return File(new System.Text.UTF8Encoding().GetBytes(csvText), "text/csv", $"EnergyConsumption_{data.Date.ToString("dd.MM.yyyy")}.csv");`. To the session question: No i don't. The idea was that in the POST-Index the data would be requested from the database and stored server-side in memory. Then when the user click the second submit-button to download the CSV, the data would not be requested again from the SQL-Database, instead it would be transfered via a second POST. – Viktor Verne Nov 02 '17 at 09:35
  • That is simply not possible unless you have stored it in `Session`. –  Nov 02 '17 at 09:38
  • That's a good hint. I probably made that mistake because i'm still thinking in desktop application terms. I'll try to solve the problem a different way. For example storing it in the session. Thanks. – Viktor Verne Nov 02 '17 at 09:44

1 Answers1

0

Thanks to Stephen Mueckes comments and this SO question C# MVC pass data from one action method to another i could solve the problem the following way:

Updated Controller:

public class SomeController : Controller
{
    [HttpGet]
    public ActionResult Index()
    {            
        ModelClass model = new ModelClass();
        return View(model);
    }

    [HttpPost]
    public ActionResult Index(ModelClass model)
    {
        model.ReadData();
        TempData['data'] = model.Data

        return View(data);
    }

    [HttpPost]
    public FileContentResult DownloadCSV(ModelClass model)
    {
        // model only has the date populated from the form, so take the data from TempData
        model.Data = TempData['data']

        // ... generate CSV     
    }
}

Updated View:

using (Html.BeginForm("DownloadCSV", "Some"))
{
    @Html.AntiForgeryToken()

    <div class="form-group">
        <div class="col-md-offset-9 col-md-12">
            @Html.HiddenFor(model => model.Date)
            <input type="submit" value="Export CSV" class="btn btn-default" />
        </div>
    </div>
}

This way, i store the results in TempData and don't have to request them again when i export a CSV. TempData will only be stored for the following request.