I have an Index view that shows a list of tenants and it works great. I added a small form to the index view to take in and pass a query string to the index method of my Tenants controller. As you can see, I have an If statement that checks to see if the query string is empty. If it is not empty, it goes in and grabs a tenant that has a first name that contains characters of the query string. Well, it is at this point I am receiving an error. I believe it has something to do with the way I am using the ICollection of Tenants in the ApplicatonUser Model or the way I am first loading the tenants that correspond to the logged in user into the tenants variable. I have added all my info below to help diagnose the issue.
Error Message
Suppression State Error CS0266 Cannot implicitly convert type 'System.Collections.Generic.IEnumerable' to 'System.Collections.Generic.ICollection'. An explicit conversion exists (are you missing a cast?) mentorient
My Index View:
#model IEnumerable<mentorient.Models.Tenant>
#{
ViewData["Title"] = "Index";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<nav>
<hr/>
<a asp-controller="Tenants" asp-action="New">New Tenant</a>
<hr/>
</nav>
#if (!Model.Any())
{
<div class="alert alert-warning alert-dismissible" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button>
<strong>Hold Up!</strong> You do not have any tenants yet.
</div>
}
else
{
<form>
<p>
Name: <input type="text" name="SearchString"/>
<input type="submit" value="Filter"/>
</p>
</form>
<table class="table table-striped">
<thead>
<tr>
<th>Id</th>
<th>Name</th>
<th>Options</th>
</tr>
</thead>
<tbody>
#foreach (var tenant in Model)
{
<tr>
<td>#tenant.Id</td>
<td>#tenant.FirstName #tenant.LastName</td>
<td><a asp-action="Delete" asp-route-id="#tenant.Id">Delete</a> | <a asp-action="Details" asp-route-id="#tenant.Id">Details</a></td>
</tr>
}
</tbody>
</table>
}
The Index method of the tenant controller that is accepting the query string:
public IActionResult Index(string searchString)
{
var userId = _userManager.GetUserId(User);
var tenants = _context.Users.Include(usr => usr.Tenants)
.Single(usr => usr.Id == userId)
.Tenants;
if (!String.IsNullOrEmpty(searchString))
{
tenants = tenants.Where(t => t.FirstName.Contains(searchString)); // this is where I am getting my error.
}
return View(tenants);
}
Here is the Tenant Model:
namespace mentorient.Models
{
public class Tenant
{
public int Id { get; set; }
[Required]
public string FirstName { get; set; }
[Required]
public string LastName { get; set; }
[Phone]
public int PhoneNumber { get; set; }
[Required]
public string Address { get; set; }
public string Email { get; set; }
[Required]
public DateTime DateOfBirth { get; set; }
[Required]
public string City { get; set; }
[Required]
public string ZipCode { get; set; }
[Required]
public string State { get; set; }
public string Country { get; set; }
}
}
My ApplicationUser Model:
namespace mentorient.Models
{
// Add profile data for application users by adding properties to the ApplicationUser class
public class ApplicationUser : IdentityUser
{
public virtual ICollection<Tenant> Tenants { get; set; }
= new List<Tenant>();
}
}
You need to add .ToList() at end of your query to select tenant list,
tenants.Where(t => t.FirstName.Contains(searchString)).ToList();
Related
I have two classes, Student and Details.
public class Student
{
[Key]
public int StudentD { get; set; } // PK
public string StudentName { get; set; }
public string StudentType { get; set; }
public virtual ICollection<Details> Details { get; set; } // FK
}
public class Details
{
[Key]
public int DetailsID { get; set; } // PK
public string Property { get; set; }
public string Value { get; set; }
public virtual Student Student { get; set; }
public int StudentID { get; set; } // FK
}
I display the list of all students on the web page and I would like to allow the editing of student details for the selected student.
<form method="post">
<table class="table">
<thead>
<tr>
<th></th>
<th> #Html.DisplayNameFor(model => model.Details[0].Name) </th>
<th> #Html.DisplayNameFor(model => model.Details[0].Value) </th>
</tr>
</thead>
<tbody>
#foreach (var item in Model.Details)
{
<tr>
<td> <input type="hidden" asp-for=#item.DetailsID /> </td>
<td> #Html.DisplayFor(modelItem => item.Property) </td>
<td contenteditable='true'> #Html.DisplayFor(modelItem => item.Value) </td>
</tr>
}
</tbody>
</table>
<div class="form-group">
<input type="submit" value="Save" class="btn btn-primary" />
</div>
</form>
The page model is as below,
public class EditModel : PageModel
{
private readonly TestEFCore.Data.TestEFCoreContext _context;
public EditModel(TestEFCore.Data.TestEFCoreContext context)
{
_context = context;
}
[BindProperty]
public IList<TestEFCore.Models.Details> Details { get; set; }
public async Task<IActionResult> OnGetAsync(int? id)
{
if (id == null) { return NotFound(); }
Detailss = await _context.Details.Where(m => m.StudentID == id).ToListAsync();
return Page();
}
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid) { return Page(); }
_context.Attach(Details).State = EntityState.Modified;
try { await _context.SaveChangesAsync(); }
catch (DbUpdateConcurrencyException) { throw; }
return RedirectToPage("./Index");
}
}
When a student is selected, I then display the details in a HTML table and would like EF Core to keep track of changes in the HTML table. To achieve this, I create an IList of Details and display it, but when the user updates any values, I get an error that the IList doesn't exist in the model which I can understand because the model only has DB table column info and not the IList of rows as such.
Would anyone be able to suggest how can I achieve this?
I got it working after changing the type as below, from IList to List
[BindProperty]
public List<TestEFCore.Models.Details> Details { get; set; }
I am trying to delete and entry from my database by click a delete link. I am also trying to use entity framework:
Here is what I have in my controller:
public ActionResult DeleteUserUserList(string UserName)
{
using (UsersContext db = new UsersContext())
{
//var username = db.UserProfile.UserName;
db.Entry(UserName).State =
System.Data.Entity.EntityState.Deleted;
db.SaveChanges();
}
return RedirectToAction("UserList", "Account", new {UserName =
UserName});
}
Then my model looks like this:
public class UsersContext : DbContext
{
public UsersContext()
: base("DefaultConnection")
{
}
public DbSet<UserProfile> UserProfiles { get; set; }
public DbSet<Membership> Membership { get; set; }
public DbSet<Role> Roles { get; set; }
}
[Table("UserProfile")]
public class UserProfile
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int UserId { get; set; }
public string UserName { get; set; }
public string Email { get; set; }
}
And this is what my view looks like:
#model IEnumerable<ComtrexCloudReporting.Models.UserProfile>
#{
ViewBag.Title = "UserList";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2 class="admin-home-link orange-titles">#Html.ActionLink("User
Information", "AdminIndex")</h2>
<p> </p>
#foreach (var item in Model)
{
<p class=" col-sm-4 userNameUserList">#Html.DisplayFor(modelItem =>
item.UserName) </p>
<p class="col-sm-4 to-link"><span style="color: #f05322">|</span> <a
href="#string.Format("mailto:{0}",
item.Email)">#Html.DisplayFor(modelItem => item.Email)</a></p>
<p class="col-sm-4 to-link"><span style="color: #f05322">|</span> <span
onclick="return confirm('Are you sure to delete?')"><a class="back-link"
href="/Account/DeleteUserUserList?UserName=#item.UserName">Delete</a>
</p>
}
I'm getting the error that the type String is not part of the model for the current context. Does anyone know what I am doing wrong? It is breaking at this point right here: db.Entry(UserName).State = System.Data.Entity.EntityState.Deleted;
You don't have a DbSet<string> (and you can not have), thus the string is not an database entity. You need to first find you UserProfile entity:
var userProfile = context.UserProfiles.FirstOrDefault(u => u.UserName == userName);
...and then, remove it:
context.UserProfiles.Remove(userProfile);
I am using Asp.Net Core 2.1.0 in a project where I want to add one extra property to default scaffolding Index.cshtml page.
Here is my Entities Please suggest.
public class Role
{
public int RoleId { get; set; }
public string RoleName { get; set; }
public ICollection<UserRole> UserRole { get; set; }
}
public class User
{
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public string UserName { get; set; }
public string Password { get; set; }
public string MobileNumber { get; set; }
public string Email { get; set; }
public ICollection<UserRole> UserRole { get; set; }
}
public class UserRole
{
public int Id { get; set; }
public string UserName { get; set; }
public int RoleId { get; set; }
[ForeignKey("RoleId")]
public Role Role { get; set; }
[ForeignKey("UserName")]
public User User { get; set; }
}
Now, the default scaffolding Index.cshtml displays RoleID and UserNamewhere as i want to add one more coloumn i.e RoleName which is available at Role entity.
List should be RoleID, RoleName, UserName
Here is my scaffolding page model.
public class IndexModel : PageModel
{
private readonly Test.Models.TestContext _context;
public IndexModel(Test.Models.TestContext context)
{
_context = context;
}
public IList<UserRole> UserRole { get;set; }
public async Task OnGetAsync()
{
UserRole = await _context.UserRole
.Include(u => u.Role)
.Include(u => u.User).ToListAsync();
}
}
Please help me out without disturbing any other pages such as Edit, Detail, Delete.
Update: Code in Index.cshtml
#page
#model Test.Pages.UserRoles.IndexModel
#{
ViewData["Title"] = "Index";
}
<h2>Index</h2>
<p>
<a asp-page="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>
#Html.DisplayNameFor(model => model.UserRole[0].Role)
</th>
<th>
#Html.DisplayNameFor(model => model.UserRole[0].User)
</th>
<th></th>
</tr>
</thead>
<tbody>
#foreach (var item in Model.UserRole)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.Role.RoleId)
</td>
<td>
#Html.DisplayFor(modelItem => item.User.UserName)
</td>
<td>
<a asp-page="./Edit" asp-route-id="#item.Id">Edit</a> |
<a asp-page="./Details" asp-route-id="#item.Id">Details</a> |
<a asp-page="./Delete" asp-route-id="#item.Id">Delete</a>
</td>
</tr>
}
</tbody>
</table>
You can try to use #item.Role.RoleName in your index.chtml
Note
I will suggest you use different ViewModel class to carry data for each view instead of ModelContext because of ModelContext responsible for getting DB data properties to mapper Db tables schema.
ViewModel responsible for carrying show data.
Here is a link wish can help you
In my ASP.NET MVC Core app, from an action method shown below, I'm passing Blogs data and its related data from Posts table to a view as return View(await _context.Blogs.Include(p => p.Posts).ToListAsync()); Since I'm passing data from two tables, I need to use a ViewModel shown below. Question: How can I use ViewModel to pass the related data from my Controller Action method
Test() to view shown below?
In the code below I'm getting the obvious error:
InvalidOperationException: The model item passed into the ViewDataDictionary is of type 'System.Collections.Generic.List'1[ASP_Core_Blogs.Models.Blog]', but this ViewDataDictionary instance requires a model item of type 'System.Collections.Generic.IList'1[ASP_Core_Blogs.Models.BlogPostViewModels.BlogsWithRelatedPostsViewModel]'.
Model:
public class BloggingContext : DbContext
{
public BloggingContext(DbContextOptions<BloggingContext> options)
: base(options)
{ }
public DbSet<Blog> Blogs { get; set; }
public DbSet<Post> Posts { get; set; }
}
public class Blog
{
public int BlogId { get; set; }
public string Url { get; set; }
public List<Post> Posts { get; set; }
}
public class Post
{
public int PostId { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public int PostYear { get; set; }
public int BlogId { get; set; }
public Blog Blog { get; set; }
}
Controller:
[HttpGet]
public async Task<IActionResult> Test(string returnUrl = null)
{
ViewData["ReturnUrl"] = returnUrl;
return View(await _context.Blogs.Include(p => p.Posts).ToListAsync());
}
ViewModel:
public class BlogsWithRelatedPostsViewModel
{
public int BlogID { get; set; }
public int PostID { get; set; }
public string Url { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public int PostYear { get; set; }
}
View:
#model IList<ASP_Core_Blogs.Models.BlogPostViewModels.BlogsWithRelatedPostsViewModel>
<div class="row">
<div class="col-md-12">
<form asp-controller="DbRelated" asp-action="EnterGrantNumbers" asp-route-returnurl="#ViewData["ReturnUrl"]" method="post">
<table class="table">
<thead>
<tr>
<th></th>
<th></th>
<th>Url</th>
<th>Title</th>
<th>Content</th>
</tr>
</thead>
<tbody>
#for (int t = 0; t < Model.Count; t++)
{
<tr>
<td><input type="hidden" asp-for="#Model[t].BlogID" /></td>
<td><input type="hidden" asp-for="#Model[t].PostID" /></td>
<td>
<input type="text" asp-for="#Model[t].Url" style="border:0;" readonly /> <!--Not using /*Html.DisplayFor(modelItem => Model[t].Url)*/ since it does not submit stateName on Post. Not using <label asp-for=.....> since Bootstrap bold the text of <label> tag-->
</td>
<td>
<input asp-for="#Model[t].Title" />
</td>
<td>
<input asp-for="#Model[t].Content" />
</td>
</tr>
}
</tbody>
</table>
<button type="submit" class="btn btn-default">Save</button>
</form>
</div>
</div>
You need to project your query using your BlogsWithRelatedPostsViewModel class:
return View( _context.Blogs
.Include(p => p.Posts)
.SelectMany(e=> e.Posts.Select(p=> new BlogsWithRelatedPostsViewModel
{
BlogId= e.BlogId,
PostId=p.PostId,
Url=e.Url,
...
})
.ToList());
SelectMany extension method allows you flatten each projection from e.Posts into one sequence, so at the end you will get a List<BlogsWithRelatedPostsViewModel>
On top of Octavioccl's, answer there is a nice little extension method I have been using (I don't know of the author to this but if anyone else knows, I will happily update my answer to give credit). This way, you don't have to write out each property.
public static T Cast<T>(this object myobj)
{
var target = typeof(T);
var x = Activator.CreateInstance(target, false);
var d = from source in target.GetMembers().ToList()
where source.MemberType == MemberTypes.Property
select source;
var memberInfos = d as MemberInfo[] ?? d.ToArray();
var members = memberInfos.Where(memberInfo => memberInfos.Select(c => c.Name)
.ToList().Contains(memberInfo.Name)).ToList();
foreach (var memberInfo in members)
{
var propertyInfo = typeof(T).GetProperty(memberInfo.Name);
if (myobj.GetType().GetProperty(memberInfo.Name) == null) continue;
var value = myobj.GetType().GetProperty(memberInfo.Name).GetValue(myobj, null);
propertyInfo.SetValue(x, value, null);
}
return (T)x;
}
Usage:
var ViewModelList = ModelList.Select(model => model.Cast<ViewModel>()).ToList();
There is also a well supported framework built for this specific problem. Called AutoMapper (http://automapper.org/).
For passing data from Action to view as ViewModel. Create a new instance of your View Model first and assign value to each propery by calling your context query(whatever your Linq query is) and return the list of view as your View model variable.
var blogWithRelatedPost = new BolblogWithRelatedPost();
// your logic here for assigning value to property or LINQ query
return View(blogWithRelatedPost);
I have a strongly-typed view (bound to userController) which lists the User with particular Roles and below that I have a dropdownlist containing all the Roles with a submit button. All I need is to assign new Role to that User. The ActionResult method is in UserRolesController. how can i pass userId and RoleId on button click to ActionResult Method.
ActionResult Method in UserRolesController:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult AddRole(UserRole userRole, int roleId, int userId)
{
if (!ModelState.IsValid) return View(userRole);
var check = db.UserRoles.Any(x => x.RoleID == roleId && x.UserID == userId);
if (check)
ViewBag.ResultMessage = "This user already has the role specified !";
else
db.UserRoles.Add(userRole);
db.SaveChanges();
ViewBag.ResultMessage = "User added to the role succesfully !";
return RedirectToAction("Index");
}
View like this:
#model IEnumerable<MvcAppCRUD.user>
#{
ViewBag.title = "AssignRole";
}
<h2>Assign Role</h2>
#if (!Model.Any())
{
#Html.Label("No Roles assigned for this user")
}
else
{
<table>
<tr>
<th>
#Html.DisplayName("Email")
</th>
<th>
#Html.DisplayName("Role Name")
</th>
<th></th>
</tr>
#foreach (var item in Model)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.email)
</td>
<td>
#Html.DisplayFor(modelItem => item.RoleName)
</td>
<td>
#Html.ActionLink("Delete", "Delete", new {id = item.id})
</td>
</tr>
}
</table>
}
<hr />
<div class="display-label">
#Html.DisplayName("Add Role")
</div>
<div class="display-field">
#Html.DropDownList("Roles", (SelectList) ViewBag.Roles)
</div>
#using (Html.BeginForm("AddRole", "UserRoles"))
{
<div class="message-success">#ViewBag.ResultMessage</div>
}
<p>
<input type="submit" value="Assign" />
</p>
<p>
#Html.ActionLink("Back to List", "Index")
</p>
Model Entities:
public partial class UserRole
{
public int ID { get; set; }
public int UserID { get; set; }
public int RoleID { get; set; }
public int Status { get; set; }
public virtual user Users { get; set; }
public virtual Role Roles { get; set; }
}
public partial class user
{
public user()
{
Roles = new List<SelectListItem>();
}
public long id { get; set; }
public string email { get; set; }
public string password { get; set; }
public System.DateTime reg_date { get; set; }
public byte validated { get; set; }
public virtual ICollection<UserRole> UserRoles { get; set; }
public int RoleId { get; set; }
public string RoleName { get; set; }
public IEnumerable<SelectListItem> Roles { get; set; }
//public IEnumerable<Role> Roles { get; set; }
}
public partial class Role
{
public int ID { get; set; }
public string RoleName { get; set; }
public string Desc { get; set; }
public int Status { get; set; }
public virtual ICollection<UserRole> UserRoles { get; set; }
}
On Button click nothing happens. Is is possible to pass the values as parameters from one model view to another?
There are numerous problems with you code. In particular your passing IEnumerable<user> to the model not including or rendering any controls in your form so nothing posts back, and in any case you cant post back UserRole because its a complex object and a dropdownlist only returns a single value. And there is no point displaying all roles in the dropdown, then checking if its already been selected on postback - just include only those roles that the user does not already have when you create the view. And assigning a message to ViewBag and then redirecting is pointless - its immediately lost.
Create a view model to represent what you want to display and edit (note I have excluded properties for displaying existing roles)
public class UserRoleVM
{
public int ID { get; set; } // user ID for post back
public int Name { get; set; } // user name for display in the view
[Display(Name="Select new role")]
public int SelectedRole { get; set; }
public SelectList RoleList { get; set; }
}
Controller
public ActionResult AddRole(int ID)
{
UserRoleVM model = new UserRoleVM();
var user = // Get the user based on the ID
model.ID = ID;
model.Name = user.??
var roles = // Get all roles and remove those that the user already has
model.RoleList = new SelectList(roles, "ID", "RoleName");
return View(model);
}
View
#model UserRoleVM
#using(Html.BeginForm())
{
<h2>#Model.Name</h2> // users name
#Html.LabelFor(m => m.SelectedRole)
#Html.DropDownListFor(m => m.SelectedRole, Model.RoleList)
<input type="submit" value="Add Role" />
}
Post method
[HttpPost]
public ActionResult AddRole(UserRoleVM model)
{
// model is now populated with the ID of the user and the ID of the selected role
// save and redirect
}