Playing around a little bit with a .NET MVC web application, and have questions about ViewModels and the data annotations.
Lets say I have a ViewModel:
public class MyViewModel
{
[DisplayName("First Name")]
public string FirstInfoName { get; set; }
[DisplayName("First Information")]
public string FirstInfo { get; set; }
[DisplayName("Second Name")]
public string SecondInfoName { get; set; }
[DisplayName("Second Information")]
public string SecondInfo { get; set; }
[DisplayName("Third Name")]
public string ThirdInfoName { get; set; }
[DisplayName("Third Information")]
public string ThirdInfo { get; set; }
}
When I want to create an Edit view my ViewModel I use:
#Html.EditorForModel()
This works great, MVC will render textboxes for me. But let's say that I want to render tabs in my view, So the properties of the ViewModel will be grouped depending on data annotations.
Example:
public class MyViewModel
{
[Tab(1)]
[DisplayName("First Name")]
public string FirstInfoName { get; set; }
[Tab(1)]
[DisplayName("First Information")]
public string FirstInfo { get; set; }
[Tab(2)]
[DisplayName("Second Name")]
public string SecondInfoName { get; set; }
[Tab(2)]
[DisplayName("Second Information")]
public string SecondInfo { get; set; }
[Tab(3)]
[DisplayName("Third Name")]
public string ThirdInfoName { get; set; }
[Tab(3)]
[DisplayName("Third Information")]
public string ThirdInfo { get; set; }
}
Is it possible to do something like this? Maybe using templates?
While i'm not going to say you can't do this, it is certainly not going to be an easy solution. The reason is attributes are evaluated on a per property basis. Tabs are typically displayed withing a group, and there's not a good way to alter your html to include groups based on multiple attributes of the same type.
Related
I have a simple problem with my site.
Inside my site, I'm using two different models, with some identical named fields.
Because of collision, I have to give them unique names and for not loosing the modelbinding, I decided to use viewModels.
So I have a Model like this:
namespace MySite.Models
{
public class Function : BaseEntity
{
//Beziehung zur Funktionsgruppe
[Required]
[Display(Name = "Übergeordnete Funktion")]
public int FunctionGroupId { get; set; }
public virtual FunctionGroup FunctionGroup { get; set; }
[Required]
[StringLength(200)]
[Display(Name = "Bezeichnung")]
public string Name { get; set; }
}
}
And I have a new ViewModel like this:
namespace MySite.ViewModels
{
public class FunctionViewModel
{
//Properties of BaseEntity
public int F_Id { get; set; }
[Display(Name = "Erstellt")]
public string F_Created { get; set; }
[Display(Name = "Bearbeitet")]
public string F_LastChange { get; set; }
[Display(Name = "Bearbeiter")]
public string F_ByUser { get; set; }
//Beziehung zur Funktionsgruppe
[Required]
[Display(Name = "Übergeordnete Funktion")]
public int F_FunctionGroupId { get; set; }
public virtual FunctionGroup F_FunctionGroup { get; set; }
[Required]
[StringLength(200)]
[Display(Name = "Bezeichnung")]
public string F_Name { get; set; }
}
}
Now my Question is, is there a way to automatically fetch the data of the corresponding model, while loading the ViewModel?
Something like a kind of function directly inside the { get; set; }?
Actually I load field by field form the model into the ViewModel.
Hope that I could have described Right, what I'd like to do.
Carsten
You can use Automapper for mapping or getting your data from Model to ViewModel or vice-versa. It will be hard for you to map/configure if your name of your property is different so have the same name in view model as well. (If possible)
Ok so I'm adding on to the Simplemembership.
Model UsersProfiles
namespace OilNGasWeb.Models
{
[Table("Users")]
public class UserProfiles
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int UserId { get; set; }
public string UserName { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string MiddleName { get; set; }
public string Initials { get; set; }
public string Company { get; set; }
public string Department { get; set; }
public string Title { get; set; }
public string Team { get; set; }
public string TeamSub { get; set; }
public string Phone { get; set; }
public string ImageLocation { get; set; }
public string CurrentlyAuthorized { get; set; }
public string Note { get; set; }
public string Comment { get; set; }
//public virtual dbClient Client { get; set; }
public virtual ICollection<Roles> Roles { get; set; } //many to many
public virtual ICollection<dbClient> Clients { get; set; } // many to many
}
}
Roles
namespace OilNGasWeb.Models
{
[Table("webpages_Roles")]
public class Roles
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
[Required(ErrorMessage = "Required")]
public int RoleID { get; set; }
[Required(ErrorMessage = "Required")]
public string RoleName { get; set; }
public virtual ICollection<UserProfiles> UserProfiles { get; set; } //many to many
}
}
My issue now that i have it creating the many to many tables like i saw it creat before modifications my question is how to get those tables Renamed
webpages_UsersInRoles
I would prefer not to go into SSMS and change them physically rather tell MVC to use a different instance
From the code above EF produced RolesUserProfiles instead of webpages_UsersInRoles
The error shows when the program is trying to #if (User.IsInRole("Administrator")) validade user.
Naturally I hit F12 on IsInRole to bring me to the definition....
it does but there all empty
Now what ? how can i recode if its hidden from me ? where is the code at , and how can i Modify this?
What i would like out of all this is
either renaming the tables ManytoMany as they are being created
being able to modify the code that looks for webpages_UsersInRoles
Thanks in advance.
You cannot rename the tables. The table names are hard coded in SimpleMembership. You can see the source code here:
http://aspnetwebstack.codeplex.com/SourceControl/latest#src/WebMatrix.WebData/SimpleMembershipProvider.cs
Don't use the EF navigational properties. You should be accessing this information via the Membership or WebSecurity API's.
If you really want to do this, then you will need to configure EF to use the tablenames required by simple membership, which means utilizing the fluent mapping syntax.. which is not exactly intuitive.
I have the following model for an article.
public class Article
{
[Key]
public int ArticleId { get; set; }
[Required(ErrorMessage = "Title is required."), MaxLength(80)]
public string Title { get; set; }
[Required(ErrorMessage = "Body is required.")]
public string Body { get; set; }
public DateTime Time { get; set; }
public int AuthorId { get; set; }
// Navigation properties
public virtual UserProfile Author { get; set; }
public virtual ICollection<Tag> Tags { get; set; }
public virtual ICollection<Comment> Comments { get; set; }
}
The UserProfile is an extendend version of the default in the MVC4 standard project.
Now, in my scaffolded controller/view, there is no way to enter the Author.
My database (MySQL) contains a field with named Author_UserId of type int.
What is wrong?
Also, is it really necessary for the author to be referenced both via the navigation property and the AuthorId
It's not common to use 2 the same Foreign keys of a specific table at any table. Such this interpreted as data redundancy. To solve the problem without any redundancy, i suggest bellow model to you:
public class Article
{
[Key]
public int ArticleId { get; set; }
[Required(ErrorMessage = "Title is required."), MaxLength(80)]
public string Title { get; set; }
[Required(ErrorMessage = "Body is required.")]
public string Body { get; set; }
public DateTime Time { get; set; }
public int AuthorId { get; set; }//determine name of foreign key here of type primary key of navigatable table
// Navigation properties
[ForeignKey("AuthorId")]
public virtual UserProfile Author { get; set; }//column with name 'AuthorId'
public virtual ICollection<Tag> Tags { get; set; }
public virtual ICollection<Comment> Comments { get; set; }
}
Solution above, used to self naming navigation property foreign key.
I wish be useful.
I have downloaded the MVCScaffolding nuget package within VS2010. I am trying to retrieve a [DisplayName data annotation from my model in order to use it within the index.cs.t4 template.
This page OneToMany Relationships has shown me how to modify the index template in order to provide a link which will take me to the controller index for my child objects. Which in this case from emails to emailrecipients. The problem is i have called my controllers emailcontroller and emailrecipientscontroller rather than their rather less descriptive tables names which i would prefer to hide anyway. i have equally decorated the metadataobject which the t4 template uses(tbl_My_unwieldytablename_emailMetadata) with this displayname (emailrecipients) dataannotation and i was hoping i could modify the template in order to replace the name for the relation to use the displayname which is my controller name i.e.
[MetadataType(typeof(tbl_My_unwieldytablename_emailMetadata))]
public partial class tbl_My_unwieldytablename_email
{
internal sealed class tbl_My_unwieldytablename_emailMetadata
{
[ScaffoldColumn(false)]
[Required(ErrorMessage="id is required")]
public Int32 id { get; set; }
[DataType(DataType.Date)]
public DateTime send_date { get; set; }
[StringLength(255)]
public String title { get; set; }
[DataType(DataType.MultilineText)]
public String message { get; set; }
[StringLength(50)]
public String author { get; set; }
[StringLength(80)]
[DataType(DataType.EmailAddress)]
public String author_email { get; set; }
[DataType(DataType.MultilineText)]
public String attachment { get; set; }
[DataType(DataType.Date)]
public DateTime created_date { get; set; }
public Int32 batches { get; set; }
[DataType(DataType.Date)]
public DateTime complete_date { get; set; }
[DisplayName("emailrecipients")]
public EntityCollection<tbl_My_unwieldytablename_email_recipients> tbl_My_unwieldytablename_email_recipients { get; set; }
}
Cheers
Tim
[Table("tbl_My_unwieldytablename_email")]
Public Class Email
[Table("tbl_My_unwieldytablename_email_recipients")]
Public Class EmailRecipients
Should map your classes to the database tables
Say we have the following (overly simple) scenario:
We have a screen to view person details and a screen to edit person details.
The screen display person details has the following fields (as display only):
First Name
Last Name
Bio
The screen edit person details shows has following fields (in input controls):
ID (hidden)
First Name
Last Name
Bio
Say our display viewmodel looks like this:
public class DisplayPersonViewModel
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Bio { get; set; }
}
And our edit viewmodel looks like this:
public class EditPersonViewModel
{
[Required]
public int ID { get; set; }
[Required]
[StringLength(20)]
public string FirstName { get; set; }
[Required]
[StringLength(20)]
public string LastName { get; set; }
[Required]
public string Bio { get; set; }
}
Not much difference between the 2, eh? The edit model has one extra field (ID) and some of attributes on the properties. Now, if we were to combine the 2 like this:
public class DisplayPersonViewModel
{
[Required]
[StringLength(20)]
public string FirstName { get; set; }
[Required]
[StringLength(20)]
public string LastName { get; set; }
[Required]
public string Bio { get; set; }
}
public class EditPersonViewModel : DisplayPersonViewModel
{
[Required]
public int ID { get; set; }
}
This is certainly more DRY, since we don't have duplicate fields to maintain, but now we have extraneous info (attributes) on our display viewmodel. I'm leaning more towards the second approach, regardless, because some of our screens have over 25 fields! (...and that's out of my control, so please don't harp on it :) ...) However, I just want to hear opinions to get a better sense of what may be "best practice".
Yes, the second approach seems fine to me. No worries other than this itchy feeling in your stomach telling you why on earth are you decorating a display view model with validation attributes. But if you can live with it it's really something that is preferred compared to duplicating the view models.
Unfortunately personally I cannot live with this feeling in my stomach and that's why I use FluentValidation.NET to define my validation rules instead of data annotations. It allows me to have those rules separately from my view models and then I don't worry about polluting the so called display view model with validation rules. So I would define in the same way as you 2 view models and EditPersonViewModel would derive from DisplayPersonViewModel and then define my EditPersonViewModelValidator for the EditPersonViewModel in a separate class.
Oh and a side note: decorating a non-nullable type with the [Required] attribute is not necessary. All non-nullable types are required by their very basic nature. So instead of:
[Required]
public int ID { get; set; }
you should only have:
public int ID { get; set; }
Another option is to use the MetadataType Attribute.
public class PersonModel
{
public int ID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Bio { get; set; }
}
[MetadataType(typeof(IDisplayPersonViewModel))]
public class DisplayPersonViewModel : PersonModel
[MetadataType(typeof(IEditPersonViewModel))]
public class EditPersonViewModel : PersonModel
public interface IDisplayPersonViewModel
{
[ScaffoldColumn(false)]
public int ID { get; set; }
}
public interface IEditPersonViewModel
{
[Required]
[StringLength(20)]
public string FirstName { get; set; }
[Required]
[StringLength(20)]
public string LastName { get; set; }
[Required]
public string Bio { get; set; }
[Required]
public int ID { get; set; }
}
Your raw Person model is attribute free. Your Display and Edit models only have the attributes you actually need for the View.