I have a Entity class which included an virtual list which is a link to another class:
public virtual Employee Employee1 { get; set; }
So in Edit page I have my item with label Employee1 when I wanted "My Employee" so in my DataAnnotation class I add
[DisplayName("My Employee")]
public virtual Employee Employee1 { get; set; }
But it is not working yet. Other items in DataAnnotation class work well.
It was not working because visual studio when create view automatically, for foreign keys use Labelfor instead of DisplaynameFor like below:
#Html.LabelFor(model => model.Employee1.LastName, "Employee1")
So read that value from second parameter instead of DataAnnotation so We need to change it to:
#Html.LabelFor(model => model.Employee1.LastName, "My Employee")
You need to give DataAnnotation in original class property. If you give DataAnnotation to virtual property, it doesnt work. I tested. But If you give DataAnnotation to property in original class. It works.
Such as:
public partial class Languages
{
DataAnnotation here
public string Code { get; set; }
public string Name { get; set; }
}
public partial class Categories
{
public string Code { get; set; }
public string FKLanguageCode { get; set; }
public string Name { get; set; }
public Nullable<int> Order { get; set; }
DataAnnotation Not Here
public virtual Languages Languages { get; set; }
}
Related
I am trying to create a discussion board using Identity where ApplicationUsers can create, save, hide, and comment on Posts. I was able to get Posts and Comments working without data annotations or overriding OnModelCreating as follows:
Post.cs:
public class Post
{
public int ID { get; set; }
public string Title { get; set; }
public string Content { get; set; }
[DataType(DataType.DateTime)]
public DateTime CreationDate { get; set; }
public ApplicationUser OriginalPoster { get; set; }
public int Upvotes { get; set; }
public int Downvotes { get; set; }
public int VoteScore { get; set; }
public ICollection<Comment> Comments { get; set; }
}
Comment.cs:
public class Comment
{
public int ID { get; set; }
public string Content { get; set; }
public ApplicationUser Commenter { get; set; }
[DataType(DataType.DateTime)]
public DateTime CreationDate { get; set; }
public int Upvotes { get; set; }
public int Downvotes { get; set; }
public int VoteScore { get; set; }
}
ApplicationDbContext.cs:
public class ApplicationDbContext : IdentityDbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
public DbSet<Post> Posts { get; set; }
public DbSet<Comment> Comments { get; set; }
}
But when I extend IdentityUser to add my own custom fields:
public class ApplicationUser : IdentityUser
{
public ICollection<Post> CreatedPosts { get; set; }
public ICollection<Post> SavedPosts { get; set; }
public ICollection<Post> HiddenPosts { get; set; }
}
Add-Migration returns with error:
"Unable to determine the relationship represented by navigation
'ApplicationUser.CreatedPosts' of type 'ICollection'. Either
manually configure the relationship, or ignore this property using the
'[NotMapped]' attribute or by using 'EntityTypeBuilder.Ignore' in
'OnModelCreating'."
Why is EF Core able to determine the relationship between a Post and its Comments but not an ApplicationUser and its created/saved/hidden Posts? I understand that I will have to specify the relationship either by using data annotations or overriding OnModelCreating but I am unsure of how to go about doing this. Any amount of help would be very much appreciated.
The reason is because you have multiple collection properties referencing the same model, Post. This type of situation you need to specifically tell EF Core which foreign properties each of CreatedPosts, HiddenPosts and SavedPosts to reference from Post. Given you only have one ApplicationUser foreign property named OriginalPoster, that would be impossible because there are no other properties HiddenPosts and SavedPosts would reference. You would only be able to reference one by configuring it like this.
builder.Entity<ApplicationUser>().HasMany(s => s.CreatedPosts)
.WithOne(f => f.OriginalPoster);
Now, which properties do the other two (HiddenPosts and SavedPosts) reference? I hope you see the problem here.
But assuming you have another type of poster defined in your Post model like this.
public ApplicationUser HiddenPoster {get;set;}
You make the collection it belongs to reference it as well.
builder.Entity<ApplicationUser>().HasMany(s => s.HiddenPosts)
.WithOne(f => f.HiddenPoster);
But you don't, so this approach would not work because it's only one type of poster you have in your Post. I would suggest you redefined your model to have an enum in Post with values Created,Hidden and Saved.
public enum PostStatus
{
Created,
Hidden,
Saved
}
Then define the status in the Post model like this.
public PostStatus Status {get;set;}
So that in your ApplicationUser, you do not have to define multiple collections, you only have Posts;
public class ApplicationUser : IdentityUser
{
public ICollection<Post> Posts {get;set;}
}
and you can then filter which post is created, hidden or saved using the Status enum property from Post.
There are loads of resources for this on Google but I can't fully understand what I need to do in my scenario:
I have this class:
public class CompanyLanguage : EntityBase
{
public int CompanyId { get; set; }
public int LanguageId { get; set; }
public bool IsDefault { get; set; }
public virtual Company Company { get; set; }
public virtual Language Language { get; set; }
}
Language is defined as:
public class Language:EntityBase
{
[Required]
[DisplayName("Language Code")]
public string LanguageCode { get; set; }
[Required]
[MaxLength(2, ErrorMessage ="2 characters maximum")]
[DisplayName("2 Char Language Code")]
public string LanguageCode2Char { get; set; }
[Required]
[DisplayName("Language Name")]
public string LanguageName { get; set; }
public virtual List<LabelLanguage> LabelLanguages { get; set; }
}
Running a Fortify Scan returns the issue below as a high priority:
(ASP.NET MVC Bad Practices: Optional Submodel With Required Property)
We can't run the fortify scan - it's being run by someone else, so I need to get the changes right so it doesn't come straight back.
All the resources I've looked at suggest that underposting attacks could be made - i.e. a null Language, even though Language has some required properties.
For me, this is a valid scenario - the required properties of Language are only required if Language isn't null.
So what am I supposed to do to resolve this? Do I make public int LanguageId { get; set; } required, or public virtual Language Language { get; set; } or both?
Or am I completely wrong an I have to do something else? As I say I can't test these as the software has to be sent away for the test or I'd be trying all sorts out.
To summarize our discussion from comments.
Create a view model which models only the information that is needed to satisfy the corresponding view.
populate view models in your controller action from your domain ef models
either project directly into view models using linq queries or Automapper.
Example view model for your question
public class CompanyLanguageEditViewModel
{
[DisplayName("Company")]
[Required]
public int CompanyId { get; set; }
[DisplayName("Language")]
[Required]
public int LanguageId { get; set; }
public bool IsDefault { get; set; }
public IEnumerable<SelectListItem> Companies{ get; set; }
public IEnumerable<SelectListItem> Languages { get; set; }
}
And in your view you can then use
#Html.DropDownListFor(x => x.CompanyId, Model.Companies);
and your label will be Country and you are only going to POST back what you need
I'm a little confused about this one. I read about metadata classes in this article on MSDN.
It says that the reason for creating metadata is not to mess with the auto generated models by EF.
So this is a model generated by EF:
namespace Blog.Models
{
using System;
using System.Collections.Generic;
public partial class Article
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public Article()
{
this.ArticleTags = new HashSet<ArticleTag>();
this.Comments = new HashSet<Comment>();
}
public int ArticleID { get; set; }
public string PostTitle { get; set; }
public string PostContent { get; set; }
public string PostLinkText { get; set; }
public Nullable<System.DateTime> PostDateTime { get; set; }
public Nullable<int> PostAuthorID { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<ArticleTag> ArticleTags { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<Comment> Comments { get; set; }
public virtual Admin Admin { get; set; }
}
}
and this is metadata class for Article model:
public class ArticleMetadata
{
[Display(Name = "Post Title")]
public string PostTitle { get; set; }
[Display(Name = "Content")]
public string PostContent { get; set; }
[Display(Name = "Link Text")]
public string PostLinkText { get; set; }
[Display(Name = "Post Date and Time")]
public DateTime? PostDateTime { get; set; }
[Display(Name = "Post Author")]
public int? PostAuthorID { get; set; }
}
linked to model class using PartialClasses.cs:
[MetadataType(typeof(ArticleMetadata))]
public partial class Article
{
}
Is the metadata class the same as a ViewModel??
If so, how are these different and which one should be used in my case?
class ArticleMetadata is a helper class for class Article, needed because you can't otherwise add those Annotation attributes to specific properties.
Together they form the Model part of MVC.
Note that the partial class can't help with the properties because it can't redefine them. It is used here only to link up the MetaData class.
For very simple operations (CRUD pages) you can use the Model directly. In all other case, create a ViewModel for each View. The Article Model will probably be used by an EditArticleViewModel.
In general you should create ViewModels to support Views, not one for each Model. One ViewModel could be composed of data from several Model classes.
No, they aren't the same.
Metadata classes allow you to define/add restraints to your class members.
View models are usually used to make it easier to use your model in a view. Such as having lists of SelectListItems for dropdowns, having properties to accept form post values, etc.
The two are usually used in conjunction, you add validation in your metadata class, and enforce it through the view model.
In your specific case, it seems all you need is a metadata class.
I'm following along the ASP.NET MVC 5 book, but I've ran into an itch that the book doesn't seem to scratch. I have an Album model as so:
namespace MvcMusicStore.Models
{
public class Album
{
public virtual int AlbumId { get; set; }
public virtual int GenreId { get; set; }
public virtual int ArtistId { get; set; }
public virtual string Title { get; set; }
public virtual decimal Price { get; set; }
public virtual string AlbumArtUrl { get; set; }
public virtual Genre Genre { get; set; }
public virtual Artist Artist { get; set; }
}
}
To make a long story short, the Genre and Artist models both have a field called Name. When I list these using the StoreManagerController, it displays simply as "Name" in each of the headers. I can add a DataAnnotation to Genre and Artist suchs as [Display(Name="Artist Name")], but I only want it to display as "Artist Name" in this particular instance. I don't want it to be so specific when I am on the "Edit Artist Page."
I understand that I should go about doing this by using a ViewModel, but I am still confused as the view model would still just be pulling in the object, and that object's Display annotations are set in the model itself.
Or better yet, is this something that's best left to the markup?
Not if you use view models properly. Many people end up creating view models like:
public class FooViewModel
{
public Foo MyFoo { get; set; }
}
That's just a waste of time. Instead, you view models should completely stand in for whatever entity your editing, which means, instead of just referencing the entity, you create properties in your view model for all the properties in your entity that you want to view/edit. Then, in your controller actions, you "map" to and from your entity and view model, which is to say, you just set the properties on one with the values of the appropriate properties on the other.
In your situation then, you would need something like:
public class AlbumViewModel
{
public string Title { get; set; }
public decimal Price { get; set; }
public string AlbumArtUrl { get; set; }
public GenreViewModel Genre { get; set; }
public ArtistViewModel Artist { get; set; }
}
public class ArtistViewModel
{
public string Name { get; set; }
...
}
public class GenreViewModel
{
...
}
Then, you can set the display name to be whatever you want on this view model. If you need a different display name in another context, create a separate view model for that.
Also, what's up with all the virtuals? The virtual keyword merely means that the property/method can be overridden by a subclass. While it technically doesn't hurt anything to just make everything virtual, it's code smell unless you truly intend something to be overridden, or even subclassed in the first place. Traditionally, on entities, the only thing you'll ever add virtual to is navigation properties, as this allows Entity Framework to apply its lazy loading logic to your entity. (It literally creates subclasses of your entities dynamically, called "proxies", that add the lazy loading logic to the navigation properties' getter.) If you don't have a navigation property or even if you just don't want lazy loading enabled for that navigation property, then you shouldn't use virtual, unless you really mean to.
I would probably do something like this.
public class AlbumViewModel
{
public int AlbumId { get; set; }
public AlbumGenre Genre { get; set; }
public AlbumArtist Artist { get; set; }
}
[MetadataType(typeof(AlbumArtistMetadata))]
public class AlbumArtist : Artist {
private class AlbumArtistMetadata {
[Display(Name="Artist Name")]
public string Name { get; set; }
}
}
[MetadataType(typeof(AlbumGenreMetadata))]
public class AlbumGenre : Genre
{
private class AlbumGenreMetadata
{
[Display(Name = "Genre Name")]
public string Name { get; set; }
}
}
Though I'm not sure I'd inherit from the entities, but instead create models based on the entities.
I have created the Glassmapper Models for all the items, I have a droplink in one of my Sitecore item.
The Following is the model for the item with droplink field.
[SitecoreClass]
public class Field:BaseItem
{
[SitecoreField("Mapper Item")]
public virtual LinkedItem MapperItem { get; set; }
}
Mapper Item field is a droplink field in sitecore,
BaseItem class has all the sitecore base properties like:
[SitecoreId]
public virtual Guid Id { get; set; }
[SitecoreInfo(SitecoreInfoType.Name)]
public virtual string Name { get; set; }
[SitecoreInfo(SitecoreInfoType.DisplayName)]
public virtual string DisplayName { get; set; }
[SitecoreInfo(SitecoreInfoType.Url)]
public virtual string Url { get; set; }
[SitecoreInfo(SitecoreInfoType.Path)]
public virtual string Path { get; set; }
[SitecoreInfo(SitecoreInfoType.ContentPath)]
public virtual string ContentPath { get; set; }
[SitecoreInfo(SitecoreInfoType.TemplateId)]
public virtual Guid TemplateId { get; set; }
[SitecoreInfo(SitecoreInfoType.TemplateName)]
public virtual string TemplatedName { get; set; }
[SitecoreField("__created")]
public virtual DateTime Created { get; set; }
[SitecoreField("__updated")]
public virtual DateTime Updated { get; set; }
and LinkedItem has the following properties:
[SitecoreClass]
public class LinkedItem:BaseItem
{
[SitecoreField("Field ID")]
public virtual string FieldID { get; set; }
[SitecoreField("Display Name")]
public virtual string DisplayName { get; set; }
[SitecoreField("Field Type")]
public virtual string FieldType { get; set; }
}
I get the LinkedItem object in the MapperItem property when I am accessing the Field object, But if i try to set the MapperItem property its not saving it in the corresponding field ("Mapper Item" field), but I don't get any error.
I'm using the following code to set the droplink
fieldItem.MapperItem = ItemUtility.GetItem<LinkedItem>(new Guid("some valid guid available in the droplist source"));
It looks like your on an old version of Glass mapper? Try adding the template reference like so:
[SitecoreClass(TemplateId = "{5281CBCF-1A2D-413A-B182-2854FC6B9176}")]
In the newest version it should be set as follows:
The namespace should be: using Glass.Mapper.Sc.Configuration.Attributes;
The classes should have an attribute: [SitecoreType(AutoMap = true)]
This link contains a good screen shot of the correct implmentation fo the above: http://www.glass.lu/en/Mapper/Sc/Tutorials/Tutorial11.aspx
Things to Check:
Is the correct nuget package installed/are you able to install the latest?: http://www.glass.lu/en/Mapper/Sc/Tutorials/Tutorial1.aspx
Is the field name "Mapper Item" unique? If not Sitecore/Glass will pick the first found not always the right one.