Get a list of checked models in controller [duplicate] - c#

This question already has answers here:
Post an HTML Table to ADO.NET DataTable
(2 answers)
Closed 6 years ago.
Controller:
[HttpPost]
public ActionResult GetChecked(FormCollection formCollection)
{
var checked = formCollection["Checked"].Split(',');
var ids = formCollection["Original.ID"].Split(',');
}
View:
#model IEnumerable<Models.Entry> []
<table>
#using (Html.BeginForm("GetChecked", "ControllerName"))
{
#foreach (var item in Model[0])
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.Original.ID)
#Html.HiddenFor(modelItem => item.Original.ID)
</td>
<td>
#Html.DisplayFor(modelItem => item.Original.Name)
</td>
<td>
#Html.DisplayFor(modelItem => item.New.Name)
</td>
<td>
#Html.CheckBoxFor(modelItem => item.Checked)
</td>
</tr>
}
</table>
//Then there's another table with Model[1]
Model
public class Entry
{
public Entry()
{
Original = new SomeObject();
New = new SomeObject();
Checked = false;
}
public SomeObject Original { get; set; }
public SomeObject New { get; set; }
public bool Checked { get; set; }
}
This works but the ids-array in my controller gets both a true- and a false value for the checked rows. I read that it's because of the FormCollection.
Question: How can I let GetChecked take an IEnumerable<Models.Entry> as a parameter instead? When I tried it it resulted in a null value.

There are a couple of things that you should change:
When rendering controls from a list or array using CheckBoxFor, EditorFor, etc. you should never use foreach - instead, ALWAYS use a for-loop and apply indexing to your collection. The reason is that indexing creates numbered items in your <form> that then no longer conflict with each other, and those numbered items are precisely what you need to successfully process a list of submitted values. See this answer for a simple example: https://stackoverflow.com/a/15375949/1220550
Don't use FormCollection, use a ViewModel class instead. By using FormCollection you are giving up on Databinding / ModelState / ValidationSummary which together are a superb feature of ASP.NET MVC. It is too much to explain it all here, but here is a great link that does exactly that.
It is best to use a fully defined ViewModel class, not only for databinding (see before) but also for consistency and ease-of-use. Having an array of IEnumerable<X> as your #model is at best confusing, and at worst a cause for errors. And what if suddenly you also want to pass an int? Impossible with IEnumerable<X>[], yet a piece of cake with a ViewModel - just add it to the class.

The result is null because you should bind your IEnumerable interface with a model binder. I think you are looking to create a model binding provider, because a provider can look at the type of the property and then create your custom model binder just for that property.
Take a look on this link also http://odetocode.com/blogs/scott/archive/2009/04/27/6-tips-for-asp-net-mvc-model-binding.aspx

Related

Bind HTML table TH dynamically using View Model Failed

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.

How to display IEnumerable in view? [duplicate]

This question already has answers here:
Display List in a View MVC
(2 answers)
List model razor view
(2 answers)
Closed 3 years ago.
Im doing comment section for product and have some troubles with displaying comments view. I made model from 2 models to display them in one view, but my way to display IEnumerable as list gets me an error. How can i modify my code to get this working?
Model
public class DetailsViewModel
{
public Product Product { get; set; }
public IEnumerable<Comments> Comments { get; set; }
}
Controller
public ActionResult ProductDetails(int? id)
{
DetailsViewModel detailsViewModel = new DetailsViewModel();
detailsViewModel.Product = db.Product.Find(id);//this is working
detailsViewModel.Comments = db.Comments.Where(c => c.Id_Product == id).OrderBy(x => x.DateComment).ToList();
return View(detailsViewModel);
}
View
#model Database.Models.DetailsViewModel
<ul>
#Html.DisplayFor(x => x.Comments.DateComment) //getting error here 'IEnumerable<Comments>'
#Html.DisplayFor(x => x.Comments.Comment) // does not containt definiton for 'Comment'
<ul>
i want to get something like this:
2019-06-25: Foo
2019-06-25: FooFoo
You can use a foreach loop to achieve your functionality. A foreach loop will iterate over your IEnumerable and display each element in the list. Specifically to your case, you can do:
<ul>
#foreach(var comment in Model.Comments)
{
<li>#comment.DateComment</li>
<li>#comment.Comment</li>
}
</ul>
Create a DisplayTemplate for your Comment model and then simply use
#Html.DisplayFor(x => x.Comments)
which will loop through them and use the DisplayTemplate to render each.

