Iterating though items of Model in MVC 4 - html

I would like to iterate though the item returned from the foreach(var item in Model). Like for(int i=0; i <item.length; i++) and then subsequently write the individual html code for the items returned by the model.
Unfortunately, I cannot seem to be able to access item.length as the property doesn't exists. Would anyone know how I could iterate through each model item so that I could save up on writing a lot of unnecessary HTML?
So some more context in lieu of the question being unclear:
Let's say my model is
public class RegisterViewModel
{
public string Name { get; set; }
public string Email { get; set; }
public string Password { get; set; }
}
Now I use a DB call to access the model data and parse it to the view via
RegisterViewModel model= db.RegisterViewModel.Where(data => data.Name == User.Name);
Now I pass this model to the view via view(model)
Now to display data for all the students, I can use a
model IEnumerable<Project.Models.RegisterViewModel>
#foreach(item in Model) {
#for(int i=0; i<item.Length;i++) { \\this is where I would like to iterate though the item
if(item[i]!= null) // using a for loop would avoid me having to write this if condition multiple times.
<div>#item[i].value</div>
}
}

You cannot iterate over item because item is of type RegisterViewModel. It doesn't make sense to be doing this.
If you want to display the value you could directly use it like this:
#model IEnumerable<Project.Models.RegisterViewModel>
#foreach(item in Model) {
<div>#item.Value</div>
}
Also bear in mind that the following line of code in your controller is wrong:
RegisterViewModel model = db.RegisterViewModel.Where(data => data.Name == User.Name);
The .Where extension method returns an IEnumerable<T> so you'd rather have:
IEnumerable<RegisterViewModel> model = db.RegisterViewModel.Where(data => data.Name == User.Name);
return View(model);
or if you prefer:
var model = db.RegisterViewModel.Where(data => data.Name == User.Name);
return View(model);

Related

Table TagHelper for an IEnumerable Model (without using Reflection)

I'm trying to create a table tag helper that can parse columns and rows from the given model automatically.
This is how it should (theoretically be used):
<table for="#Model">
</table>
and this should automatically show column names and the rows.
Generating column names wasn't that difficult since I'm passing the model
[HtmlTargetElement("table", Attributes = "for")]
public class DataTableTagHelper :TagHelper
{
[HtmlAttributeName("for")]
public ModelExpression For { get; set; }
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
foreach (var item in For.Metadata.ElementMetadata.Properties)
{
// generate html for theader using item.Name
}
}
}
But getting the values of the model isn't as easy.
Is there a way to get the values of those properties?
I'm trying to avoid reflection, because I don't think generating HTML code though reflection with every request is a good idea.
We get the property's value by passing the model to its property's PropertyGetter.
public override void Process(TagHelperContext context, TagHelperOutput output)
{
foreach (var prop in For.Metadata.Properties)
{
var propertyName = prop.Name;
var propertyValue = prop.PropertyGetter(For.Model);
}
return Task.CompletedTask;
}
If the model implements IEnumerable, then we need to pass each item to its PropertyGetter.
public override Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
foreach (var item in For.Model as IEnumerable)
{
foreach (var prop in For.Metadata.ElementMetadata.Properties)
{
var name = prop.Name;
var value = prop.PropertyGetter(item);
}
}
return Task.CompletedTask;
}

ASP .NET MVC Drop Down List throwing multiple errors when empty [duplicate]

