I am a beginner ASP.net, I want to display all comment in post. Detail() method in PostController only shows post content, I don't know how to handle Detail() method to show commnent in post.
Hope someone can help me, Thank so much.
I have two model
public class Post
{
public int PostID { get; set; }
public string Title{ get; set; }
public string Content{ get; set; }
}
public class Comment
{
public int CommentID { get; set; }
public int PostID { get; set; }
public string Content { get; set; }
}
and this is Post Controller
public class PostController : Controller
{
//////
public ViewResult Detail(int id )
{
Post viewpost= (repository.Posts.Single(p => p.PostID == id));
return View(viewpost);
}
////////////////
}
Moralus has a good answer. If you don't want to change your Post model to have a navigation property to its comments, if out of your control or something, then you would have to make something like a ViewPostModel
public class ViewPostModel
{
public Post Post { get; set; }
public List<Comment> Comments { get; set; }
}
Then, you would have to query your repository for both lists:
public class PostController : Controller
{
public ViewResult Detail(int id )
{
ViewPostModel viewpost = new ViewPostModel();
viewPost.Post = repository.Posts.Single(p => p.PostID == id);
viewPost.Comments = repository.Comments.Where(c=> c.PostID = id).ToList();
return View(viewpost);
}
}
So first of all you should have a navigation property from post to it's comment.
something like this:
public class Post
{
public int PostID { get; set; }
public string Title{ get; set; }
public string Content{ get; set; }
public virtual IEnummrable<Comment> Comments {get; set;}
}
Note that if your using DB-First to do that you should edit EDMX
Then just take the comments from the repository like that:
public ViewResult Detail(int id )
{
Post viewpost= (repository.Posts.Include("Comments").Single(p => p.PostID == id));
return View(viewpost);
}
And in your view iterate trought your comments and display them like that
#foreach (var comment in Model.Comments)
{
#comment.Content
}
EDIT:
All of the above is relevant only if you use EF\ Linq-2-SQL (thanks #tostringtheory)
Also you can use the ViewData put the comments to the View.
eg:
Action:
ViewData["Comments"] = repository.Comments.Where(c=> c.PostID = id).ToList();
View:
var comments =ViewData["Comments"] as List;
...and you can use the var-comments
Related
I'm using ASP.NET MVC to build an application for Forums. I have an entity named Posts and an entity named PostReplies.
On a particular Post, there will be a list of replies which are linked by a FK Post_Id in my PostReplies entity.
When I delete a reply on a post and call:
RedirectToAction(GetPost, Post, new { id = post.id});
(gets the individual post, with list of replies on it)
I get an error relating to this bit of code:
var replies = post.Replies;
(the post, always returns null)
I'm not sure why this is, it always redirects fine when I add a reply and then redirect back to the post.
I feel like I'm doing something fundamentally wrong when I'm calling delete method. I'll expand the logic I have below:
Post entity:
public class Post
{
public int Id { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public DateTime Created { get; set; }
public virtual Discussion Discussion { get; set; }
public virtual ICollection<PostReply> Replies { get; set; }
public virtual ApplicationUser ApplicationUser { get; set; }
}
PostReply entity:
public class PostReply
{
public int Id { get; set; }
public string Content { get; set; }
public DateTime Created { get; set; }
public virtual Post Post { get; set; }
public virtual ApplicationUser ApplicationUser { get; set; }
}
ReplyController - delete a reply:
[HttpGet]
public ActionResult DeleteReply(int id)
{
return View(_replyService.GetReply(id));
}
[HttpPost]
public ActionResult DeleteReply(int id, PostReply reply, Post posts)
{
var replies = _replyService.GetReply(id);
_replyService.DeleteReply(id, reply, posts);
return RedirectToAction("GetPost", "Post", new { id = posts.Id });
}
_replyService logic (called in the controller above):
public void DeleteReply(int id, PostReply reply, Post posts)
{
var replytoDelete = _context.Replies
.FirstOrDefault(r => r.Id == id);
if (replytoDelete != null)
{
_context.Replies.Remove(replytoDelete);
_context.SaveChanges();
}
}
PostController - get individual post:
public ActionResult GetPost(int id)
{
Post post = _postService.GetPost(id);
var replies = post.Replies;
var listofReplies = replies.Select(reply => new NewPostReplyModel
{
Id = reply.Id,
ReplyPosted = reply.Created,
ReplyContent = reply.Content,
ReplyUserId = reply.ApplicationUser.Id,
ReplyUserName = reply.ApplicationUser.UserName
});
var model = new GetPostViewModel
{
Replies = listofReplies,
Posts = BuildNewPost(post)
};
return View(model);
// return View("GetPost", post);
}
private NewPostModel BuildNewPost(Post post)
{
return new NewPostModel
{
PostId = post.Id,
PostContent = post.Content,
PostTitle = post.Title,
DatePosted = post.Created.ToString(),
DiscussionName = post.Discussion.Title,
DiscussionId = post.Discussion.Id,
UserId = post.ApplicationUser.Id,
UserName = post.ApplicationUser.UserName,
};
}
GetReply logic in service:
public PostReply GetReply(int id)
{
return _context.Replies.Find(id);
}
I think you are not including replies in your GetPost() methode,
So check if your code is like this:
public Post GetPost(int id)
{
return _context.Posts.Include(p=>p.Replies).FirstOrDefault(p=>p.Id == id);
}
As I'm developping an asp net core + ef core 2.0 with localized objects in my model, I adapted the solution provided in the following link to localize my objects link.
I'm now trying to find a clean way to update my collection of translation when updated object are received in the controller.
For the moment I have a step model class defined this way :
public class Step
{
//Native properties
public Guid ID { get; set; }
public string Name { get; set; }
public int Order { get; set; }
public string ScriptBlock { get; set; }
//Parent Step Navigation property
public Nullable<Guid> ParentStepID { get; set; }
public virtual Step ParentStep { get; set; }
//Collection of sub steps
public virtual ICollection<Step> SubSteps { get; set; }
//MUI Properties
public TranslationCollection<StepTranslation> Translations { get; set; }
public string Description { get; set; }
//{
// get { return Translations[CultureInfo.CurrentCulture].Description; }
// set { Translations[CultureInfo.CurrentCulture].Description = value; }
//}
public Step()
{
//ID = Guid.NewGuid();
Translations = new TranslationCollection<StepTranslation>();
}
}
public class StepTranslation : Translation<StepTranslation>
{
public Guid StepTranslationId { get; set; }
public string Description { get; set; }
public StepTranslation()
{
StepTranslationId = Guid.NewGuid();
}
}
Translation and translationCollection are the same as in the link
public class TranslationCollection<T> : Collection<T> where T : Translation<T>, new()
{
public T this[CultureInfo culture]
{
// indexer
}
public T this[string culture]
{
//indexer
}
public bool HasCulture(string culture)
{
return this.Any(x => x.CultureName == culture);
}
public bool HasCulture(CultureInfo culture)
{
return this.Any(x => x.CultureName == culture.Name);
}
}
public abstract class Translation<T> where T : Translation<T>, new()
{
public Guid Id { get; set; }
public string CultureName { get; set; }
protected Translation()
{
Id = Guid.NewGuid();
}
public bool HasProperty(string name)
{
return this.GetType()
.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Any(p => p.Name == name);
}
}
My issue in this sample is how to deal correctly with the PUT method and the Description property of my step controller. When it receive a Step object to update (which is done through a native c# client) only the string Description property of Step might have been created/updated/unchanged. So I have to update/create/do Nothing on the Description of the translation in the correct culture.
My first guess is to add in the TranslationCollection class a method in which I could pass the culture, the name of the property to update or not (Description in this case) and the value of the Description.
But as the TranslationCollection is a collection of abstract objects I don't even if this is a good idea and if it's possible.
If someone would have any advice on it (hoping I was clear enough) it would be great !
Finally answered my own question, and it was quite simple.
Just had to use the indexer like :
myobject.Translations[userLang].Name = value;
I was practicing User.Identity and timestamps functions in ASP.NET MVC 5,
So I created a student class filled some properties, I just wanted to test if it is capturing timestamps and userId, so user id is getting captured and datetime too, problem is whenever I'm editing a record and save it, its created date becomes Null and modified date is updated, please review the code and help.
Thanks in advance.
Below is the Code
{
public class BaseEntity
{
public DateTime? DateCreated { get; set; }
public string UserCreated { get; set; }
public DateTime? DateModified { get; set; }
public string UserModified { get; set; }
}
public class Student : BaseEntity
{
public int Id { get; set; }
public string Name { get; set; }
public string Subject { get; set; }
public string Class { get; set; }
public Section Section { get; set; }
public byte SectionId { get; set; }
}
then I used Codefirst approach and created an application Database and added this code in Identity Model
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public DbSet<Student> Students { get; set; }
public override int SaveChanges()
{
AddTimestamps();
return base.SaveChanges();
}
//public override async Task<int> SaveChangesAsync()
//{
// AddTimestamps();
// return await base.SaveChangesAsync();
//}
private void AddTimestamps()
{
var entities = ChangeTracker.Entries().Where(x => x.Entity is BaseEntity && (x.State == EntityState.Added || x.State == EntityState.Modified));
var currentUsername = !string.IsNullOrEmpty(System.Web.HttpContext.Current?.User?.Identity?.Name)
? HttpContext.Current.User.Identity.Name
: "Anonymous";
foreach (var entity in entities)
{
if (entity.State == EntityState.Added)
{
((BaseEntity)entity.Entity).DateCreated = DateTime.UtcNow;
((BaseEntity)entity.Entity).UserCreated = currentUsername;
}
else
((BaseEntity)entity.Entity).DateModified = DateTime.UtcNow;
((BaseEntity)entity.Entity).UserModified = currentUsername;
}
}
public DbSet<Section> Sections { get; set; }
public ApplicationDbContext()
: base("DefaultConnection", throwIfV1Schema: false)
{
}
public static ApplicationDbContext Create()
{
return new ApplicationDbContext();
}
}
I have created a simple controller with create,edit and dispay actions.
The code you posted doesn't show DateCreated being set to null as far as I can see. I think the issue is when you save an existing record you do not have the DateCreated or UserCreated fields in your view. So when you post the form the MVC model binder doesn't see them and thus sets them to null (I'm assuming your are binding to the Student model in your controller action).
In your edit view add the following hidden fields:
#Html.HiddenFor(model => model.DateCreated)
#Html.HiddenFor(model => model.UserCreated)
Now when you post the form the MVC model binder will bind these values to your model and save them to the database.
Using Web API and OData, I have a service which exposes Data Transfer Objects instead of the Entity Framework entities.
I use AutoMapper to transform the EF Entities into their DTO counter parts using ProjectTo():
public class SalesOrdersController : ODataController
{
private DbContext _DbContext;
public SalesOrdersController(DbContext context)
{
_DbContext = context;
}
[EnableQuery]
public IQueryable<SalesOrderDto> Get(ODataQueryOptions<SalesOrderDto> queryOptions)
{
return _DbContext.SalesOrders.ProjectTo<SalesOrderDto>(AutoMapperConfig.Config);
}
[EnableQuery]
public IQueryable<SalesOrderDto> Get([FromODataUri] string key, ODataQueryOptions<SalesOrderDto> queryOptions)
{
return _DbContext.SalesOrders.Where(so => so.SalesOrderNumber == key)
.ProjectTo<SalesOrderDto>(AutoMapperConfig.Config);
}
}
AutoMapper (V4.2.1) is configured as follows, note the ExplicitExpansion() which prevents serialisation auto expanding navigation properties when they are not requested:
cfg.CreateMap<SalesOrderHeader, SalesOrderDto>()
.ForMember(dest => dest.SalesOrderLines, opt => opt.ExplicitExpansion());
cfg.CreateMap<SalesOrderLine, SalesOrderLineDto>()
.ForMember(dest => dest.MasterStockRecord, opt => opt.ExplicitExpansion())
.ForMember(dest => dest.SalesOrderHeader, opt => opt.ExplicitExpansion());
ExplicitExpansion() then creates a new problem where the following request throws an error:
/odatademo/SalesOrders('123456')?$expand=SalesOrderLines
The query specified in the URI is not valid. The specified type member 'SalesOrderLines' is not supported in LINQ to Entities
The navigation property SalesOrderLines is unknown to EF so this error is pretty much what I expected to happen. The question is, how do I handle this type of request?
The ProjectTo() method does have an overload that allows me to pass in an array of properties that require expansion, I found & modified the extension method ToNavigationPropertyArray to try and parse the request into a string array:
[EnableQuery]
public IQueryable<SalesOrderDto> Get([FromODataUri] string key, ODataQueryOptions<SalesOrderDto> queryOptions)
{
return _DbContext.SalesOrders.Where(so => so.SalesOrderNumber == key)
.ProjectTo<SalesOrderDto>(AutoMapperConfig.Config, null, queryOptions.ToNavigationPropertyArray());
}
public static string[] ToNavigationPropertyArray(this ODataQueryOptions source)
{
if (source == null) { return new string[]{}; }
var expandProperties = string.IsNullOrWhiteSpace(source.SelectExpand?.RawExpand) ? new List<string>().ToArray() : source.SelectExpand.RawExpand.Split(',');
for (var expandIndex = 0; expandIndex < expandProperties.Length; expandIndex++)
{
// Need to transform the odata syntax for expanding properties to something EF will understand:
// OData may pass something in this form: "SalesOrderLines($expand=MasterStockRecord)";
// But EF wants it like this: "SalesOrderLines.MasterStockRecord";
expandProperties[expandIndex] = expandProperties[expandIndex].Replace(" ", "");
expandProperties[expandIndex] = expandProperties[expandIndex].Replace("($expand=", ".");
expandProperties[expandIndex] = expandProperties[expandIndex].Replace(")", "");
}
var selectProperties = source.SelectExpand == null || string.IsNullOrWhiteSpace(source.SelectExpand.RawSelect) ? new List<string>().ToArray() : source.SelectExpand.RawSelect.Split(',');
//Now do the same for Select (incomplete)
var propertiesToExpand = expandProperties.Union(selectProperties).ToArray();
return propertiesToExpand;
}
This works for expand, so now I can handle a request like the following:
/odatademo/SalesOrders('123456')?$expand=SalesOrderLines
or a more complicated request like:
/odatademo/SalesOrders('123456')?$expand=SalesOrderLines($expand=MasterStockRecord)
However, more complicated request that try to combine $select with $expand will fail:
/odatademo/SalesOrders('123456')?$expand=SalesOrderLines($select=OrderQuantity)
Sequence contains no elements
So, the question is: am I approaching this the right way?
It feels very smelly that I would have to write something to parse and transform the ODataQueryOptions into something EF can understand.
It seems this is a rather popular topic:
odata-expand-dtos-and-entity-framework
how-to-specify-the-shape-of-results-with-webapi2-odata-with-expand
web-api-queryable-how-to-apply-automapper
how-do-i-map-an-odata-query-against-a-dto-to-another-entity
While most of these suggest using ProjectTo, none seem to address serialisation auto expanding properties, or how to handle expansion if ExplictExpansion has been configured.
Classes and Config below:
Entity Framework (V6.1.3) entities:
public class SalesOrderHeader
{
public string SalesOrderNumber { get; set; }
public string Alpha { get; set; }
public string Customer { get; set; }
public string Status { get; set; }
public virtual ICollection<SalesOrderLine> SalesOrderLines { get; set; }
}
public class SalesOrderLine
{
public string SalesOrderNumber { get; set; }
public string OrderLineNumber { get; set; }
public string Product { get; set; }
public string Description { get; set; }
public decimal OrderQuantity { get; set; }
public virtual SalesOrderHeader SalesOrderHeader { get; set; }
public virtual MasterStockRecord MasterStockRecord { get; set; }
}
public class MasterStockRecord
{
public string ProductCode { get; set; }
public string Description { get; set; }
public decimal Quantity { get; set; }
}
OData (V6.13.0) Data Transfer Objects:
public class SalesOrderDto
{
[Key]
public string SalesOrderNumber { get; set; }
public string Customer { get; set; }
public string Status { get; set; }
public virtual ICollection<SalesOrderLineDto> SalesOrderLines { get; set; }
}
public class SalesOrderLineDto
{
[Key]
[ForeignKey("SalesOrderHeader")]
public string SalesOrderNumber { get; set; }
[Key]
public string OrderLineNumber { get; set; }
public string LineType { get; set; }
public string Product { get; set; }
public string Description { get; set; }
public decimal OrderQuantity { get; set; }
public virtual SalesOrderDto SalesOrderHeader { get; set; }
public virtual StockDto MasterStockRecord { get; set; }
}
public class StockDto
{
[Key]
public string StockCode { get; set; }
public string Description { get; set; }
public decimal Quantity { get; set; }
}
OData Config:
var builder = new ODataConventionModelBuilder();
builder.EntitySet<StockDto>("Stock");
builder.EntitySet<SalesOrderDto>("SalesOrders");
builder.EntitySet<SalesOrderLineDto>("SalesOrderLines");
I have created an Automapper explicit navigation expansion utility function that should work with N-deph expands. Posting it here since it might help someone.
public List<string> ProcessExpands(IEnumerable<SelectItem> items, string parentNavPath="")
{
var expandedPropsList = new List<String>();
if (items == null) return expandedPropsList;
foreach (var selectItem in items)
{
if (selectItem is ExpandedNavigationSelectItem)
{
var expandItem = selectItem as ExpandedNavigationSelectItem;
var navProperty = expandItem.PathToNavigationProperty?.FirstSegment?.Identifier;
expandedPropsList.Add($"{parentNavPath}{navProperty}");
//go recursively to subproperties
var subExpandList = ProcessExpands(expandItem?.SelectAndExpand?.SelectedItems, $"{parentNavPath}{navProperty}.");
expandedPropsList = expandedPropsList.Concat(subExpandList).ToList();
}
}
return expandedPropsList;
}
You can call it with :
var navExp = ProcessExpands(options?.SelectExpand?.SelectExpandClause?.SelectedItems)
it will return a list with ["Parent" ,"Parent.Child"]
I never really managed to work this one out. The ToNavigationPropertyArray() extension method helps a little, but does not handle infinite depth navigation.
The real solution is to create Actions or Functions to allow clients to request data requiring a more complicated query.
The other alternative is to make multiple smaller/simple calls then aggregate the data on the client, but this isn't really ideal.
When you want to mark something for explicit expansion in AutoMapper, you need to also opt-back-in when calling ProjectTo<>().
// map
cfg.CreateMap<SalesOrderHeader, SalesOrderDto>()
.ForMember(dest => dest.SalesOrderLines, opt => opt.ExplicitExpansion());
// updated controller
[EnableQuery]
public IQueryable<SalesOrderDto> Get()
{
return _dbContext.SalesOrders
.ProjectTo<SalesOrderDto>(
AutoMapperConfig.Config,
so => so.SalesOrderLines,
// ... additional opt-ins
);
}
While the AutoMapper wiki does state this, the example is perhaps a little misleading by not including the paired ExplicitExpansion() call.
To control which members are expanded during projection, set ExplicitExpansion in the configuration and then pass in the members you want to explicitly expand:
I am trying to understand why my Html.ListBoxFor() is not highlighting current selected items when the view loads.
I have a database model:
public class Issue
{
[Key]
public int IssueId { get; set; }
public int Number { get; set; }
public string Title { get; set; }
public DateTime Date { get; set; }
public virtual ICollection<Creator> Creators { get; set; }
}
public class Creator
{
[Key]
public int CreatorId { get; set; }
public string FirstName { get; set; }
public string MiddleName { get; set; }
public string LastName { get; set; }
public virtual ICollection<Issue> Issues { get; set; }
}
public class Icbd : DbContext
{
public DbSet<Issue> Issues { get; set; }
public DbSet<Creator> Creators { get; set; }
}
I then have an editing model:
public class IssueEditModel
{
public Issue Issue { get; set; }
public IEnumerable<Creator> Creators { get; set; }
public IEnumerable<Creator> SelectedCreators { get {return Issue.Creators;} }
}
Then, in my controller I populate IssueEditModel:
public ActionResult EditIssue( int id = 0 )
{
IssueEditModel issueEdit = new IssueEditModel{
Creators = db.Creators.ToList(),
Issue = new Issue{ Creators = new List<Creator>()},
};
if (id > 0)
{
issueEdit.Issue = db.Issues.Include("Creators").Where(x => x.IssueId == id).Single();
}
return View(issueEdit);
}
This populates all objects correctly (as far as I can tell, anyway.) In my View, I am writing a listbox like this:
<%: Html.ListBoxFor(
x => x.SelectedCreators,
new SelectList(
Model.Creators,
"CreatorId",
"LastName"
)
)%>
This lists all the options correctly, but I cannot get the currently select items to highlight. I almost want to write my own Html Helper because this is such a simple operation, I don't understand why this is being so difficult.
Why wont the Html Helper highlight the current items?
You need a list of scalar types as first argument to the ListBoxFor helper which will map to the creator ids that you want preselected:
public class IssueEditModel
{
public IEnumerable<Creator> Creators { get; set; }
public IEnumerable<int> SelectedCreatorIds { get; set; }
}
and then:
IssueEditModel issueEdit = new IssueEditModel
{
Creators = db.Creators,
SelectedCreatorIds = db.Creators.Select(x => x.CreatorId)
};
and in the view:
<%: Html.ListBoxFor(
x => x.SelectedCreatorIds,
new SelectList(
Model.Creators,
"CreatorId",
"LastName"
)
) %>
The example submitted by Darin is ALMOST correct, but there is a slight error. Presently, his example will preselect ALL creators! However, the desired result is to only preselect the creators associated with a particular instance of Issue.
So this:
IssueEditModel issueEdit = new IssueEditModel
{
Creators = db.Creators,
SelectedCreatorIds = db.Creators.Select(x => x.CreatorId)
};
Should be this:
IssueEditModel issueEdit = new IssueEditModel
{
Creators = db.Creators,
SelectedCreatorIds = CurrentIssue.Creators.Select(x => x.CreatorId)
};
Where CurrentIssue is an instantiation of the Issue class (presumably previously populated from the datastore).