Filter a property IENumerable to only include items that pass a test

I have a list of items, Articles, and each article can have a series of reviews. Right now I grab the list of articles from my database using Entity Framework in my controller, db.Articles and I send that to my view. In my view I want to display all the articles and some of the reviews. For instance, the reviews done by a certain user.
The way I do this know looks like:
#foreach (var item in Model) { //item is a article, model is IEnumerable<Article>
<tr>
<td>
#Html.DisplayFor(modelItem => item.Name)
</td>
<td>
#foreach (var reviewStatus in item.ReviewStatus.Where(u=> u.user.Id == ViewBag.userId))
{
#Html.DisplayFor(model => reviewStatus.Status)
#Html.Raw(", ")
}
</td>
</tr>
}
I feel like there should be a way in my Controller to send Articles that already have this filter applied so that I don't have to do it in my view (since it can be a bit slow).
Primarily it is not good that you are using the Entity Framework object as model for your view. Model in MVC is conceptually still element in UI layer, but EF is far away from it in persistence layer. And you should have at least domain and business logic layer in between.
What I usually do is to define the Model as an extra class, I construct the instance of class in controller action handler and I take all the data I need within the view from that instance (which is available in view as Model indeed).
In your case the class(es) would look somehow like this:
public class ArticleModel {
public string Title { get; set; }
// other article properties
private IEnumerable<ReviewModel> reviews;
public IEnumerable<ReviewModel> Reviews {
get {
if (reviews == null) {
// lazy loader to get ReviewModels from your business/domain logic
}
return reviews;
}
}
}
public class ArticlesPageModel {
public string PageTitle { get; set; }
private IEnumerable<ArticleModel> articles;
public IEnumerable<ArticleModel> Articles {
get {
if (articles == null) {
// lazy load articles from business/domain logic
}
return articles;
}
}
}
etc... I hope you see the pattern...
Usually my "model" classes have a constructor, which takes domain object (EF entity in your case) and they know how to initialize themselves.
I think in the action in your controller you can do something like this:
var allArticles = new IEnumerable<Article>();
allArticles = db.GetAllArticles(); //or whatever method that you have for grabing everything from db
var filteredArticles = allArticles.FinAll(x=>x.ReviewStatus != null && x.ReviewStatus.Count > 0 && x.ReviewStatus.Exists(u=> u.user.Id == 1234) && x.ReviewStatus.Exists(u.otherCriteria = 4321 ));
foreach(var article in filterArticles)
{
var specificReviews_1 = article.ReviewStatus.FinaAll(u=> u.user.Id == 1234).Take(3);
var specificReviews_2 = article.ReviewStatus.FinaAll(u=> u.user.Id != 1234 && u.otherCriteria = 4321).Take(3);
article.ReviewStatus = new List<ReviewStatus>();
article.ReviewStatus.AddRange(specificReviews_1 );
article.ReviewStatus.AddRange(specificReviews_2 );
}
return View("yourView", filterArticles);
and then in view do something like this
#foreach (var item in Model) {
<tr>
<td>
#Html.DisplayFor(modelItem => item.Name)
</td>
<td>
#foreach (var reviewStatus in item.ReviewStatus)
{
#Html.DisplayFor(model => reviewStatus.Status)
#Html.Raw(", ")
}
</td>
</tr>
}