I have the following view model
public class ProjectVM
{
....
[Display(Name = "Category")]
[Required(ErrorMessage = "Please select a category")]
public int CategoryID { get; set; }
public IEnumerable<SelectListItem> CategoryList { get; set; }
....
}
and the following controller method to create a new Project and assign a Category
public ActionResult Create()
{
ProjectVM model = new ProjectVM
{
CategoryList = new SelectList(db.Categories, "ID", "Name")
}
return View(model);
}
public ActionResult Create(ProjectVM model)
{
if (!ModelState.IsValid)
{
return View(model);
}
// Save and redirect
}
and in the view
#model ProjectVM
....
#using (Html.BeginForm())
{
....
#Html.LabelFor(m => m.CategoryID)
#Html.DropDownListFor(m => m.CategoryID, Model.CategoryList, "-Please select-")
#Html.ValidationMessageFor(m => m.CategoryID)
....
<input type="submit" value="Create" />
}
The view displays correctly but when submitting the form, I get the following error message
InvalidOperationException: The ViewData item that has the key 'CategoryID' is of type 'System.Int32' but must be of type 'IEnumerable<SelectListItem>'.
The same error occurs using the #Html.DropDownList() method, and if I pass the SelectList using a ViewBag or ViewData.
The error means that the value of CategoryList is null (and as a result the DropDownListFor() method expects that the first parameter is of type IEnumerable<SelectListItem>).
You are not generating an input for each property of each SelectListItem in CategoryList (and nor should you) so no values for the SelectList are posted to the controller method, and therefore the value of model.CategoryList in the POST method is null. If you return the view, you must first reassign the value of CategoryList, just as you did in the GET method.
public ActionResult Create(ProjectVM model)
{
if (!ModelState.IsValid)
{
model.CategoryList = new SelectList(db.Categories, "ID", "Name"); // add this
return View(model);
}
// Save and redirect
}
To explain the inner workings (the source code can be seen here)
Each overload of DropDownList() and DropDownListFor() eventually calls the following method
private static MvcHtmlString SelectInternal(this HtmlHelper htmlHelper, ModelMetadata metadata,
string optionLabel, string name, IEnumerable<SelectListItem> selectList, bool allowMultiple,
IDictionary<string, object> htmlAttributes)
which checks if the selectList (the second parameter of #Html.DropDownListFor()) is null
// If we got a null selectList, try to use ViewData to get the list of items.
if (selectList == null)
{
selectList = htmlHelper.GetSelectData(name);
usedViewData = true;
}
which in turn calls
private static IEnumerable<SelectListItem> GetSelectData(this HtmlHelper htmlHelper, string name)
which evaluates the the first parameter of #Html.DropDownListFor() (in this case CategoryID)
....
o = htmlHelper.ViewData.Eval(name);
....
IEnumerable<SelectListItem> selectList = o as IEnumerable<SelectListItem>;
if (selectList == null)
{
throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture,
MvcResources.HtmlHelper_WrongSelectDataType,
name, o.GetType().FullName, "IEnumerable<SelectListItem>"));
}
Because property CategoryID is typeof int, it cannot be cast to IEnumerable<SelectListItem> and the exception is thrown (which is defined in the MvcResources.resx file as)
<data name="HtmlHelper_WrongSelectDataType" xml:space="preserve">
<value>The ViewData item that has the key '{0}' is of type '{1}' but must be of type '{2}'.</value>
</data>
according to stephens (user3559349) answer, this can be useful:
#Html.DropDownListFor(m => m.CategoryID, Model.CategoryList ?? new List<SelectListItem>(), "-Please select-")
or in ProjectVM:
public class ProjectVM
{
public ProjectVM()
{
CategoryList = new List<SelectListItem>();
}
...
}
Most Likely Caused some sort of error redirecting to your page and you not initializing your model's drop down lists again.
Make sure that you initialize your drop downs in either the model's constructor or every time before you send said model to the page.
Otherwise you will need to maintain the state of the drop down lists either through the view bag or through the hidden value helpers.
OK, the poster's canned answer neatly explained why the error occurred, but not how to get it to work. I'm not sure that's really an answer, but it did point me in the right direction.
I ran into the same issue and found a slick way to resolve it. I'll try to capture that here. Disclaimer - I work on web pages once a year or so and really don't know what I'm doing most of the time. This answer should in no way be considered an "expert" answer, but it does the job with little work...
Given that I have some data object (most likely a Data Transfer Object) that I want to use a drop-down list to supply valid values for a field, like so:
public class MyDataObject
{
public int id;
public string StrValue;
}
Then the ViewModel looks like this:
public class MyDataObjectVM
{
public int id;
public string StrValue;
public List<SectListItem> strValues;
}
The real problem here, as #Stephen so eloquently described above, is the select list isn't populated on the POST method in the controller. So your controller methods would look like this:
// GET
public ActionResult Create()
{
var dataObjectVM = GetNewMyDataObjectVM();
return View(dataObjectVM); // I use T4MVC, don't you?
}
private MyDataObjectVM GetNewMyDataObjectVM(MyDataObjectVM model = null)
{
return new MyDataObjectVM
{
int id = model?.Id ?? 0,
string StrValue = model?.StrValue ?? "",
var strValues = new List<SelectListItem>
{
new SelectListItem {Text = "Select", Value = ""},
new SelectListITem {Text = "Item1", Value = "Item1"},
new SelectListItem {Text = "Item2", Value = "Item2"}
};
};
}
// POST
public ActionResult Create(FormCollection formValues)
{
var dataObject = new MyDataObject();
try
{
UpdateModel(dataObject, formValues);
AddObjectToObjectStore(dataObject);
return RedirectToAction(Actions.Index);
}
catch (Exception ex)
{
// fill in the drop-down list for the view model
var dataObjectVM = GetNewMyDataObjectVM();
ModelState.AddModelError("", ex.Message);
return View(dataObjectVM);
)
}
There you have it. This is NOT working code, I copy/pasted and edited to make it simple, but you get the idea. If the data members in both the original data model and the derived view model have the same name, UpdateModel() does an awesome job of filling in just the right data for you from the FormCollection values.
I'm posting this here so I can find the answer when I inevitably run into this issue again -- hopefully it will help someone else out as well.
I had the same problem, I was getting an invalid ModelState when I tried to post the form. For me, this was caused by setting CategoryId to int, when I changed it to string the ModelState was valid and the Create method worked as expected.
In my case the first ID in my list was zero, once I changed the ID to start from 1, it worked.

