Ok, so I have seen a lot of similar questions, but unfortunalety I can't figure out how to access a list of users from db in the View. I get a System.NullReferenceException. Can one of you experts see what I am doing wrong?
Model
public class MyModel
{
[Key]
public int Id { get; set; }
[Display(Name = "Name")]
public string Name { get; set; }
public bool TriggerOnLoad { get; set; }
public string TriggerOnLoadMessage { get; set; }
public string EmployeeNumber { get; set; }
public IEnumerable<User> Users { get; set; }
public List<MyModel> GetAllUsers()
{
var queryString = "SELECT Name FROM Users";
var adapter = new SqlDataAdapter(queryString, System.Configuration.ConfigurationManager.ConnectionStrings["SQLConn"].ConnectionString);
var current = new DataSet();
adapter.Fill(current, "Name");
return (from DataRow item in current.Tables[0].Rows
select new MyModel()
{
Name = Convert.ToString(item["Name"]),
}).ToList();
}
Controller
public ActionResult GetUser()
{
var model = new MyModel();
_db.GetAllUsers();
return View(model);
}
View
#model ModelName.Models.MyModel
--HTMLCode--
#foreach (var item in Model.Users) <-- Exception.
{
<tr>
<td>#Html.DisplayFor(modelItem => item.Name)</td>
</tr>
}
What am I forgetting?
Your problem is that you are getting the users, but you are not passing the users to the view, instead, you pass an empty model.
A better approach would be to create a ViewModel with all the properties you need in the view.
Let's say your view model looks something like this:
ViewModel:
class YourViewModel
{
public string Name { get; set; }
public bool TriggerOnLoad { get; set; }
public IEnumerable<User> Users { get; set; }
}
After creating your ViewModel what you need to do is that you need to create an instance of the ViewModel in your Controller and fill it with data and then pass the ViewModel to the View. In your case it would look something like this:
Action:
public ActionResult RandomAction()
{
YourViewModel vm = new YourViewModel()
{
Users = _db.GetAllUsers(),
Name = "Random Name",
TriggerOnLoad = true
};
return View(vm);
}
Later on, if you decide you need some extra properties you need to work with in your view, you just add them to your ViewModel and continue on.
Pass users like this. You are not passing the users to View
public ActionResult GetUser()
{
var model = new MyModel();
var users = _db.GetAllUsers();
return View(users);
}
Then in View
#model List<MyModel>
#foreach (var item in Model) //foreach through the list
{
<tr>
<td>#Html.DisplayFor(model => item.Name)</td>
</tr>
}
Related
I'm building a website in ASP.Net, using MVC, and need to list a set of results
but i get error in the code
model:
public class Customers
{
public int Id { get; set; }
public string Name { get; set; }
public List<Customers> Itemlst { get; set; }
}
controller:
public ActionResult List()
{
Customers itemobj = new Customers();
return View(itemobj);
}
view:
#foreach(var item in Model.Itemlst)
{
<tr>
<td>Items ID:</td>
<td>#item.ID</td>
<td>Items Name:</td>
<td>#item.Name</td>
</tr>
}
</table>
From the NullReferenceException that you are receiving we can see that the issue is because of the Itemlst not being initialised. One of the ways to solve this is just to make sure that there is a valid list when you create the object:
public class Customers
{
public Customers()
{
Itemlst = new List<Customers>();
}
public int Id { get; set; }
public string Name { get; set; }
public List<Customers> Itemlst { get; set; }
}
So you can add values to the list in your action if need:
public ActionResult List()
{
Customers itemobj = new Customers();
var example = new Customers ();
example.Id = 1;
example.Name = "Example";
itemobj.Add();
return View(itemobj);
}
I don't know if you are just using this as an example for your question, but I can't help but notice that there is something weird. You could use something different like:
public class ViewModel // Name to what makes sense to you
{
// Some other properties...
public List<Customer> Customers { get; set; }
}
public class Customer
{
public int Id { get; set; }
public string Name { get; set; }
}
Or you could just use List<Customer> as your model in the view directly (yes, your model can be a object which is simply a list of objects).
When you pass the Customers list to the view, this list itself is the model.
Change Model.Itemlst —> Model inside the foreach loop.
This will iterate the list of customers.
I have a below model
public class category
{
[Key]
public long category_id { get; set; }
public string category_desc { get; set; }
public long? client_id { get; set; }
public long? display_sno { get; set; }
}
controller passing the model to view
public ActionResult category(long? client_id)
{
var category_type = db.category.Where(m => m.client_id == null).ToList();
if(client_id == 10)
{
category_type = db.category.Where(m => m.client_id == client_id).ToList();
}
return View(category_type);
}
In view populating the radion button
#foreach (var item in Model)
{
#Html.DisplayFor(modelItem => item.display_sno)<text>.</text>
#Html.RadioButtonFor(modelItem => item.category_id, item.category_id, new { id=item.category_id})#item.category_desc
}
post Method
public ActionResult Category(category g)
{
}
In post method g is coming as null.
What I am missing here?
Your misunderstanding how radio buttons work. They are for binding one of many options to a single property, in the same way a dropdownlist works. Your code is generating radio buttons that bind to itself. And if you inspect the html you will see that it generates name="item.category_id" which would not bind to your model anyway (that would only bind to a model containing a complex object named Item which contained a property named category_id).
Assuming you have a model for a Product which contained a property for its Category, and you wanted to assign one of the values of your category table to the Category property, then your view models would look like
public class ProductVM
{
[Required(ErrorMessage = "Please select a category")]
public int? Category { get; set; }
public IEnumerable<CategoryVM> CategoryOptions { get; set; }
}
public class CategoryVM
{
public int ID { get; set; }
public string Name { get; set; }
}
Note the use of a nullable property for Category is explained in What does it mean for a property to be [Required] and nullable?
Then the controller methods (for a Create view) would look like
public ActionResult Create()
{
ProductVM model = new ProductVM();
ConfigureViewModel(model);
return View(model);
}
public ActionResult Create(ProductVM model)
{
if (!ModelState.IsValid())
{
ConfigureViewModel(model);
return View(model);
}
.... // Initialize your Product data model, set its properties based on the view model
// Save and redirect
}
private void ConfigureViewModel(ProductVM model)
{
model.CategoryOptions = db.categories.Select(x => new CategoryVM
{
ID = x.category_id,
Name = x.category_desc
});
}
Then the view would be
#model ProductVM
....
#using (Html.BeginForm())
{
#Html.DisplayNameFor(m => m.Category)
foreach(var category in Model.CategoryOptions)
{
<label>
#Html.RadioButtonFor(m => m.Category, category.ID, new { id = "" })
<span>#category.Name</span>
</label>
}
#Html.ValidationMessageFor(m => m.Category)
<input type="submit" .... />
}
Note that the new { id = "" } is removing the id attribute which would otherwise be invalid html because of duplicates.
i have created 2 model classes Mocmodel and mocsubmodel and set navigation property also.for each model id, there will be different submodels for a particular model id. i wanted to create am action link showing count of submodels, when i click on the count, view should display submodel details for each particular modelIDs like below
1220 FXRate count as hyperlink
public class tModel
{
public int ModelID { get; set; }
public string ModelName { get; set; }
public virtual ICollection<tSubModelcs> submodellist { get; set; }
}
public class tSubModelcs
{
public int submodelID { get; set; }
public string submodel { get; set; }
public DateTime launchdate { get; set; }
public int ModelID { get; set; }
}
in my view page i want to display the count of submodel as a link
#foreach (var m in Model)
{
<tr>
<td>#m.ModelID</td>
<td>#m.ModelName</td>
<td>
Html.ActionLink(, "findsubmodels", new { #id = m.ModelID, #class = "linkclick" })
</td>
</tr>
Nothing is clear. Which model are you sending to view ? Post your complete code with action methods. It seems like you are sending IEnumerable of tModel. If this is the case your Actionlink should be like this.
#if(m.submodellist != null)
{
#Html.ActionLink(m.submodellist.Count.ToString(), "findsubmodels",null, new { id = #m.ModelID, #class = "linkclick" })
}
Replace the null with your routevalue if any.
I have an index page that lists all users. This has the following in the Users controller -
public ViewResult Index()
{
return View(userRepository.AllIncluding(user => user.Roles));
}
and then the view starts with
#model IEnumerable<TRS.Models.User>
and then uses
#foreach (var item in Model) {
to loop through all users in my model.
I now need to change my model to a ViewModel that contains both the User model and an extended UserDetails model.
I have changed my Index view to use the view model -
#model IEnumerable<TRS.ViewModels.RegisterViewModel>
But I don't know how I should be going about filling the ViewModel in my controller -
public ViewResult Index()
{
var viewModel = new RegisterViewModel
{
UserName = "???"
FirstName = "???"
LastName = "???"
};
return View(viewModel);
}
I assume I need to create an instance of the view model and then pass it to the view. But I don't understand how I can get data against each individual item. I'll obviously need to get all data for all users here and then loop through them in my view. Any ideas what I should be going in the controller above? What should replace the "???" with that will fill the viewModel with all the data? Or is this the wrong approach?
Thanks
Edit - Models added -
public class User
{
[Key]
public string UserName { get; set; }
public virtual ICollection<Role> Roles { get; set; }
public virtual UserDetails UserDetails { get; set; }
}
public class UserDetails
{
[Key]
public string UserName { get; set; }
public string Firstname { get; set; }
public string Lastname { get; set; }
[Required]
public virtual User User { get; set; }
}
Edit - From View -
<td>
#foreach (Role role in item.Roles){
#role.RoleName <br />
}</td>
public ActionResult Index()
{
var usersVm = userRepository
.AllIncluding(user => user.Roles)
.Select(user => new RegisterViewModel
{
UserName = user.UserName
FirstName = user.UserDetails.FirstName
LastName = user.UserDetails.LastName
});
return View(usersVm);
}
Automapper was created for this very thing. I'd highly recommend it.
Take a read of getting started section of wiki.
Once you have it configured your controller code would look something like this:
List<RegisterViewModel> registrants =
Mapper.Map<List<User>, List<RegisterViewModel>>(users);
Though you might want to consider a viewmodel that has a list of registrants on them but that is up to you.
Your viewModel
Public Class RegisterViewModel
{
Public IEnumerable<TRS.Models.User> AllUsers {get;set;}
Public IEnumerable<TRS.Models.UserDetails> UserDetails {get;set;}
}
Then in your controller
public ViewResult Index()
{
var viewModel = new RegisterViewModel
{
AllUsers = userRepository.AllIncluding(user => user.Roles).ToList()
};
var DetailList = new list<TRS.Models.UserDetails>();
foreach(var user in viewModel.AllUsers)
{
DetailList.add(new userDetails
{
name = user.Name,
age = user.age,
....
}
}
viewModel.UserDetails = DetailList;
return View(viewModel);
}
it is unclear from your question what UserDetails look like.
You should not use IEnumerable<RegisterViewModel> in your view. The RegisterViewModel should be the container of everything you will need in the view:
public ViewResult Index()
{
var usersDetails = GetUserDetails(userRepository.AllIncluding(user => user.Roles));
var viewModel = new RegisterViewModel
{
UserDetails = usersDetails.ToList(),
SomeMoreDataRequiredByTheView= "1"
};
return View(viewModel);
}
//you can use automapper to do this dirty job.
private IEnumerable<UserDetails> GetUserDetails(IEnumerable<User> users)
{
foreach(var user in users)
{
yield return new UserDetails()
{
FullName = user.FirstName + " " + user.LastName,
// Other stuff you want
};
}
}
I am trying to create a database with a many-to-many relationship using EF code first.
public class Item
{
public int ItemId { get; set; }
public String Description { get; set; }
public ICollection<Tag> Tags { get; set; }
public Item()
{
Tags = new HashSet<Tag>();
}
}
public class Tag
{
public int TagId { get; set; }
public String Text { get; set; }
public ICollection<Item> Presentations { get; set; }
public Tag()
{
Presentations = new HashSet<Item>();
}
}
public class ItemsEntities : DbContext
{
public DbSet<Item> Items { get; set; }
public DbSet<Tag> Tags { get; set; }
}
After that I'm adding an Item to the database
var tag = new Tag { Text = "tag1" };
var item = new Item
{
Description = "description1",
Tags = new List<Tag>()
};
item.Tags.Add(tag);
using (var db = new ItemsEntities())
{
db.Items.Add(item);
db.SaveChanges();
}
The problem is that I can't output items with their associated tags. The controller looks like this:
public ActionResult Index()
{
ItemsEntities db = new ItemsEntities();
return View(db.Items.ToList());
}
and the view page has the following code:
#foreach (var item in Model)
{
<tr>
<td>
#Html.DisplayFor(model => item.Description)
</td>
<td>
#foreach (var tag in item.Tags)
{
#tag.Text
}
</td>
</tr>
}
I expect the table to contain "description1" and "tag1" but I get only "description1". I really don't understand where the problem is. What is the correct way to do this?
Your navigation properties need to be marked virtual.
public class Item
{
public int ItemId { get; set; }
public String Description { get; set; }
public virtual ICollection<Tag> Tags { get; set; }
public Item()
{
Tags = new HashSet<Tag>();
}
}
public class Tag
{
public int TagId { get; set; }
public String Text { get; set; }
public virtual ICollection<Item> Presentations { get; set; }
public Tag()
{
Presentations = new HashSet<Item>();
}
}
To make your code work, you could mark your collection properties as virtual stated by #danludwig. By marking the collection properties as virtual EF Code First will lazy load those properties when iterating over the items in your view. You run into a SELECT N+1 problem using this approach. Let's examine your view code:
#foreach (var item in Model)
{
<tr>
<td>
#Html.DisplayFor(model => item.Description)
</td>
<td>
#foreach (var tag in item.Tags)
{
#tag.Text
}
</td>
</tr>
}
In this foreach loop you iterate over all items in your Model that were selected using the EF data context.
db.Items.ToList()
This is your first select. But in your view above every time you access an item's Tags property another select is executed. The important thing is FOR EVERY ITEM. That means if you have 100 Items in db.Items DbSet, you'll execute 101 selects. This is not acceptable for most systems.
A better approach is to pre select the tags for each item. One approach is to use Include or to select the tags related to an item into dedicated object.
public class ItemWithTags
{
public Item Item { get;set; }
public IEnumerable<Tag> Tags { get;set; }
}
public ActionResult Index()
{
ItemsEntities db = new ItemsEntities();
var itemsWithTags = db.Items.Select(item => new ItemWithTags() { Item = item, Tags = item.Tags});
return View(itemsWithTags.ToList());
}
In your view you can iterate over the itemsWithTags collection, access items's properties and for tags you access the Tags property of ItemWithTags.
Another problem with your code is, that the ItemsEntities DbContext is opened in your code but never closed. You can use the VS MVC Templates to generate a Controller that handles DbContext opening and closing correctly!
You can use a tool like MVC Mini Profiler for to inspect the commands executed against the database. This Stackoverflow Question shows how to set up MVC Mini Profiler with EF Code First.