Can we pass the HttpGet query string implicitly

So I created MVC 4 app with Database First and Entity Framework approach.I have created respective Models,Controllers and Views(By adding MVC controller with read/write actions and views, using Entity Framework template). So it comes with Create,Delete,Edit,Index and Details Views and respective action methods.
When I click on Details,Edit or Delete I see that location id which is a parameter in action method is not passed passed with value, so only default value 0 is used. and I learned that with HttpGet we need to pass it as query string in URL,but in my case how can I implicitly pass the Location ID for Edit/Details or Delete.
You do it like this:
VIEW:
foreach (var item in Model)
{
<tr>
<td>
#Html.ActionLink("Edit", "EditAction", new {id = item._account})
</td>
<td>
#Html.ActionLink("Details", "DetailsAction", new {id = item._account})
</td>
<td>
#Html.ActionLink("Delete", "DeleteAction", new {id = item._account})
</td>
</tr>
}
CONTROLLER:
//
// GET: /Controller/EditAction/id
public ActionResult EditAction(int id)
{
//Do Something
}
//
// GET: /Controller/DetailsAction/id
public ActionResult DetailsAction(int id)
{
//Do Something
}
//
// GET: /Controller/DeleteAction/id
public ActionResult DeleteAction(int id)
{
//Do Something
}
the list View template's links are created using #Html.ActionLink right ? If so, they have to respectively contain the item Id they intend to point to explicitely, so they should be something like
#Html.ActionLink("Edit", new {id=item.Id}))
Which is correct if you use the default Route.
By the way I am surprised these links do not contain the right Id setup by default in list templates.

Why DefaultModelBinder in MVC3 does not Bind?

I do not understand why the DefaultModelBinder in MVC3 does not map the Form Post Data to my action method. I have the following ViewModels:
public class DisplayExcelTableViewModel
{
public string UploadedFileName { get; set; }
public List<string> TableHeaders { get; set; }
public List<TableRowViewModel> TableRows { get; set; }
}
public class TableRowViewModel
{
public List<string> TableColumns { get; set; }
}
They are displayed in a (partial) View using DisplayTemplates:
#using (Html.BeginForm("SubmitExcel", "Home", FormMethod.Post))
{
<fieldset>
<table>
<tr>
<th>#Html.DisplayFor(m => m.TableHeaders)</th>//<input id="TableHeaders_0_" name="TableHeaders[0]" type="text" value="Opportunity Id" />
</tr>
<tr>
<td>#Html.DisplayFor(m => m.TableRows)</td>//<input id="TableRows_0__TableColumns_0_" name="TableRows[0].TableColumns[0]" type="text" value="1-H7PKD9" />
</tr>
</table>
<input type="submit" value="Send" />
</fieldset>
}
And the action method looks like this:
public ActionResult SubmitExcel(DisplayExcelTableViewModel excelTable)
To try whether it worked just with one TableRows I tried:
public ActionResult SubmitExcel([Bind(Prefix = "TableRows")] TableRowViewModel TableRows)
to test I also tried to put List<TableRows> and take out the Bind attribute. It does not work.
I got a runtime exception:
"System.MissingMethodException: No parameterless constructor defined for this object."
May you tell me what I am doing wrong?
Thanks Francesco
The problem is that my ViewModels DID NOT have a parameterless constructor, which is what the Default Model Binder looks for(uses .NET’s Activator.CreateInstance() method, which relies on those types having public parameterless constructors).The solutions in this case are two:
1) Add a parameterless constructor to the ViewModel and the other custom classes wrapped inside it.
2) Create a custom model binder that covers also the case of your ViewModel
Thanks
Source: Pro ASP.NET MVC2 Framework (2nd Edition)
Have you checked (for example with Firebug) whether are form values being posted to the server? I'm asking because Html.DisplayFor usually renders display elements, whereas for posting values you usually have to use Html.EditorFor.

Categories

Resources