Get scaffolder to generate fields in specific order

I am trying to get asp.net core MVC to scaffold a Razor View with fields in a different order than the apparently default alphabetical order. I've got a simple model:
public class Application : EntityBase
{
[Display(Name = "Naam", Order = 1)]
public string Name { get; set; }
[Display(Name = "Omschrijving", Order = 2)]
public string Description { get; set; }
}
I want the scaffolder to generate a field for Name before Description. How to do this?
I've been trying to come up with a solution in the Razor template. The relevant code is:
...
IEnumerable<PropertyMetadata> properties = Model.ModelMetadata.Properties;
foreach (var property in properties)
{
if (property.Scaffold && !property.IsPrimaryKey && !property.IsForeignKey)
{
...
I was hoping that a property had an Order-property, so I could write something like
foreach (var property in properties.OrderBy(p => p.Order))
Any ideas?
So, after some (deep) digging I came up with a solution. As I allready customized the templates, it was acceptable to add yet another customization. I ended up creating a helper class ScaffoldHelpers.cs:
public class ScaffoldHelpers
{
public static IEnumerable<PropertyMetadata> GetPropertiesInDisplayOrder(string typename, IEnumerable<PropertyMetadata> properties)
{
Type type = Type.GetType($"{typename}, {typename.Split('.')[0]}");
SortedList<string, PropertyMetadata> propertiesList = new SortedList<string, PropertyMetadata>();
foreach (PropertyMetadata property in properties)
{
int order = 0;
if (type != null)
{
var member = type.GetMember(property.PropertyName)[0];
var displayAttribute = member.GetCustomAttribute<System.ComponentModel.DataAnnotations.DisplayAttribute>();
if (displayAttribute != null)
{
order = displayAttribute.Order;
}
}
propertiesList.Add($"{order:000} - {property.PropertyName}", property);
}
return propertiesList.Values.AsEnumerable();
}
}
This iterates over all the properties, and determines if a [Display()] attribute is specified. If so, it gets the value of the Order-parameter. If you don't specify this, the Order-property will be zero. Using a SortedList and making sure the key is ordered by the specified order, I'm able to easily return an IEnumerable<PropertyMetadata> in the desired order.
In the template, I needed to add #using for this helper-class. After that, I could insert the following into the template:
...
IEnumerable<PropertyMetadata> properties = Model.ModelMetadata.Properties;
// added:
properties = ScaffoldHelpers.GetPropertiesInDisplayOrder(Model.ViewDataTypeName, properties);
foreach (var property in properties)
{
if (property.Scaffold && !property.IsPrimaryKey && !property.IsForeignKey)
{
...
That's it!

Proper way to return JSON list in Web API with MVC and partial model

I am trying to use Web API to grab certain fields from my MVC controller. I can't seem to match the right type with the right list. I am fine with converting everything to string.
I either get an error in code (can not convert types), or if I get it to compile, I get this error:
"The 'ObjectContent`1' type failed to serialize the response body for content type 'application/xml; charset=utf-8'."
From other similar posts, people responded with how to create a list, but not with the declaration of the return value of the Get. Please include both.
Also I would prefer not to add additional controllers as I need to do this on a number of my models.
Here is my code--note you can see I tried a number of different ways:
public class APICLIENTsController : ApiController
{
private ApplicationDbContext db = new ApplicationDbContext();
// GET api/<controller>
public IEnumerable<string> Get()
//public IEnumerable<CLIENT> Get()
{
//return db.CLIENTs.OrderBy(x => x.CLIENTNAME).ToList();
string[] listOfUsers = db.CLIENTs.OrderBy(x => x.CLIENTNAME).Select(r => new
{
ID = r.CLIENTID.ToString(),
NAME = r.CLIENTNAME
});
return listOfUsers.ToList();
//return db.CLIENTs.Select(x => new { x.CLIENTNAME }).ToArray();
}
If you want to return JSON use the
JsonResult
type.
public JsonResult Get()
{
//return db.CLIENTs.OrderBy(x => x.CLIENTNAME).ToList();
string[] listOfUsers = db.CLIENTs.OrderBy(x => x.CLIENTNAME).Select(r => new
{
ID = r.CLIENTID.ToString(),
NAME = r.CLIENTNAME
});
return Json(listOfUsers.ToList(), JsonRequestBehavior.AllowGet);
}
Your query is returning a collection of anonymous objects, not string[] so it will throw an exception. Even if you were to generate string[] by concatenating the CLIENTID and CLIENTNAME properties, it would be a little use to the client.
Create a model to represent what you need to return to the view
public class ClientVM
{
public int ID { get; set; }
public string Name { get; set; }
}
and modify your method to
public IEnumerable<ClientVM> Get()
{
IEnumerable<ClientVM> model = db.CLIENTs.OrderBy(x => x.CLIENTNAME).Select(r => new ClientVM
{
ID = r.CLIENTID,
Name = r.CLIENTNAME
});
return model;
}
Side note: depending on how your calling and consuming this in the client, you may need to change the Content-Type to specifically return json (refer these answers for more detail)

asp.net mvc 5 Dapper Json is mapping the whole model class

I am using Dapper in my ASP.NET MVC 5 application and in my query I only want 2 fields to return but the Json returns all of the fields. This is my model
public class thread
{
[Key]
public int id { get; set; }
public int? profileID { get; set; }
public int numberkeeper { get; set; }
public int? photocount { get; set; }
}
This is my controller..
[ResponseType(typeof(thread))]
public IHttpActionResult Getstream()
{
string Connectionstring = ConfigurationManager.ConnectionStrings["db"].ConnectionString;
using (System.Data.SqlClient.SqlConnection sqlConnection = new System.Data.SqlClient.SqlConnection(Connectionstring))
{
sqlConnection.Open();
var statevi = sqlConnection.Query<thread>("Select top 5 id,numberkeeper from threads").ToList();
if (statevi == null)
{
return NotFound();
}
return Ok(statevi);
}
}
That code returns Json as it is using .Net Web API,as you can see from the query I only want 2 fields returned. When I run it and see the Json it displays all fields (4) and off course the 2 fields not selected show up as null . I wanted so that the Json only shows the returnn of id and numberkeeper
Create a View Model class:
public class ThreadViewModel
{
public int id { get; set; }
public int numberkeeper { get; set; }
}
Let Dapper know you want it to create the ThreadViewModel for you:
var statevi = sqlConnection.Query<ThreadViewModel>("Select top 5 id,numberkeeper from threads").ToList();
This way you both query the database for the relevant properties and return just them to the client (without Dapper creating the full object with nulls).
If you create a new model that exposes the only two members that you want to render, that will prevent Web API from returning back additional JSON.
You could also convert the data after loading it into a new anonymous model using LINQ.
return Ok(statevi.Select(s => new { s.id, s.numberkeeper }));
If you want to keep the same model, but suppress null valued members Web API allows you to configure the JSON formatting to exclude null properties.
config.Formatters.JsonFormatter.SerializerSettings = new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Ignore
};
If you want to use 2 or selected rows from query then you can use query method and extension method...
1. LINQ query method
using (System.Data.SqlClient.SqlConnection sqlConnection = new System.Data.SqlClient.SqlConnection(Connectionstring))
{
sqlConnection.Open();
var statevi = sqlConnection.Query<thread>("Select top 5 id,numberkeeper from threads").ToList();
if (statevi == null)
{
return NotFound();
}
var result = (from d in statevi
select new { d.id, d.numberkeeper }).ToList();
return Ok(result);
}
Extension Method: change this syntax to result of query method of above
var result = query.Select(d => new { d.Id, d.Title }).ToList();
both will give result same.
let me tell if it is working fine for your project or not.