List<T> passed to controller sends empty items - c#

A PartialView contains a model Foo with a List<Bar>. Each Bar item contains two properties, BarA (string) and BarB (decimal).
I am trying to render a Chart in that partial view, but for that to work I need to call an action on the same Controller and pass the result to an <img /> element. To render the chart, I need the collections of BarA and BarB to format the data.
So I'm trying with something like this:
Controller
public void GenerateChart(List<Bar> model)
{
var chart = new System.Web.Helpers.Chart(400, 200)
.AddTitle("Title")
.AddSeries(
name : "name",
xValue : model.Select(m => m.BarA).ToArray(),
yValues : model.Select(m => m.BarB).ToArray())
.Write();
}
Partial View
RouteValueDictionary rvd = new RouteValueDictionary();
for (int i = 0; i < Model.Bars.Count; ++i)
{ rvd.Add("model[" + i + "]", Model.Bars[i]); }
<img src="#Url.Action("GenerateChart", rvd)" />
The problem with this is that even though the model object contains the three items it should contain, these are null.
I also tried to use the ViewBag, like this:
ViewBag.BarA = Model.Bars.Select(m => m.BarA).ToArray();
ViewBag.BarB = Model.Bars.Select(m => m.BarB).ToArray();
With this on the controller side
public void GenerateChart()
{
var chart = new System.Web.Helpers.Chart(400, 200)
.AddTitle("Title")
.AddSeries(
name : "name",
xValue : ViewBag.BarA,
yValues : ViewBag.BarB)
.Write();
}
But both arrays are null. I've also tried a few different ideas but I'm not able to get the information I need.
To triple-check (the data is shown fine in the view) that the data is correct, I changed to this:
#{
string[] barAs = Model.Select(m => m.BarA).ToArray();
decimal[] barBs = Model.Select(m => m.BarB).ToArray();
ViewBag.BarAs = barAs; // this has 3 items with the expected data
ViewBag.BarBs = barBs; // this also works
}
<img src="#Url.Action("GenerateChart")" />
string[] BarAs = ViewBag.BarAs; // this assigns null
decimal[] BarBs = ViewBag.BarBs; // this also assigns null

It seems you don't really understand how MVC works. I encourage you to spend some time going through all the tutorials at http://asp.net/mvc to familiarize yourself with the framework. Namely, it seems you're trying to approach a lot of this as if you were in the world of Web Forms. MVC is an entirely different beast.
First, Html.Action cannot return a full image, because all it's going to do is just dump the return value to the HTML being generated, and you can't embed a binary object directly in HTML.
Second, even if you could, you can't use that as the src for an img tag. The src must be a string, namely a URL, point to a location of an image.
So, in order to achieve this. You will need a full action that returns a proper response as an image. Then, you can simply link your image src to the route that hits this action.
public ActionResult GenerateChart(List<Bar> model)
{
var chart = new System.Web.Helpers.Chart(400, 200)
.AddTitle("Title")
.AddSeries(
name : "name",
xValue : model.Select(m => m.BarA).ToArray(),
yValues : model.Select(m => m.BarB).ToArray())
.GetBytes("jpeg");
return File(chart, "image/jpeg");
}
Then,
<img src="#Url.Action("GenerateChart", new { model = rvd })" alt="" />
Now, you're just link to a URL. That URL maps to a route that hits your GenerateChart action, which then returns an actual image - same as if you directly linked to a physical image. Now, the browser can properly render the img tag to the page.

Passing a complex type to actions via GET request is technically bloody thing so I do not know if this solution fits your needs you can follow up the method below;
Your action will recieve model as serialized string and you have to Deserialize it to your model
public ActionResult GenerateChart(string modelAsString)
{
List<Bar> model = new List<Bar>();
model = JsonConvert.DeserializeObject<List<Bar>>(modelAsString);
var chart = new System.Web.Helpers.Chart(400, 200)
.AddTitle("Title")
.AddSeries(
name: "name",
xValue: model.Select(m => m.BarA).ToArray(),
yValues: model.Select(m => m.BarB).ToArray())
.GetBytes("jpeg");
return File(chart, "image/jpeg");
}
Then you need to call your action via querystring like ?modelAsString={jsonData}
The example URL I use to process data : http://localhost:18681/Home/GenerateChart/?modelAsString=[{%22BarA%22:%22barAData%22,%22BarB%22:1.1},{%22BarA%22:%22barAData2%22,%22BarB%22:441.14},{%22BarA%22:%22barAData43%22,%22BarB%22:44.1}]
You should create your <img> URLs via serializing your model which ready on the page's action which you use <img>s.
I have tested a dummy data you can see output below;
PS: for creating dummy data I used the method below;
public ActionResult DummyData()
{
List<Bar> model = new List<Bar>();
model.Add(new Bar() { BarA = "barAData", BarB = 1.1m });
model.Add(new Bar() { BarA = "barAData2", BarB = 441.14m });
model.Add(new Bar() { BarA = "barAData43", BarB = 44.1m });
return Json(model, JsonRequestBehavior.AllowGet);
}
And I wonder too if any more efficient way to do this via get request.
Hope this helps!

