Reputation: 350
I'm working on my first application in ASP.NET MVC and have run into a snag that I cannot figure out, even after reading the entire internet.
So I have several Views (they are reports) that are created with View Models, which are populated based on user selection criteria. I'm trying to build a method that accepts a Model and exports it in Excel format (using EPPlus), but the Model needs to be sent from one of these Views. This is somewhat of a functional/usability, rather than a technical requirement. A user can run reports, and after viewing the report, can click a button/link and an Excel version of the report they're looking at will be created. I want to pass the Model that the View that's being displayed is based on since the work has already been done to build this specific View Model based on the user's report selection criteria.
This is all fine and good, and I can successfully pass a Model from a View that's displaying a report to the method that builds the report. All of the data in the View Model I pass seems to be there, except a collection of Models (IEnumerable) that is part of it. <--That is where I'm stuck. The collection of IEnumerable has 0 items in it at breakpoint, but this is definitely not the case in when the View Model is created and passed into the View initially.
I'm stuck--please help.
View Model:
public class ReportViewModel
{
public int ProjectId { get; set; }
[Display(Name = "Project Name")]
public string ProjectName { get; set; }
[Display(Name = "Project #")]
public string ProjectNumber { get; set; }
public IEnumerable<SystemModel> SelectedSystems { get; set; }//has items when created and passed into the View intially, but not when the Model is passed from the View to the method/controller.
[Display(Name = "All Systems")]
public bool AllSystems { get; set; }
[Display(Name = "In Progress Systems")]
public bool InProgressSystems { get; set; }
[Display(Name = "Completed Systems")]
public bool CompletedSystems { get; set; }
[Display(Name = "Unchecked Systems")]
public bool UncheckedSystems { get; set; }
[Display(Name = "Systems with Problems - Ours")]
public bool JciIssue { get; set; }
[Display(Name = "Systems with Problems - Other's")]
public bool OtherIssue { get; set; }
[Display(Name = "Include Points with Problems")]
public bool IncludePoints { get; set; }
}
View:
@model ProjectOps_5.ViewModels.ReportViewModel
@{
ViewBag.Title = "Software Cx Status Report";
}
<h2>Software Cx Status Report</h2>
<div>
<div>
<hr />
@Html.ActionLink("Export to Excel", "ExportExcel", Model)//call to method
//the report
Method:
public void ExportExcel(ReportViewModel model)
{
//make Excel file
}
Upvotes: 5
Views: 23642
Reputation: 1063
Passing the whole view model to another method is probably not the best idea since you don't know the size of the model (the number of elements in SelectedSystems
). If it is too large you may exceed the max request length or make the user wait too long until the request completes.
But if you're absolutely sure this is what you need - it can be done using a form:
@using (Html.BeginForm("ExportExcel", null, FormMethod.Post))
{
@Html.Hidden("ProjectId", Model.ProjectId)
@Html.Hidden("ProjectName", Model.ProjectName)
...
@for(int i = 0; i < Model.SelectedSystems.Count(); i++)
{
var systemModel = Model.SelectedSystems.ElementAt(i);
@Html.Hidden("SelectedSystems[" + i + "].Property1", systemModel.Property1)
@Html.Hidden("SelectedSystems[" + i + "].Property2", systemModel.Property2)
}
<input type="submit" value="Export to Excel" />
}
However, a better approach would be to use some identificators of all the objects you want to export (not sure what they might be in your case) and pass those IDs as ActionLink
route values.
Upvotes: 1
Reputation: 475
Can you try below logic , that you have to do before sending the request(or depends on your page, but it should be available before sending the request)
var html = "";
for (index = 0; index < SelectedSystems.length; index++) {
html += '<input type="hidden" name="SelectedSystems[' + index + '].PropertyName" id="SelectedSystems _' + index + '_PropertyName" value="YourValue"/>';
}
$("#divSelectedSystems").html(html); //Here divSelectedSystems is one dummy div with that id
Upvotes: -1
Reputation:
You cannot pass a model that contains properties that are either complex objects or collections to a GET. Internally the ActionLink()
method calls the .ToString()
method of each property so basicaly the DefaultModelBinder
is trying to set the property to as follows
model.SelectedSystems = "System.Collections.Generic.IEnumerable<SystemModel>";
which of course fails and the property is null
.
Fortunately it does not work, or you would generate a really ugly query string and could easily exceed the query string limit and throw an exception.
Instead, pass the ProjectId
property (I'm assuming that uniquely identifies the object) and get the model again in the ExportExcel()
method
@Html.ActionLink("Export to Excel", "ExportExcel", new { ID = Model.ProjectId })
and in the controller
public void ExportExcel(int ID)
{
// Get your ReportViewModel based on the ID and do stuff
}
Upvotes: 8