I am trying to pass data using ViewBag.Unions. I get data at controller, but when i foreach loop in view it says 'object' does not contain a definition for 'CountryName'.I give the full code from controller and view. I can not solve this problem.
Controller
public ActionResult Index()
{
List<Country> listCountry = _db.Countries.ToList();
ViewBag.Countries = listCountry;
ViewBag.Unions = (from unon in _db.Unions
join upz in _db.Upazilas on unon.UpazilaId equals upz.UpazilaId
join dic in _db.Districts on upz.DistrictId equals dic.DistrictId
join div in _db.Divisions on dic.DivisionId equals div.DivisionId
join con in _db.Countries on div.CountryId equals con.CountryId
select new
{
con.CountryName,
div.DivisionName,
dic.DistrictName,
upz.UpazilaName,
unon.UnionName
}).ToList();
return View();
}
View
#{
ViewBag.Title = "Index";
}
<h2>Index</h2>
<table class="">
<thead>
<tr>
<td>Country</td>
<td>Division</td>
<td>District</td>
<td>Upazila</td>
<td>Union</td>
</tr>
</thead>
<tbody>
#if (ViewBag.Unions != null)
{
foreach (var un in ViewBag.Unions)
{
<tr>
<td>#un.CountryName </td>
<td>#un.DivisionName</td>
<td>#un.DistrictName</td>
<td>#un.UpazilaName</td>
<td>#un.UnionName</td>
</tr>
}
}
</tbody>
</table>
Because ViewBag is a dynamic type dictionary. So each of your item in that collection are dynamic type. The compiler skips the type checking when you try to access a property of an dynamic type object, but it might fail in run time (This is exactly one of the reason i am avoiding ViewBag/ViewData as much as possible).
What you should be doing is, create a view model to represent this data and project to that in your LINQ expression
public class MyViewModel
{
public string CountryName { set;get;}
public string DivisionName { set;get;}
public string DistrictName { set;get;}
}
Now since you have a strongly typed class, you do not really need ViewBag to pass the data. You can directly pass the list of MyViewModel objects to the view.
var items = (from unon in _db.Unions
join upz in _db.Upazilas on unon.UpazilaId equals upz.UpazilaId
join dic in _db.Districts on upz.DistrictId equals dic.DistrictId
join div in _db.Divisions on dic.DivisionId equals div.DivisionId
join con in _db.Countries on div.CountryId equals con.CountryId
select new MyViewModel
{
CountryName = con.CountryName,
DivisionName = div.DivisionName,
DistrictName = dic.DistrictName
}).ToList();
return View(items);
Now make sure your view is strongly typed to this collection type
#model List<MyViewModel>
<table class="table>
#foreach(var item in Model)
{
<tr>
<td>#item.CountryName</td>
<td>#item.DivisionName</td>
<td>#item.DistrictnName</td>
</tr>
}
</table>
If you still want to use ViewBag to pass the data (but why ???), you can do that. Instead of passing the list of items to the view method, you can set it to view bag and access it in your razor view. Make sure to cast it to a list MyViewModel before you start looping the collection.
ViewBag.Items = items;
return View();
and in the view
<table class="table>
#foreach(var item in ViewBag.Items as List<MyViewModel>)
{
<tr>
<td>#item.CountryName</td>
<td>#item.DivisionName</td>
<td>#item.DistrictnName</td>
</tr>
}
</table>
Related
I am trying to bind the HTML table using the View Model of type Object on the Razor Page. Code is as below :
index.cshtml.cs
[BindProperty]
public List<object> TableData { get; set; }
public class Cainfo
{
public string Ca_id { get; set; }
public object Nca { get; set; }
}
public async Task<IActionResult> OnGetAsync()
{
List<object> tablerows = GetTableRows(tabledata);
TableData = tablerows;
}
public List<object> GetTableRows(GetTableRowsResponse getTableRowsResponse)
{
List<object> tableRows = new List<object>();
var tables = getTableRowsResponse.rows;
foreach (var table in tables)
{
var tab = JsonConvert.SerializeObject(table);
var row = JsonConvert.DeserializeObject<Cainfo>(tab);
tableRows.Add(row);
}
return tableRows;
}
index.cshtml
<table class="resultTable">
<thead class="grid-header">
<tr>
#foreach (var property in #Model.TableData.GetType().GetGenericArguments()[0].GetProperties())
{
<th class="col-lg-1">#property.Name</th>
}
</tr>
</thead>
<tbody class="grid-body">
#if (#Model.TableData != null)
{
#if ((#Model.TableData.Count != 0))
{
#foreach (var row in Model.TableData)
{
<tr>
#foreach (var property in #row.GetType().GetProperties())
{
<td class="col-lg-1">#property.GetValue(#row)</td>
}
</tr>
}
}
}
</tbody>
</table>
var tables = getTableRowsResponse.rows; return the JSON data. Problem is that table <th> is not getting bind. #Model.TableData.GetType().GetGenericArguments()[0].GetProperties() is getting empty. <td> is getting bind as expected. Maybe I am doing a mistake somewhere, I am new to the asp .net core. Now, I am using the Model Cainfo but in future, I need to set different models according to data and bind the same table. That's why I am giving View Model type as Object. Please help.
I would not use reflection like this when you can design common models for the view. That's the art of designing which makes things easier. However here assume that you want to stick with that solution anyway, I'll show where it's wrong and how to fix it and how it's limited.
First this Model.TableData.GetType().GetGenericArguments()[0] will return Type of object. And that Type of course contains no properties. That's why you get no <th> rendered. The generic argument type exactly reflects what's declared for Model.TableData which is a List<object>.
Now to fix it, assume that all the items in the List<object> are of the same type, you can get the first item's Type, like this:
#foreach (var property in #Model.TableData?.FirstOrDefault()
?.GetType()?.GetProperties()
?? Enumerable.Empty<PropertyInfo>())
{
<th class="col-lg-1">#property.Name</th>
}
That has a limit in case the Model.TableData contains no item. No <th> will be rendered. If that's acceptable (instead of rendering an empty table with headers, you will render nothing or just some message) then just go that way. Otherwise, you need to provide a Type for the element/row's Typevia your model, such as via a property like Model.RowType. Then you can use that instead of this:
Model.TableData?.FirstOrDefault()?.GetType()
The remaining code is just the same.
I am getting the following error.
The model item passed into the dictionary is of type 'System.Collections.Generic.List`1[MYAPP.Models.User]', but this
dictionary requires a model item of type
'System.Collections.Generic.IEnumerable`1[MYAPP.Models.ViewModel.AccountViewModel]'.
My code is as follows:
VIEWMODEL
public class AccountViewModel
{
...
public IQueryable<User> user { get; set; }
}
CONTROLLER
public class UserController : Controller
{
private REc db = new REc();
// GET: /User/
public ActionResult Index()
{
AccountViewModel avm = new AccountViewModel();
var users = db.Users.Include(u => u.USERROLE);
avm.user = users;
return View((avm.user).ToList());
}
VIEW
#model IEnumerable<SNAPAPP.Models.ViewModel.AccountViewModel>
#{
ViewBag.Title = "Index";
}
<table class="table">
<tr>
<th>
#Html.DisplayNameFor(model => model.USERROLE)
</th>
<th></th>
</tr>
#foreach (var item in Model) {
<tr>
<td>
#Html.DisplayFor(modelItem => item.USERROLE)
</td>
<td>
#Html.ActionLink("Edit", "Edit", new { id=item.ID }) |
#Html.ActionLink("Details", "Details", new { id=item.ID }) |
#Html.ActionLink("Delete", "Delete", new { id=item.ID })
</td>
</tr>
}
</table>
Your razor view is strongly typed to a collection of AccountViewModel class objects. But your current action method code is sending a list of User entity (the entity class which is totally different than your view model class). That is the reason you are getting this type mismatch error.
You should send the correct type from your action method. You may use projection on your linq statement to get a collection of AccountViewModel from the db.Users collection.
var accountViewModelList = db.Users
.Select(x=> new AccountViewModel { Id=x.Id })
.ToList();
return View(accountViewModelList);
The above code will basically create a list of AccountViewModel objects and read each User entity object's Id property value and assign it to the corresponding view model object's Id property. If you want more properties in your view, you should map those as well.
For example, if you have a FirstName property in both your entity class and view model, you may do this,
var accountViewModelList = db.Users
.Select(x=> new AccountViewModel {
Id=x.Id,
FirstName = x.FirstName })
.ToList();
in your controller, use this to return the model to the view
return View(avm.user.AsEnumerable);
your db query returns a IQueryable,
you did put the ToList on the whole model, not on the user field
I have a news item page and I would like to include a partial view presenting a list of the latest 5 news items. However, I get a
this dictionary requires a model item of type
'System.Collections.Generic.List
error message. I'm assuming the main view is strong typed to present a single item, whereas the partial view is strongly typed with a generic list and the two don't match.
Controller
public ActionResult NewsItem(int newsId, string newsTitle)
{
var q = _ctx.tblNews.Single(x => x.newsID == newsId);
return View(q);
}
public ActionResult NewsLatest()
{
var q = _ctx.tblNews.OrderBy(x => x.newsCreateDate)
.Where(x => x.WebsiteID == 2 && x.newsPublish).Take(5).ToList();
return View(q);
}
View (simplified)
#using MyMVC.Models
#model tblNews
<h2>#Model.newsTitle</h2>
#Html.Raw(Model.newsText)
<p>#Model.newsCreateDate</p>
#Html.RenderPartial("NewsLatest")
Partial View
#using MyMVC.Helpers
#model List<MyMVC.Models.tblNews>
<table>
#foreach (var x in Model)
{
<tr>
<td>
#Html.ActionLink(x.newsTitle, "NewsItem", new { newsId = x.newsID, newsTitle = x.newsTitle.ToSeoUrl() })<br />
<hr />
</td>
</tr>
}
</table>
I tried this in the view:
#{
Html.RenderPartial("NewsLatest", new List<tblNews> { new tblNews()});
}
but it looks like the ActionResult doesn't get fired trying i this way.
There are a few questions on SO relating to this. But I just can't get my head around the solutions. The penny isn't dropping!
Do I really have to create a view model that incorporates the news item data and the list of news items?
I'm working on my first complex MVC project. I have an existing database that I used Entity Framework 4.0 to model. The project is basically a survey tool. There are 8 tables in my viewmodel each with a few properties needed in my main survey view. Those are basically questionaire, section, question, and possible answers (to be in the form of dropdownlists) plus the intermediate connecting tables.
public class MyQuestionModel
{
public Questionaire Questionaire { get; set; }
public QuestionaireSection QuestionaireSection { get; set; }
public Section Section { get; set; }
public SectionQuestion SectionQuestion { get; set; }
public Question Question { get; set; }
public QuestionType QuestionType { get; set; }
public QuestionAnswerListCode QuestionAnswerListCode { get; set; }
public AnswerListCode AnswerListCode { get; set; }
}
My ViewMode MyQuestionModel is loaded like this:
public ActionResult Index()
{
var viewModel =
from qa in db.Questionaires
join qas in db.QuestionaireSections on qa.QuestionaireKey equals qas.QuestionaireKey
join s in db.Sections on qas.SectionKey equals s.SectionKey
join sq in db.SectionQuestions on s.SectionKey equals sq.SectionKey
join q in db.Questions on sq.QuestionKey equals q.QuestionKey
join qtc in db.QuestionTypes on q.QuestionTypeKey equals qtc.QuestionTypeKey
join qddl in db.QuestionAnswerListCodes on q.QuestionKey equals qddl.QuestionKey
join ddl in db.AnswerListCodes on qddl.AnswerListCodeKey equals ddl.AnswerListCodeKey
where qa.QuestionaireName.Equals("TAD")
select new MyQuestionModel
{
Questionaire = qa,
QuestionaireSection = qas,
Section = s,
SectionQuestion = sq,
Question = q,
QuestionType = qtc,
QuestionAnswerListCode = qddl,
AnswerListCode = ddl
};
return View(viewModel);
//var viewModel = new List<MyQuestionModel>();
//return View(viewModel);
}
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Index(IEnumerable<MyQuestionModel> model)
{
if (ModelState.IsValid )
{
// PROCESS THE POSTED DATA HERE
return RedirectToAction("Index", "MyQuestion");
}
// If we got this far, something failed, redisplay form
ModelState.AddModelError("", "error message");
return View(model);
}
When constructing the view by iterating through the results using nested foreach and GroupBy and OrderBy Linq statements doesn't leave me with a model with results I can post back to the controller. I tried using for loops instead of foreach but the grouping issues are causing me problems. If I try loading the ViewModel discretely with out grouping and with just the distinct data for each table I end up with problems getting the correct types for each table and the composite ViewModel. I would guess one of these 3 ways is workable but I'm not sure which way to hang my hat and grind through getting it to work. I'm getting what I need (See Image) in the view using the nested foreach loops but I think I am breaking the model in the process as when I look at the posted model in the controller it is null. Maybe my foreach statements are not constructed properly. I can't help but think there is a more elagent way of doing this. Ultimately I think using editor templates of other partial views may be best but I need to get a prototype working I can refine later.
#model IEnumerable<eValuate_Prototype_07.Models.MyQuestionModel>
#{
ViewBag.Title = "Index";
Layout = "~/Views/Shared/_Layout.cshtml";
}
#using (Html.BeginForm()) {
#Html.AntiForgeryToken()
<table>
#foreach (var group in (Model.OrderBy(x => x.QuestionaireSection.DefaultSequence).GroupBy(item => item.Section.SectionName)))
{
<tr>
<th colspan="3">#group.Key</th>
</tr>
foreach (var item in group.OrderBy(x => x.SectionQuestion.DefaultSequence).GroupBy(subItem => subItem.Question.Question1).Distinct()) {
<tr>
<td> </td>
<td>#item.Key</td>
<td>
<select id="ddlAnswerListCode">
<option value="#Guid.Empty"></option>
#foreach (var ans in item.OrderBy(x => x.QuestionAnswerListCode.DefaultSequence))
{
<option value="#ans.AnswerListCode.AnswerListCodeKey">#ans.AnswerListCode.AnswerListCodeName</option>
}
</select>
</td>
</tr>
}
}
</table>
<p>
<input type="submit" value="Save" />
</p>
}
My Questions:
Which is preferred foreach, for loop, or loading the view model discretely then use foreach?
Is the way I'm using foreach breaking my model for posting?
I have 2 controllers that generate 2 index views.
What i would like to do is use these views as global shared partial views but cant seem to get this working.
Does anyone know if this is even possible?
My controller code is
public ActionResult Index()
{
var viewModel = (from P in db.Projects
join R in db.Reports on P.ProjectTitle equals R.ReportProjectID into ps
from R in ps.DefaultIfEmpty()
select new MyViewModel { Project = P, Report = R });
return View(viewModel);
}
My ViewModel code is
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace MiLife2.ViewModels
{
public class MyViewModel
{
public Project Project { get; set; }
public Report Report { get; set; }
}
}
and my view is
#model IQueryable<MiLife2.ViewModels.MyViewModel>
#{
ViewBag.Title = "Index";
}
enter code here
<h2>Index</h2>
<div>#Html.Partial("_Partial1")</div>
<p>
#Html.ActionLink("Create New", "Create")
</p>
<table>
<tr>
<th></th>
</tr>
#foreach (var item in Model)
{
<tr>
<td>#item.Project.ProjectTitle </td>
<td>#item.Project.ProjectCreatedByID</td>
<td>#item.Project.ProjectCreatedDate</td>
<td>#if (item.Report == null)
{
<text>No Reports</text>
}
else
{
#item.Report.Title;
}
</td>
<td>#if (item.Report == null)
{
<text> </text>
}
else
{
#item.Report.Description;
}</td>
<td>
#Html.ActionLink("Edit", "Edit", new { id=item.Project.ProjectID }) |
#Html.ActionLink("Details", "Details", new { id=item.Project.ProjectID }) |
#Html.ActionLink("Delete", "Delete", new { id=item.Project.ProjectID })
</td>
</tr>
}
</table>
If i create a partial page and paste the above view into it and then use #HTML.Partial("_ProjPartial") i get the error
The model item passed into the dictionary is of type 'System.Collections.Generic.List1[MiLife2.Project]', but this dictionary requires a model item of type 'System.Linq.IQueryable1[MiLife2.ViewModels.MyViewModel]'.
This does not happen if i use #HTML.Partial("_ProjPartial") from within the Index cshtml page in the specific controller views folder.
From the error it looks like to me that your partial view is looking for the same model as you have on your view. Passing the model to your partial should fix that error
#Html.Partial("_Partial1", Model)
update:
since that didn't work for you I would try using an ajax call
$('.btnSubmit').on('click', function(){
$.ajax({
url: "#(Url.Action("Action", "Controller"))",
type: "POST",
cache: false,
async: true,
data: { id: id },
success: function (result) {
$(".Content").html(result);
}
});
});
then in your controller
public PartialViewResult GetPartial()
{
var viewModel = (from P in db.Projects
join R in db.Reports on P.ProjectTitle equals R.ReportProjectID into ps
from R in ps.DefaultIfEmpty()
select new MyViewModel { Project = P, Report = R });
return PartialView("_Partial1", viewModel);
}
Using this ajax call you can call the partial view from any view and you can pass different id's, on button clicks or as needed to refresh the view. Hopefully calling it this way will fix your error. let me know if you have any questions.
Recently ran into something similar, so I wanted to add my 2 cents. The answer for me was in what I was passing to the Partial View.
I was attempting to pass a string to a partial view, but when that string happened to be null, it was acting as if I had not passed anything into the Partial, which means it defaulted to passing the current view's model.
For example, I have a view which renders a partial and that partial takes in a string:
#model SomeModel
#{ Html.RenderPartial("_MyPartialView", SomeModel.StringProperty) }
If SomeModel.StringProperty happens to be null, then it is going to try and pass what ever the current view's model is (which, in this case is SomeModel). So instead, I simply wrote the following which will pass in an empty string if SomeModel.StringProperty happens to be null:
#model SomeModel
#{ Html.RenderPartial("_MyPartialView", SomeModel.StringProperty ?? string.Empty) }
Hope this helps someone.