Related

Reloading Datatable from sDom format in MVC

we have a project (SmartAdmin template to be specific) where we are trying to reload the data in the partial view for the table on certain actions by users. I just can't quite figure out what to do with this with the setup we started with.
Datatable initialization code -
function setupInProgressTable(tabletSize, phoneSize) {
/* Data Tables */
var responsiveHelper_in_progress = undefined;
var breakpointDefinition = {
tablet: Number(tabletSize),
phone: Number(phoneSize)
};
/* In Progress */
$('#in_progress').dataTable({
"sDom": "<'dt-toolbar'<'col-xs-12 col-sm-6'f><'col-sm-6 col-xs-6 hidden-xs'C>r>" +
"t" +
"<'dt-toolbar-footer'<'col-sm-6 col-xs-12 hidden-xs'i><'col-xs-12 col-sm-6'p>>",
"autoWidth": true,
"preDrawCallback": function () {
// Initialize the responsive datatables helper once.
if (!responsiveHelper_in_progress) {
responsiveHelper_in_progress = new ResponsiveDatatablesHelper($('#in_progress'), breakpointDefinition);
}
},
"rowCallback": function (nRow) {
responsiveHelper_in_progress.createExpandIcon(nRow);
},
"drawCallback": function (oSettings) {
responsiveHelper_in_progress.respond();
},
"order": [[2, "asc"]]
});
}
MVC Controller action that builds up data and sends it to the partial
// GET: Tier2/InProgressTable
/// <summary>
/// Gets data to supply to the In Progress Table on draw
/// </summary>
/// <returns>ActionResult - _InProgressTable Partial View</returns>
[Authorize(Roles = "Tier2.Issues.Edit.All")]
public ActionResult InProgressTable()
{
var results = _api.Tier2.Issues.GetTier2Issue(resolved: false);
List<Tier2IssuesViewModel> viewModel = new List<Tier2IssuesViewModel>();
if (results.message == null)
{
// Get the corresponding issues for this table
var statuses = new int[] { 2, 4 };
var issues = results.data.Where(i => statuses.Contains(int.Parse(i.IssueStatus.id.ToString())));
// Set items for the view model
foreach (var item in issues)
{
var theIssueStatusList = GetIssueStatusList(); // Build up data for IssueStatusList
Tier2IssuesViewModel theModel = new Tier2IssuesViewModel();
theModel.Issue = item;
theModel.IssueStatusList = theIssueStatusList;
if (theModel.Issue.IssueStatus != null)
theModel.IssueStatusList.Where(m => m.Value == theModel.Issue.IssueStatus.id.ToString()).First().Selected = true;
viewModel.Add(theModel);
}
return PartialView("_InProgressTable", viewModel);
}
else
{
ModelState.AddModelError("", results.message);
}
return PartialView("");
}
Any ideas on where I should head with this?
In a nutshell, just change the way you populate the datatable. Instead of passing the data in a ViewModel, you can use the datatables API to populate it using Ajax.
A simple example:
The Controller method can be simplified as it's not handling any data:
public ActionResult InProgressTable()
{
return View();
}
Your View just needs an empty table:
<table id="in_progress">
<thead>
<tr>
<th>Id</th>
<th>Name</th>
</tr>
</thead>
<tbody></tbody>
</table>
You need a new Controller method that returns the data as json:
public ActionResult PopulateInProgressTable()
{
var results = _api.Tier2.Issues.GetTier2Issue;
// read into object array
var result = from r in results
select new object[]
{
r.Id,
r.Title
};
// Get the sEcho param
var _sEcho = request.QueryString["sEcho"];
// return data to datatable
return Json(new
{
_sEcho,
iTotalRecords = result.Count(),
iTotalDisplayRecords = result.Count(),
aaData = result
}, JsonRequestBehavior.AllowGet);
}
A few things to note here - datatables expects the json data in a specific format, so create a json object with the properties named as in the above example. sEcho is a parameter send in the request, you are simply returning it unaltered. iTotalRecords and iTotalDisplayRecords are used for paging, aaData is the actual data.
Finally you initialise the datatable in your javascript:
var oTable = $('#in_progress').DataTable({
'serverSide': true,
"ajax": {
"url": [your url] + '/PopulateInProgressTable'
},
'processing': true,
'columns': [
{
'data': 0
},
{
'data': 1
}
});
This needs to run after the partial view is created otherwise the table won't exist when the datatables initialisation code runs.
As mentioned earlier, this is a very simple example but it should be enough to get you started.
You need to set up the initial configuration in your partial view code. The data will need to be applied in a separate endpoint call.
To define the initial UI
return PartialView();
To return Data to your grid
return Json(Something.GetData(),JsonBehavior.AllowGet);
To setup the initial state of your PartialView and initially bind data, you should tie into the page load event and this is usually done in the jquery.load() function defined in your PartialView.
When your partial loads, make sure that you call a controller method to return data to your grid. You may also want to add a parameter to your grid data function to indicate user's intent? However, you can load and reload your grid by binding your grid to the result of a client side ajax function that returns the json payload.
success(data) { bindGrid(data); }

How can I pass the results returned by a method from controller to the view

I want to use the results returned by a method (cursor in my case) from the controller in my view,
in order to draw a graph from the data returned.
I wrote a piece of code but I have not found how to pass data to the view.
//controller
[HttpPost]
public async System.Threading.Tasks.Task<ActionResult> drawgraph(Inputmodel m)
{
List<Client> client = new List<Client>();
var collection = db.GetCollection<Client>("Client");
var builder = Builders<Client>.Filter;
var beginDate = Convert.ToDateTime(m.date_begin).Date;
var endDate = Convert.ToDateTime(m.date_end).Date;
var filter = builder.Gte("date", beginDate) & builder.Lt("date", endDate.AddDays(1)) & builder.Eq("field2", m.taux);
var cursor = await collection.DistinctAsync<double>("field2",filter);
return View(cursor);
}
//view
#{
var myChart = new Chart(width:600,height: 400)
.AddTitle("graphique")
.AddSeries(chartType: "Line")
.DataBindTable(dataSource: cursor, xField: "date", yField:"field2") //here I want to use the returnet result by drawgraph in the controller
.Write();
}
You must strongly type your view to access the model :
#model YourModelTypeHere #*type of the cursor variable*#
//view
#{
var myChart = new Chart(width:600,height: 400)
.AddTitle("graphique")
.AddSeries(chartType: "Line")
.DataBindTable(dataSource: Model, xField: "date", yField:"field2") //here I want to use the returnet result by drawgraph in the controller
.Write();
}
And then you can use Model property of the web view page to get your model.
Please see this article.
You could use
in the controller
ViewBag.MyData = cursor;
//in the view:
#ViewBag.MyData
To acceess data from controller.
But the best practice is use strongly typed views.

