I'm currently facing a problem with a NullException. I'm getting the error at #comment.ApplicationUser.UserName down below, but only if I am accesing a comment which was posted by another user rather than the one who uploaded the picture. If I comment to the same picture the user(me) posted it shows the UserName correctly. I don't really know what could be the problem, since if I call #comment.ApplicationUserId everything works as it should displaying the id of the user who posted the image. Shouldn't it be linked automatically to the ApplicationUser object when I call #comment.ApplicationUser.UserName ?
The view where I'm getting the error
#foreach (var comment in Model.Comments)
{
<dd class="font-italic">
#comment.Body commented by #comment.ApplicationUser.UserName
</dd>
}
The Comment Model
public class Comment
{
public int Id { get; set; }
public string Body { get; set; }
public bool ApprovedByUser { get; set; }
public ApplicationUser ApplicationUser { get; set; }
public string ApplicationUserId { get; set; }
public Photo Photo { get; set; }
public int PhotoId { get; set; }
}
Every-time I create a new comment into the CommentsController/Create I do this
public ActionResult Create([Bind(Include = "Id,Body,ApprovedByUser,ApplicationUserId,PhotoId")] Comment comment, int id)
{
var userManager = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(db));
var currentUser = userManager.FindById(User.Identity.GetUserId());
comment.PhotoId = id;
comment.ApplicationUserId = currentUser.Id;
if (ModelState.IsValid)
{
db.Comments.Add(comment);
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.PhotoId = new SelectList(db.Photos, "Id", "Title", comment.PhotoId);
return View(comment);
}
I also have a custom PhotoCommentsViewModel containing the photo and the comments for that photo.
public class PhotoCommentsViewModel
{
public Photo Photo { get; set; }
public IEnumerable<Comment> Comments { get; set; }
}
I get the abobe viewModel from Photo/Details/{id} controller here
public ActionResult Details(int? id)
{
var photo = db.Photos.Find(id);
var comments = db.Comments.Where(s => s.PhotoId == photo.Id);
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
var viewModel = new PhotoUserViewModel
{
Photo = photo,
Comments = comments,
};
if (viewModel.Photo == null)
{
return HttpNotFound();
}
return View(viewModel);
}
You should use eager loading in this case.
var comments = db.Comments
.Where(s => s.PhotoId == photo.Id)
.Include(x =>x.ApplicationUser).ToList();
see this
In the Details action, ApplicationUser needs to be eagerly loaded with the comments.
var comments = db.Comments.Where(s => s.PhotoId == photo.Id).Include(x => x.ApplicationUser).ToList();
Further reading on Loading Related Entities
Related
Kindly tell me where I am doing a mistake I have seen StackOverflow same mistake but I am not finding my error after searchIng a lot.
This line Creates Error After Debugging
#Model.Product.category.Name
This is my front end
<div class="product-categories">
<span>Categories: </span>#Model.Product.category.Name
</div>
my View model looks like
public class ProductViewModel
{
public Product Product { get; set; }
}
This is my Entities
public class Product:BaseEntity
{
public decimal price { get; set; }
//public int CategoryID { get; set; }
public string ImageURL { get; set; }
public virtual Category category { get; set; }
}
```[Ent][3]
[Front End where an error has been raised][1]
[My Entities image for your better understanding][2]
[1]: https://i.stack.imgur.com/sHRtx.png
[2]: https://i.stack.imgur.com/CVsQQ.png
[3]: https://i.stack.imgur.com/WXOa0.png
This is my Product Service where I get Products and these lines of code are showing an error
Before Code
public Product GetProduct(int ID)
{
using (var context = new TablesContext())
{
return context.Products.Find(ID);
}
}
After Same code updated
public Product GetProduct(int ID)
{
using (var context = new TablesContext())
{
return context.Products.Where(x => x.id == ID).Include(x => x.category).FirstOrDefault();
}
}
this line of code updated from this return context.Products.Find(ID);
to this
return context.Products.Where(x => x.id == ID).Include(x => x.category).FirstOrDefault();
I have these two models:
public class Film
{
public long Id { get; set; }
[Required]
public string Title { get; set; }
[Required]
public string Director { get; set; }
public string Synopsis { get; set; }
public int? Release { get; set; }
[Required]
public Genre Genre { get; set; }
}
public class Genre
{
public long Id { get; set; }
public string Description { get; set; }
}
And I want to be able to update a Film's Genre through a PUT method. I am currently trying this, but I get the following error:
[HttpPut]
public async Task<IActionResult> UpdateFilm(Film film)
{
var existingFilm = await _context.Films
.Include(f => f.Genre)
.FirstOrDefaultAsync(f => f.Id == film.Id);
if (existingFilm == null)
{
return NotFound(new JsonResponse { Success = false, Message = "Impossible to update, film was not found", Data = null });
}
existingFilm.Title = film.Title;
existingFilm.Synopsis = film.Synopsis;
existingFilm.Release = film.Release;
existingFilm.Director = film.Director;
if (existingFilm.Genre.Id != film.Genre.Id)
{
existingFilm.Genre.Id = film.Genre.Id;
existingFilm.Genre.Description = film.Genre.Description;
//_context.Entry(existingFilm.Genre).State = EntityState.Modified;
_context.Entry(existingFilm).CurrentValues.SetValues(film);
}
_context.Films.Update(existingFilm);
try
{
await _context.SaveChangesAsync();
}
catch (Exception e)
{
return BadRequest(new JsonResponse { Success = false, Message = e.Message, Data = null });
}
return Ok(new JsonResponse { Success = true, Message = "Film updated with success", Data = film });
}
The error message is:
System.InvalidOperationException: The property 'Id' on entity type 'Genre' is part of a key and so cannot be modified or marked as modified. To change the principal of an existing entity with an identifying foreign key first delete the dependent and invoke 'SaveChanges' then associate the dependent with the new principal.
Anyone able to help? Thanks a lot.
According to the error, its existingFilm.Genre.Id that you cannot update the id, if its not equal to the id.
My suggestion would be ignore the id update, but if it is necessary:
if (existingFilm.Genre.Id != film.Genre.Id)
{
var genre = _context.Genre.FirstOrDefault(x => x.Id == film.Genre.Id);
// Update the fields needed except the id
existingFilm.Genre = genre;
}
I have a Razor pages project that is trying to populate a Kendo TreeView (or any other TreeView) from a Database created with a Data Model.
The page I am working on contains apps that are in use, and the tree is reading a self referencing Organisations list so that we know what apps each organisation or department etc has access to.
I'm working on the Edit page in a razor app, so ~Pages\Apps\Edit.cshtml and ~Pages\Apps\Edit.cshtml.cs, with associated model pages as shown below.
These are the models involved, ignore RoleApps for this issue:
namespace FliveRetry.Models
{
public class Org
{
public int ID { get; set; }
public string OrgName { get; set; }
public int? ParentID { get; set; }
public bool? HasChildren { get; set; }
}
}
And
namespace FliveRetry.Models
{
public class App
{
public int ID { get; set; }
public string AppName { get; set; }
public string AppDescription { get; set; }
public int? DisplayOrder { get; set; }
public bool? Archived { get; set; }
public DateTime? Saved { get; set; }
public int? SavedBy { get; set; }
public ICollection<OrgAppJoin> OrgAppJoins { get; set; }
public ICollection<RoleAppJoin> RoleAppJoins { get; set; }
}
}
and the index model:
{
public class AppIndexData
{
public IEnumerable<App> Apps { get; set; }
public IEnumerable<Role> Roles { get; set; }
public IEnumerable<Org> Orgs { get; set; }
public IEnumerable<RoleAppJoin> RoleAppJoins { get; set; }
public IEnumerable<OrgAppJoin> OrgAppJoins { get; set; }
}
public class AssignedAppOrgData
{
public int OrgID { get; set; }
public string Org { get; set; }
public int? ParentID { get; set; }
public bool Assigned { get; set; }
public bool? HasChildren { get; set; }
}
public class SelectedAppOrgs
{
public int OrgID { get; set; }
}
public class SelectedAppOrgNames
{
public string OrgName { get; set; }
}
I have a page model to populate selected items into lists called selectedOrgs or selectedOrgNames that I can use in the view.
public class AppSelectPageModel : PageModel
{
//Load list for Selecting Orgs for Apps
public List<AssignedAppOrgData> AssignedAppOrgDataList;
public List<SelectedAppOrgs> selectedOrgs;
public List<SelectedAppOrgNames> selectedOrgNames;
public void PopulateAssignedAppOrgData(FliveRetryContext context, App app)
{
var allOrgs = context.Org;
var appOrgs = new HashSet<int>(
app.OrgAppJoins.Select(c => c.OrgID));
AssignedAppOrgDataList = new List<AssignedAppOrgData>();
selectedOrgs = new List<SelectedAppOrgs>();
selectedOrgNames = new List<SelectedAppOrgNames>();
foreach (var org in allOrgs)
{
AssignedAppOrgDataList.Add(new AssignedAppOrgData
{
OrgID = org.ID,
Org = org.OrgName,
Assigned = appOrgs.Contains(org.ID)
});
if (appOrgs.Contains(org.ID))
{
selectedOrgs.Add(new SelectedAppOrgs
{
OrgID = org.ID
});
selectedOrgNames.Add(new SelectedAppOrgNames
{
OrgName = org.OrgName
});
}
}
}
public void UpdateAppOrgs(FliveRetryContext context,
string[] selectedOrgs, App appToUpdate)
{
if (selectedOrgs == null)
{
appToUpdate.OrgAppJoins = new List<OrgAppJoin>();
return;
}
var selectedOrgsHS = new HashSet<string>(selectedOrgs);
var appOrgs = new HashSet<int>
(appToUpdate.OrgAppJoins.Select(c => c.Org.ID));
foreach (var org in context.Org)
{
if (selectedOrgsHS.Contains(org.OrgName.ToString()))
{
if (!appOrgs.Contains(org.ID))
{
appToUpdate.OrgAppJoins.Add(
new OrgAppJoin
{
AppID = appToUpdate.ID,
OrgID = org.ID
});
}
}
else
{
if (appOrgs.Contains(org.ID))
{
OrgAppJoin orgToRemove
= appToUpdate
.OrgAppJoins
.SingleOrDefault(i => i.OrgID == org.ID);
context.Remove(orgToRemove);
}
}
}
}
I then process them in OnGetAsync in Edit.cshtml.cs:
public async Task<IActionResult> OnGetAsync(int? id)
{
this.TreeData = GetOrgTreeData();
if (id == null)
{
return NotFound();
}
App = await _context.App
.Include(i => i.OrgAppJoins).ThenInclude(i => i.Org)
.Include(i => i.RoleAppJoins).ThenInclude(i => i.Role)
.AsNoTracking()
.FirstOrDefaultAsync(m => m.ID == id);
if (App == null)
{
return NotFound();
}
PopulateAssignedAppRoleData(_context, App);
PopulateAssignedAppOrgData(_context, App);
return Page();
}
and OnPostAsync
public async Task<IActionResult> OnPostAsync(int? id, string[] selectedOrgs, string[] selectedRoles)
{
if (!ModelState.IsValid)
{
return Page();
}
var appToUpdate = await _context.App
.Include(i => i.OrgAppJoins).ThenInclude(i => i.Org)
.Include(i => i.RoleAppJoins).ThenInclude(i => i.Role)
.FirstOrDefaultAsync(s => s.ID == id);
if (await TryUpdateModelAsync<App>(
appToUpdate,
"app", // Prefix for form value.
c => c.AppName, c => c.AppDescription, c => c.DisplayOrder))
{
UpdateAppOrgs(_context, selectedOrgs, appToUpdate);
UpdateAppRoles(_context, selectedRoles, appToUpdate);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
UpdateAppOrgs(_context, selectedOrgs, appToUpdate);
UpdateAppRoles(_context, selectedRoles, appToUpdate);
PopulateAssignedAppOrgData(_context, App);
PopulateAssignedAppRoleData(_context, App);
return Page();
}
This works fine when using multiselect dropdowns and reads and writes correctly to the many to many join tables.
I am using kendo controls at the moment, but I'm happy to use generic if I can find a solution to my problem.
I need to have a treeview for the Org model to display as a multi select, so I have two examples I am trying to get working, that behave differently, the DropDownTreeview is not essential but in some pages it will be handy, the TreeView is essential.
The first is the TreeView:
#(Html.Kendo().TreeView()
.Name("selectedOrgNames")
.DataTextField("OrgName")
.Checkboxes(checkboxes => checkboxes
.Name("ischecked")
.CheckChildren(true))
.HtmlAttributes(new { style = "width:100%" })
.DataSource(d => d
.Read(read =>
read.Url("/Apps/Edit?handler=Read")
)
)
)
The second is the DropDownTreeview:
#(Html.Kendo().DropDownTree()
.Placeholder("Select ...")
.Name("selectedOrgs")
.DataTextField("OrgName")
.DataValueField("ID")
.Checkboxes(checkboxes => checkboxes
.Name("ischecked")
.CheckChildren(true))
.AutoClose(false)
.Value(Model.selectedOrgNames)
.HtmlAttributes(new { style = "width:100%" })
.DataSource(d => d
.Read(read =>
read.Url("/Apps/Edit?handler=Read")
)
)
)
Both examples read the datasource from here in edit.cshtml.cs:
public IActionResult OnGetRead(int? id)
{
var result = from e in _context.Org
where id.HasValue ? e.ParentID == id : e.ParentID == null
select new
{
id = e.ID,
hasChildren = (from q in _context.Org
where (q.ParentID == e.ID)
select q
).Count() > 0,
OrgName = e.OrgName,
selected = (from s in _context.OrgAppJoin
where (s.OrgID == e.ID) && (s.AppID == 2)// <--this works, this doesn't--> (s.AppID == app.ID)
select s
).Count() > 0,
ischecked = (from s in _context.OrgAppJoin
where (s.OrgID == e.ID) && (s.AppID == 2)// <--this doesn't work, this doesn't either-->
(s.AppID == app.ID)
select s
).Count() > 0
};
return new JsonResult(result);
}
My first issue is probably very simple, I'm new to this platform: I can't seem to find a way to get the value of the AppID from the page into the OnGetRead Module ( i have hard coded s.AppID == 2 as an test example to see if it works) I have tried all sorts of variables and other methods.
The id passed into OnPostAsync and OnGetAsync is the id of the App, but the id passed into the OnGetRead is the id of the Org, which is correct and works, but how do I use the AppID from the page to replace the number two in this line? where (s.OrgID == e.ID) && (s.AppID == 2)?
My second issue is getting the checkboxes to read and write.
The DropDownTree above writes to database correctly, but doesn't read and populate checkboxes.
The Treeview doesn't populate checkboxes or write to the database, however it DOES read the selected value from the join table for app number 2 (or any other number I manually insert) in OnGetRead, and displays different font colours etc for the correct items as selected, but not as checked (aschecked may not even be a valid call theere, but I can't find a reference for that).
I feel like I'm close but I have tried for so long to get this to work to no avail, including to and fro with telerik who have tried help but they don't really help much with modelling to your own code, just with static data, and they seem to be light on for a Razor app knowledgebase and keep giving examples of controllers with hard coded data.
Please advise if I need to split this into two questions or more but any help will be much appreciated
Thanks
The background is this. I have a controller that shows the details of an story object (which you can think of as a blog post).
I would like anyone to be able to read the details of a story without being logged in to the application. However, in my StoryDetailsViewModel I have an ApplicationUser field. I do this because if someone wants to comment on the story, I need to know who the author is and therefore, I want to force them to log-in to write a comment.
I have [AllowAnonymous] as an attribute on my controller action. When I try to get the identity of the user who's signed in, if the person is not signed in, the call returns null, sticks null in my ApplicationUser field in the viewmodel and consequently breaks the view. My controller action is below.
This whole thing is because in the view, I want this textarea if someone is logged in:
I don't know whether or not I should have some kind of boolean that if User.Identity.GetUserId() returns null I can act on that, or, as the controller action below has, try to create two separate viewmodels depending on if the user is anonymous or logged in.
Any thoughts on the best (most efficient) way to tackle this?
[HttpGet]
[AllowAnonymous]
public ActionResult Details(int id)
{
var FoundStory = _dbContext.Stories.SingleOrDefault(x => x.Id == id);
if (FoundStory == null)
{
return HttpNotFound();
}
//get the logged in userId
string signedInUserId = User.Identity.GetUserId();
//if the person reading the article isn't signed in, the userId will come back null
//need to create a viewmodel and view that doesn't have a signed in user associated with it
if (signedInUserId == null)
{
var viewModel = new StoryDetailsViewModelAnonymousUser
{
StoryId = FoundStory.Id,
AuthorId = FoundStory.AuthorId,
Story = FoundStory,
Comments = _dbContext.Comments.Where(x => x.StoryId == FoundStory.Id).ToList()
};
return View(viewModel);
} else
{
var viewModel = new StoryDetailsViewModelSignedInUser
{
StoryId = FoundStory.Id,
AuthorId = FoundStory.AuthorId,
Story = FoundStory,
User = _dbContext.Users.SingleOrDefault(x => x.Id == signedInUserId),
Comments = _dbContext.Comments.Where(x => x.StoryId == FoundStory.Id).ToList()
};
return View(viewModel);
}
}
My viewModel:
public class StoryDetailsViewModelSignedInUser
{
public ApplicationUser User { get; set; }
public int StoryId { get; set; }
public Story Story { get; set; }
public string AuthorId { get; set; }
[Required]
public string Content { get; set; }
public IEnumerable<Comment> Comments { get; set; }
}
In this case you probably don't need to use different views.
You can add a bool property such as IsAnonymous to StoryDetailsViewModelSignedInUser to indicate if a user is logged in or not, or check if property User is set (model.User != null). Finally, in your view, show or hide the Comments section/partial view using these properties.
ViewModel:
public class StoryDetailsViewModelSignedInUser
{
public bool IsAnonymous
{
get
{
return User != null;
}
}
public ApplicationUser User { get; set; }
// other properties here
}
Using a partial view for your comments section will probably make your life easier, given that all you have to do is to add an if statement checking if the user is logged in or not.
I'd also refactor the controller method in order to reduce code duplication. Instead of:
if (signedInUserId == null)
{
var viewModel = new StoryDetailsViewModelAnonymousUser
{
StoryId = FoundStory.Id,
AuthorId = FoundStory.AuthorId,
Story = FoundStory,
Comments = _dbContext.Comments.Where(x => x.StoryId == FoundStory.Id).ToList()
};
return View(viewModel);
} else
{
var viewModel = new StoryDetailsViewModelSignedInUser
{
StoryId = FoundStory.Id,
AuthorId = FoundStory.AuthorId,
Story = FoundStory,
User = _dbContext.Users.SingleOrDefault(x => x.Id == signedInUserId),
Comments = _dbContext.Comments.Where(x => x.StoryId == FoundStory.Id).ToList()
};
return View(viewModel);
}
You can do:
var viewModel = new StoryDetailsViewModelAnonymousUser
{
StoryId = FoundStory.Id,
AuthorId = FoundStory.AuthorId,
Story = FoundStory,
Comments = _dbContext.Comments.Where(x => x.StoryId == FoundStory.Id).ToList()
};
if (signedInUserId != null)
{
viewModel.User = _dbContext.Users.SingleOrDefault(x => x.Id == signedInUserId);
}
return View(viewModel);
I have a web project that everything is working and this below line works for other models except for this one. I'm just needing some info on where to start looking for the solution at.
When I debug it I see that it is getting all the new data that has been edited but it does not assign the new data to EditedtimeEntry. The EditedtimeEntry var has the old data not the new data that was edited. I looked at the timeEntry.Id and it has the new edit its just not being assigned to the EditedtimeEntry. There is no exception or build errors it just does not save the changes, and it looks like the reason it is not save the changes is because the EditedtimeEntry var is not getting the new data assigned to it for some reason. Can anyone point me in the right direction?
TimeEntry EditedtimeEntry = db.TimeEntries.Find(timeEntry.Id);
Here is the Full method with the problem:
public ActionResult Edit( [Bind(Include = "Id,Description,Rate,Paid,Tech,Company")] TimeEntry timeEntry)
{
if (ModelState.IsValid)
{
TimeEntry EditedtimeEntry = db.TimeEntries.Find(timeEntry.Id);
Technician tech = db.Technician.Single(m => m.PhoneNumber == timeEntry.Tech.PhoneNumber);
EditedtimeEntry.Tech = tech;
Company comp = db.Companies.Single(m => m.Name == timeEntry.Company.Name);
EditedtimeEntry.Company = comp;
db.Entry(EditedtimeEntry).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(timeEntry);
}
I have other methods for other models that are identical to this one and it works. Here is alsos the model
public class TimeEntry
{
[Key]
public Guid Id { get; set; }
public DateTime StartTime { get; set; }
public DateTime EndTime { get; set; }
public string Description { get; set; }
public Rate Rate { get; set; }
public Technician Tech { get; set; }
public bool Paid { get; set; }
public Company Company { get; set; }
}
public enum Rate { Desktop, Network, Remote, Phone }
Thanks =)
Basically, what you are doing is exactly the contrary of what you want to do :) when you recover the item from the database, you are simply getting the item unchanged that is still in database. What you want is update the database item and then save changes (with EntityState.Modified then SaveChanges())
You simply want to edit timeEntry so that all changes done in the UI are translated into DB :
public ActionResult Edit( [Bind(Include = "Id,Description,Rate,Paid,Tech,Company")] TimeEntry timeEntry)
{
if (ModelState.IsValid)
{
Technician tech = db.Technician.Single(m => m.PhoneNumber == timeEntry.Tech.PhoneNumber);
timeEntry.Tech = tech;
Company comp = db.Companies.Single(m => m.Name == timeEntry.Company.Name);
timeEntry.Company = comp;
db.Entry(timeEntry).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(timeEntry);
}