How to update a View without Postback in MVC3

How can I update a dropdownlist in MVC3. I want to refill it with latest data filled by some other view, but I do not want to postback the view and want to achieve it with jquery.
I have a dropdownlist like:
#Html.DropDownListFor(m => m.Department, Model.Departments)
#Html.ValidationMessageFor(m => m.Departments)
<input type="button" value="Refresh" id="btnrefresh" />
I have written jquery code to call controller's method:
$("#btnrefresh").click(function () {
var ref = '#Url.Action("RefreshDepartments")';
var model = '#Model.ToJson()';
var data = { empModel: model };
$.getJSON(ref, data, function (result) { alert(result.message); });
return false;
});
And Here is the controller method:
public ActionResult RefreshDepartments(EmployeeModel empModel)
{
empModel.Departments = GetDepartments();
empModel.Roles = GetRoles();
return Json(new { message = "Updated successfully"}, JsonRequestBehavior.AllowGet);
}
How can I update the dropdownlist with latest values on clicking "Refresh" button without any postback?
Is it a good idea to pass the model to the controller and update the model properties? What other ways are possible ?
It doesn't look to me like you need the model to be posted to your controller for what you're doing. In addition, yes, you absolutely can do this with jquery! On a side note, you could also do it with an Ajax.BeginForm() helper method, but lets deal with your jquery example.
Rather than complexify your jquery with your #Url.Action, you can simply call the path itself.
$("#btnrefresh").click(function () {
var ref = 'ControllerName/RefreshDepartments';
$.each(result, function (index, val) {
$('#whateverYourRenderedDropdownListHtmlObjectis')
.append($("<option></option>")
.attr("value", val.Text)
.text(val.Text));
});
});
Now, for your controller...
public JsonResult RefreshDepartments()
{
return Json(GetDepartments, JsonRequestBehavior.AllowGet);
}
private SelectList GetDepartments
{
var deparments = GetDepartments;
SelectList list = new SelectList(departments);
return list;
}
This is an alternative to returning the model. It allows you to manipulate the raw JSON instead. Hope it helps!
You almost did it all! Why don't you send the data, I mean list, by RefreshDepartments action? You sent a message to view, so you can send the list similarly and instead of alerting the result you can fill the dropdownlist. something like this:
public ActionResult RefreshDepartments(EmployeeModel empModel)
{
return Json(new { departments = GetDepartments()}, JsonRequestBehavior.AllowGet);
}
$.getJSON(ref, data, function (result) {
$("#Department").html("");
for (var i = 0; i < result.departments.length; i++) {
var item = result.departments[i];
$("#Department").append(
$("<option></option>").val(item.Id).html(item.Name);
);
});
});

How to save query values when searching? ASP.NET MVC

I have such an action method:
[AcceptVerbs(HttpVerbs.Get)]
public ActionResult Search(String filter, String value, Int32? page) {
var set = new List<Employee>();
switch(filter) {
case "by-name": {
set = this.repository.Get(
e => (e.LastName + " " + e.FirstName + " " + e.MiddleName) == value
).ToList();
break;
}
case "by-empn": {
set = this.repository.Get(
e => e.EmployeeNumber == value
).ToList();
break;
}
default: return RedirectToAction("Search", "Employee");
}
ViewBag.SearchedEmployees = set.Count();
return View(set.ToPagedList(page ?? 1, PageSize));
}
Search view is like this:
#if(Model.Count > 0) {
foreach(var item in Model) {
Html.RenderPartial("Employee.Card", item);
}
#Html.PagedListPager(
Model,
page => Url.Action("Search", new { page = page }),
new PagedListRenderOptions {
LinkToFirstPageFormat = "<< Beginning",
LinkToPreviousPageFormat = "< Back",
LinkToNextPageFormat = "Forth >",
LinkToLastPageFormat = "End >>"
}
)
}
Search form is presented as a partial view:
#using(Html.BeginForm("Search", "Employee", FormMethod.Get, new { #class = "search-form" }))
{
<p>
#Html.TextBox("value")
</p>
<p>
#Html.RadioButton("filter", "by-name", true) By name <br/>
#Html.RadioButton("filter", "by-empn") By empn <br/>
</p>
<p>
<input type="image" src="#Url.Content("~/Content/Images/Search.png")" />
</p>
}
Problem: I have N page links. When I try to go to the second page I face an infinite loop of redirects. That's the way I implemented my action - default case is fired. So filter/value values are null on the second action call? Why?
How do I refactor my search action?
Also how should I configure route for such an action?
Thanks!
EDIT
So should route for the search action look like:
routes.MapRoute(
null,
"{controller}/{action}/Page{page}/filter{filter}/val{value}",
new { controller = "Employee", action = "Search" }
);
?
EDIT 2
So it is possible to write next:
page => Url.Action("Search", new { filter = ViewBag.SearchFilter, value = ViewBag.SearchValue, page = page }),
And inside a controller:
public ActionResult Search(String filter, String value, Int32? page) {
ViewBag.SearchFilter = filter;
ViewBag.SearchValue = value;
// ...
}
Is this right?
So filter/value values are null on the second action call? Why?
Because their corresponding input fields are inside a separate form and are never sent to the server.
You seem to be using some custom Html.PagedListPager helper (the code for which you haven't shown) but I guess that this helper generates the page links as anchors and it simply doesn't take into account any current query string or POSTed values when generating those links. So the href of your pagination link looks like this /SomeController/Search?page=5 instead of the correct one which would take into account those parameters which is /SomeController/Search?page=5&filter=somefilter&value=somevalue.
You can now easily understand why the filter and value parameters in your controller action are always null. It's because you never send them to the server when clicking on the pagination links.
So in order to resolve this issue you could modify this custom HTML helper that you are using to generate the pagination links to include those additional parameters. Or maybe the helper allows you to pass additional parameters? Check the documentation if this is some third party plugin that you are using.

DataBinding a DropDownList in ASP.NET MVC 2

I'm extremely frustrated trying to switch to MVC after a couple years of webforms development.
Here's my extremely simple problem that I can't manage to solve:
I have a list of States in a table called StateProvince.
I have a DropDownList.
I want the DropDownList to display all of the States.
Keep it simple, I know nothing about MVC.
Here's what I have. All this gives me is a DropDownList filled with "System.Web.Mvc.SelectListItem".
Action:
public ActionResult Create()
{
var dbTimecard = new TimecardDbDataContext();
IEnumerable<SelectListItem> stateProvinces = dbTimecard.StateProvinces.Select(p => new SelectListItem
{
Value = p.StateProvinceId.ToString(),
Text = p.Name
});
SelectList theList = new SelectList(stateProvinces);
ViewData["StateProvince"] = theList;
return View();
}
View:
<div class="editor-label">
<%: Html.LabelFor(model => model.StateProvinceId) %>
</div>
<div class="editor-field">
<%: Html.DropDownListFor(model => model.StateProvinceId, (SelectList)ViewData["StateProvince"])%>
<%: Html.ValidationMessageFor(model => model.StateProvinceId) %>
</div>
Here is what I was looking for:
Action:
public ActionResult Create()
{
var dbTimecard = new TimecardDbDataContext();
IEnumerable<SelectListItem> stateProvinces = dbTimecard.StateProvinces.Select(p => new SelectListItem
{
Value = p.StateProvinceId.ToString(),
Text = p.Name
});
ViewData["StateProvince"] = stateProvinces;
return View();
}
View:
<div class="editor-field">
<%: Html.DropDownListFor(model => model.StateProvinceId, (IEnumerable<SelectListItem>)ViewData["StateProvince"])%>
<%: Html.ValidationMessageFor(model => model.StateProvinceId) %>
</div>
Try replacing this
SelectList theList = new SelectList(stateProvinces);
with this...
SelectList theList = new SelectList(stateProvinces, "Value", "Text");
Common browsers don't support PUT verb within HTML forms. You might need to handle this using ajax:
$(function() {
// when some button is clicked:
$('#someButton').click(function() {
$.ajax({
url: '/controller/action',
data: { selectedState: $('#StateProvinceId').val() },
type: 'PUT',
success: function(data) {
alert('state successfully submitted');
}
});
return false;
});
});
When the data is posted, the items listed in the DropDown are not posted back to the model so I am assuming you are not fetching them again and re-adding them to your model before returning your model back to the view.
You need to make sure on your post that you are filling Model.StateProvinces and then passing it to your View. Only the values are persisted unlike WebForms which would maintain the DropDownList items in the ViewState and rebuild them for you.
So assuming your controller looks something like:
// POST: /YourController/YourAction/{ID}
[HttpPost]
public ActionResult YourAction(YourModel model, int id)
{
// model.StateProvinceId has the selected value
// you need to fill StateProvinces since it is empty as the list is not posted
// back so it is not autofilled into your model.
model.StateProvinces = LoadYourStateProvincesList();
// This would blow up if StateProvinces is null because your View is assuming that
// it has a valid list of StateProvinces in it to build the DropDown
return View(model);
}
I actually asked a question a while back that might be of some use to help explain how to handle DropDown lists:
Best way of implementing DropDownList in ASP.NET MVC 2?
EDIT: Based on your comment it you are not even getting the first list up... well it looks like you might not have your model setup correctly. Do you even have a model? Your create is just returning View() with no model data in it. You need to create a Model and return it.
Eg.
public class YourModel
{
public int StateProvinceId { get; set; }
/// ... whatever else you need
}
// Then in your view your controller you need to set it and create it
View(new YourModel() { StateProvinceId = 123 };);
You need the model so that when the form IS posted back you can retrieve the value because MVC will stuff the value into your model like th example I posted above regarding the POST part.
EDIT: Ok now that question is clearer and a lot simpler than the original question was making the problem out to be. The problem is your SelectList needs tell it which fields to put where:
SelectList theList = new SelectList(stateProvinces, "Value", "Text");
In your original question you had:
<%: Html.DropDownListFor(model => model.StateProvinceId, new SelectList(Model.StateProvinces, "StateProvinceId", "Name") )%>
Which was totally correct but it was how you were building up Model.StateProvinces that was wrong so it threw me off thinking it had to be something else.

Categories