What is the best practice when I have 2 or more models, and I would like to use the properties of all those models in a form?
namespace TestApplication.Models
{
public class Parent
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Street { get; set; }
public string HouseNumber { get; set; }
public string City { get; set; }
public string Email { get; set; }
public string Mobile { get; set; }
}
}
namespace TestApplication.Models
{
public class Student
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime Birthday { get; set; }
public int Grade { get; set; }
public char Class { get; set; }
public string Email { get; set; }
public string Mobile { get; set; }
}
}
=== I need the resulting ViewModel to be passed into a FormController action to process the form.
=== the form (cshtml file) itself would contain input fields for all the properties of both classes. I would also like to have more than 2 classes in the form.
should I use AutoMapper and map both/multiple models into one ViewModel?
If yes, what would be the best approach to this?
should I rather create a common FormClass that would have both Student and Parent classes as properties?
is there another/better way?
For This I would suggest following solution. I partially agree with your second point.
Models should look like this.
public class Parent
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Street { get; set; }
public string HouseNumber { get; set; }
public string City { get; set; }
public string Email { get; set; }
public string Mobile { get; set; }
}
public class Student
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime Birthday { get; set; }
public int Grade { get; set; }
public char Class { get; set; }
public string Email { get; set; }
public string Mobile { get; set; }
}
public class FormClass // This is what you suggested but it use other two model
{
public Student Student { get; set; }
public Parent Parent { get; set; }
}
View should use FormClass
#model HelloMvc.FormClass
<form method="post" action="/">
<div class="row">
<div class="col-md-4">
#Html.EditorFor(cc=>cc.Parent)
</div>
<div class="col-md-4">
#Html.EditorFor(cc=>cc.Student)
</div>
<div class="col-md-4">
<input type="submit" />
</div>
</div>
</form>
Note :
Controller Should look like this.
public class HomeController : Controller
{
[HttpGet("/")]
public IActionResult Index()
{
return View();
}
[HttpPost("/")]
public IActionResult Index(FormClass model)
{
return View(model);
}
}
The best approach is to use different names for similar properties. For example for Email in Parent use ParentEmail and for Student use StudentEmail.
Since you own the presentation layer, I would go with #2 for simplicity and fast to produce/deploy and I don't have to worry about fields that I am sending to the client. If this app is an API, I will go with #1 and use Automapper using Profile to map the view model to your domain classes.
Related
I used Entity Framework to generate my controller and view for my class.
This is what I have:
DemandeController.cs (controller):
public ActionResult Create()
{
Demande model = new Demande();
model.date = DateTime.Now;
model.status = "en cours";
Employe emp = (Employe)Session["currentUser"];
model.Employe = emp;
ViewBag.ServiceID = new SelectList(db.Services, "id", "nom");
ViewBag.EmployeID = new SelectList(db.Employes, "matricule", "nom");
return View(model);
}
Demande -> Create.cshtml (View)
<div class="editor-label">
#Html.LabelFor(model => model.EmployeID, "Employe")
</div>
<div class="editor-field">
#Html.DropDownList("EmployeID", String.Empty)
#Html.ValidationMessageFor(model => model.EmployeID)
</div>
Employe class:
public partial class Employe
{
public Employe()
{
this.ActivityLogs = new HashSet<ActivityLog>();
this.Demandes = new HashSet<Demande>();
}
public int matricule { get; set; }
public int DepartementID { get; set; }
public string nom { get; set; }
public string prenom { get; set; }
public string telephone { get; set; }
public string adresse { get; set; }
public string fonction { get; set; }
public string username { get; set; }
public string password { get; set; }
public string role { get; set; }
public Nullable<bool> isAdmin { get; set; }
public virtual ICollection<ActivityLog> ActivityLogs { get; set; }
public virtual ICollection<Demande> Demandes { get; set; }
public virtual Departement Departement { get; set; }
}
Demande class:
public partial class Demande
{
public int id { get; set; }
public int EmployeID { get; set; }
public int ServiceID { get; set; }
public Nullable<System.DateTime> date { get; set; }
public string status { get; set; }
public string details { get; set; }
public virtual Service Service { get; set; }
public virtual Employe Employe { get; set; }
}
By default, because I have many employees, the view generates a dropdownlist where I have to chose the employe name. That works without problems.
However, I am trying to change the dropdownlist into a textbox that would display the currently logged-in employe which is saved in a session object.
I tried a lot of things such as saving the Employe object in the model from the Controller like you can see in the Controller code above but it did not work as the View does not save the entire object to my understanding so when I submit, it overrides the Employe object and only leaves the name attribute. It worked for the date and status because they are basic objects but not for Employe.
I tried explaining to the best of capacity, I'm fairly new to ASP.NET MVC. If there is any further information you'd like me to provide, just let me know.
Im fairly new at programming (6months) and getting my ass kicked by EF, so any pointers in the right direction are welcome!
The aim of my project/exercise is to make a facebook-type app for dog owners and their dogs.
In particular i'm struggling with making a DBO with Dogs and their DogFriends.
The goal is to enter a Dogname in the Form, get the DogId for that Name and add it to a DBO with Dogs and their DogFriends.
Im not really sure on how to go about that. I'm thinking creating a DBO with just DogId-column and DogFriendId-column (but i could be very wrong, as it appears my understanding of EFCore is still very limited^^)
I hope i included all the necessary code for the issue i'm facing, if not, feel free to ask for more
public class Dog : EntityBase<int>
{
public string FirstName { get; set; }
public DogFertility DogFertility { get; set; }
public DogGender DogGender { get; set; }
public string Description { get; set; }
public DateTime DoB { get; set; }
public DogBreed DogBreed { get; set; }
public virtual List<UserDog> UserDogs { get; set; }
public virtual List<DogFriend> DogFriends{ get; set; }
public virtual List<Dog> DogFriendList { get; set; }
public Dog()
{
UserDogs = new List<UserDog>();
DogFriends = new List<DogFriend>();
DogFriendList = new List<Dog>();
}
}
public class DogFriend : EntityBase<int>
{
public string DogFriendSearchInput { get; set; }
public string DogFriendFirstName { get; set; }
public Dog Dog { get; set; }
public DogFriend()
{
DogFriendFirstName = DogFriendSearchInput;
}
}
I got a ViewModel for adding DogFriends
public class AddDogFriendViewModel
{
public string DogFriendSearchInput { get; set; }
public string DogFriendFirstName { get; set; }
public int DogFriendId { get; set; }
public DogBreed DogBreed { get; set; }
public int DogId { get; set; }
public string ReturnUrl { get; set; }
public DogFriend DogFriend { get; set; }
public Domain.Model.Dog Dog { get; set; }
public AddDogFriendViewModel()
{
DogFriendFirstName = DogFriendSearchInput;
}
}
And a form to add DogFriends
<div>
<form method="post" asp-controller="DogOwner" asp-action="AddDogFriend" asp-route-returnurl="#Model.ReturnUrl">
<div asp-validation-summary="ModelOnly"></div>
<div class="navbar-header">
<div class="navbar-collapse collapse" style="margin-left:-15px">
<div>
<ul class="nav navbar-nav">
<div>
<li>
<input asp-for="#Model.DogFriendSearchInput" value="#Model.AddDogFriendViewModel.DogFriendSearchInput" asp-route-dogFriendFirstName="DogFriendSearchInput" style="width:400px" type="text" id="DogFriendSearchInput" onkeyup="SearchDogFriends()" placeholder="Search for DogName or DogOwnerUserName.." />
<input hidden asp-for="#Model.DogFriendSearchInput" value="#Model.DogFriendSearchInput" />
</li>
<li>
<a asp-controller="DogOwner" asp-action="AddDogFriend" asp-area="" asp-route-dogId="#Model.Dog.Id" asp-route-dogFriendFirstName="DogFriendSearchInput">Add this Dog to #Model.Dog.FirstName's friendslist</a>
</li>
</div>
</ul>
</div>
<br />
</div>
</div><br />
</form>
</div>
This is my current Context
public class DogFaceDbContext : IdentityDbContext<User, Role, string>
{
public DogFaceDbContext()
{
}
public virtual DbSet<Dog> Dogs { get; set; }
public virtual DbSet<UserDog> UserDogs { get; set; }
public virtual DbSet<DogFriend> DogFriends { get; set; }
public virtual DbSet<Dog> DogFriendList { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<UserDog>().HasKey(x => new { x.DogId, x.UserId });
builder.Entity<DogFriend>().HasOne(x => x.Dog).WithMany(x => x.DogFriends);
builder.Entity<Dog>().HasMany(x => x.UserDogs).WithOne(x => x.Dog);
builder.Entity<Dog>().HasMany(x => x.DogFriends).WithOne(x => x.Dog);
builder.Entity<User>().HasMany(x => x.UserDogs).WithOne(x => x.User);
}
The relevant part of the Controller (obviously it isn't correct, but i think i could figure this part out for myself if i had the correct DBO):
[HttpGet]
public IActionResult AddDogFriend(int dogId)
{
var model = new DogViewModel();
model.Dog = _dogFaceDbContext.Dogs.First(x => x.Id == dogId);
model.Dogs = _dogFaceDbContext.Dogs;
return View(model);
}
[HttpPost]
public async Task<IActionResult> AddDogFriend(int dogFriendId, string DogFriendSearchInput, AddDogFriendViewModel model)
{
var dogfriend = new DogFriend
{
DogFriendSearchInput = model.DogFriendFirstName,
};
await _dogFaceDbContext.DogFriends.AddAsync(dogfriend);
await _dogFaceDbContext.SaveChangesAsync();
return RedirectToAction("Index", "DogOwner");
}
Big Thanks for helping out a struggling student!
My working solution to the problem:
public class Dog : EntityBase<int>
{
public string FirstName { get; set; }
public DogFertility DogFertility { get; set; }
public DogGender DogGender { get; set; }
public string Description { get; set; }
public DateTime DoB { get; set; }
public DogBreed DogBreed { get; set; }
public virtual List<UserDog> UserDogs { get; set; }
public virtual List<DogFriend> DogFriends{ get; set; }
public Dog()
{
UserDogs = new List<UserDog>();
DogFriends = new List<DogFriend>();
}
}
public class DogFriend : EntityBase<int>
{
public int DogId { get; set; } //for foreign key
[NotMapped]
public string DogFriendSearchInput { get; set; }
[NotMapped]
public string DogFriendFirstName { get; set; }
[NotMapped]
public string DogFriendProfilePic { get; set; }
public int DogFriendId { get; set; }
[NotMapped]
public DogBreed DogBreed { get; set; }
public Dog Dog { get; set; }
public DogFriend()
{
}
}
public class AddDogFriendViewModel
{
public string DogFriendSearchInput { get; set; }
public Domain.Model.Dog Dog { get; set; }
public string DogFriendFirstName { get; set; }
public int DogFriendId { get; set; }
public int DogId { get; set; }
public string ReturnUrl { get; set; }
public DogFriend DogFriend { get; set; }
public AddDogFriendViewModel()
{
}
}
my form to AddDogFriend
<form method="post" asp-controller="DogOwner" asp-action="AddDogFriend">
<div asp-validation-summary="ModelOnly"></div>
<div>
<input asp-for="DogFriendSearchInput" placeholder="Search for DogName.." /><br />
<span asp-validation-for="DogFriendSearchInput"></span>
</div>
<input type="hidden" asp-for="#Model.DogFriendSearchInput" value="#Model.DogFriendSearchInput" />
<input type="submit" value="Save" asp-area="" asp-route-dogId="#Model.Dog.Id" asp-route-dogFriendFirstName="DogFriendSearchInput" />
my Context
public DogFaceDbContext()
{
}
public virtual DbSet<Dog> Dogs { get; set; }
public virtual DbSet<UserDog> UserDogs { get; set; }
public virtual DbSet<DogFriend> DogFriends { get; set; }
my Get and Post from Controller
[HttpGet]
public IActionResult AddDogFriend(int dogId)
{
var model = new DogViewModel();
model.Dog = _dogFaceDbContext.Dogs.First(x => x.Id == dogId);
model.Dogs = _dogFaceDbContext.Dogs;
return View(model);
}
[HttpPost]
public async Task<IActionResult> AddDogFriend(int dogId, string DogFriendSearchInput, AddDogFriendViewModel model)
{
var dog = _dogFaceDbContext.Dogs.First(x => x.Id == dogId);
var dogFriendToAdd = _dogFaceDbContext.Dogs.First(x => x.FirstName == DogFriendSearchInput);
dog.DogFriends.Add(new DogFriend { DogId = dog.Id, DogFriendId = dogFriendToAdd.Id });
await _dogFaceDbContext.SaveChangesAsync();
return RedirectToAction("Index", "DogOwner");
}
For further questions, don't hesitate to ask!
Your model structure is wrong and if you're doing code first, then your database relationships will be wrong. You have a circular reference in your Dog model here:
public virtual List<Dog> DogFriendList { get; set; }
I think if you get your model structure corrected, you'll be able to understand the task a little better. You didn't display all of your classes so I'm not sure what some of the virtual collections are for, but dog should look similar to this:
public class Dog : EntityBase<int>
{
public int Id {get; set;} (PK)
public string FirstName { get; set; }
public DogFertility DogFertility { get; set; }
public DogGender DogGender { get; set; }
public string Description { get; set; }
public DateTime DoB { get; set; }
public DogBreed DogBreed { get; set; }
public virtual List<DogFriend> DogFriends{ get; set; }
}
Now dog friends:
public class DogFriend : EntityBase<int>
{
public int Id {get; set;} (PK)
public int DogId {get; set;} (FK)
public string FirstName { get; set; }
public string LastName { get; set; }
}
There is a one-to-many relationship between dog and dog friends (a dog can have many friends) and maybe a many-to-many depending on the business rules (a dog can have many friends and a dog friend can be friends to many dogs).
Now your view model:
public class AddDogFriendViewModel
{
public string DogFriendSearchInput { get; set; }
public Dog Dog {get;set;}
}
I'll skip your html page for now and go to the controller. Your get will be something like this:
[HttpGet]
public IActionResult AddDogFriend(AddDogFriendViewModel model)
{
model.Dog = new Dog();
model.Dog = dogFaceDbContext.Dogs.Include("DogFriends").Where(d => d.FirstName == model.DogFriendSearchInput).FirstOrDefault();
return View(model);
}
If lazy loading is enabled you won't need the include (Dogs.Include("DogFriends"))
Please help solve my problem. I want get message if textbox TagsSites is empty.
My models:
Site:
public int Id { get; set; }
[Required]
public string UserId { get; set; }
[Required]
public string Name { get; set; }
[Required]
public string Description { get; set; }
[Required]
public string TypeMenuId { get; set; }
public virtual IList<Page> Pages { get; set; }
[Required]
public virtual IList<TagSite> TagsSites { get; set; }
public virtual TypeMenu TypeMenu { get; set; }
public virtual ApplicationUser User { get; set; }
Tag:
public int Id { get; set; }
public string Name { get; set; }
public virtual IList<TagSite> TagsSites { get; set; }
TagSite:
public int Id { get; set; }
public int SiteId { get; set; }
public int TagId { get; set; }
public virtual Site Site { get; set; }
public virtual Tag Tag { get; set; }
I now get this message for all empty inputs.
How to get message "The TagsSites field is required." ?
Thanks.
What you may want here is the MinLengthAttribute. Implementation looks something like this.
[Required]
[MinLength (1)]
public virtual IList <TagSite> TagSites { get; set; }
You should create a view model for your view with a property for a comma separated tag names and mark it with the Required attribute.
public class CreateSiteVm
{
[Required]
public string Name { set;get;}
[Required]
public string Description { set;get;}
[Required]
public string Tags { set;get;}
[Required]
public int TypeMenuId { set;get;}
public List<SelectListItem> TypeMenus { set;get;}
}
and in your GET action
public ActionResult Create()
{
var vm = new CreateSiteVm();
vm.TypeMenus = dbContext.TypeMenus.Select(x=> new SelectListItem {
Value=x.Id.ToString(),
Text=x.Name}).ToList();
return View(vm);
}
and in your view,
#model CreateSiteVm
#using(Html.BeginForm())
{
<p>#Html.ValidationSummary(false)</p>
<label>Name</label>
#Html.TextBoxFor(f=>f.Name)
<label>Descsription</label>
#Html.TextBoxFor(f=>f.Descsription)
<label>Tags</label>
#Html.TextBoxFor(f=>f.Tags)
<input type="submit" />
}
and in your HttpPost action method, create an object of your entity and set the values from view model object which is your method parameter. You can use Split method to split the comma separated string.
[HttpPost]
public ActionResult Create(CreateSiteVm model)
{
if(ModelState.IsValid)
{
var e=new Site { Name = model.Name, Description = model.Description};
e.TypeMenuId = model.TypeMenuId;
var arr = model.Tags.Split(',');
foreach (var s in arr)
{
e.Tags.Add(new Tag { Name = s});
}
dbContext.Sites.Add(e);
dbContext.SaveChanges();
return RedirectToAction("Index");
}
//to do : Load the dropdown again same as GET
return View(model);
}
I would like to populate certain parts of a view with certain fields from a database. I'm not sure exactly how to make this work. This is my current situation...
Here is my controller action:
public ActionResult Course_page(string id)
{
string abbrev = id;
var cpage = from c in db.Course_page
select c;
if (!String.IsNullOrEmpty(abbrev))
{
cpage = cpage.Where(x => x.abbrev.Contains(abbrev));
}
return View(cpage);
}
My Model:
[Table("course_page")]
public class Course_page
{
[Key]
public int CourseID { get; set; }
public string Meta { get; set; }
public string Title { get; set; }
public string Country { get; set; }
public string main_image { get; set; }
public string list_1 { get; set; }
public string list_2 { get; set; }
public string list_3 { get; set; }
public string list_4 { get; set; }
public string list_5 { get; set; }
public string list_6 { get; set; }
public string list_7 { get; set; }
public string list_8 { get; set; }
public string list_9 { get; set; }
public string list_10 { get; set; }
public string testim_1 { get; set; }
public string testim_2 { get; set; }
public string testim_3 { get; set; }
public string course_site { get; set; }
public string popup_script { get; set; }
public string abbrev { get; set; }
}
The view (shortened):
#model IEnumerable<oltinternational_mvc.Models.Course_page>
<div id="main_container">
<article class="content">
<h1>{#Html.DisplayNameFor(modelItem => cpage.Title)}</h1>
</article>
</div>
I get that I have to reference the cpage variable somewhere on the view but I'm not sure exactly in what manner. The idea is that the course page will load with the correct details as per the ID provided by the URL.
Please could someone advise me how I am to include the cpage variable in the view file or if I am doing this the correct way at all?
You are doing it correct way just change this helper :
{#Html.DisplayNameFor(modelItem => cpage.Title)} to
#Html.DisplayNameFor(modelItem => modelItem .Title)
And print the data in foreach loop because you can have more than one result.
Well I have a very complex User Profile system in a social network application I am building. The profile page has tabs that distinguishes each category of user profile information: Basic, Education, Job. There is a UserProfileViewModel sitting on top of everything, which composes of inner view models such as BasicViewModel, EducationViewModel and JobViewModel. Consider the structure as below:
public class ProfileViewModel
{
public string id { get; set; }
public BasicViewModel basic { get; set; }
public EducationViewModel education { get; set; }
public JobViewModel job { get; set; }
}
public class BasicViewModel
{
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime? DateOfRegistration { get; set; }
public DateTime? DateOfBirth { get; set; }
public string Gender { get; set; }
public string PhoneNumber { get; set; }
[Display(Name = "Biography")]
public string Biography { get; set; }
public string NickName { get; set; }
public string FavoriteQuotes { get; set; }
}
public class EducationViewModel{
public string EducationStatus { get; set; }
public List<University> Universities { get; set; }
public string CourseStatus { get; set; }
public string CourseSpecialization { get; set; }
public List<string> EducationEvents { get; set; }
}
public class JobViewModel
{
public string WorkStatus { get; set; }
public List<Company> Companies { get; set; }
}
public abstract class Organization
{
public string Name { get; set; }
public DateTime? Year { get; set; }
public int TimePeiod { get; set; }
}
public class University: Organization
{
public string Degree { get; set; }
public string Profession { get; set; }
}
public class Company: Organization
{
public string Website { get; set; }
public string Position { get; set; }
}
So the question is, does data annotation for model validation(both server and client side) work for a model that has composite structure like this? If so, do I just place annotation like I usually do with simple view models? If not, how can I achieve this in alternative ways? Please help.
Any single view model may contain other viewmodels like this:
This model is server side:
[Serializable]
public class MyBigViewModel : IValidatableObject
{
public MyBigViewModel(){
MyOtherViewModel = new MyOtherViewModel();
MyThirdViewModel = new MyThirdViewModel();
}
public MyOtherViewModel {get;set;}
public MyThiddViewModel {get;set;}
public void Post(){
//you can do something here based on post back
//like maybe this where the post method here processes new data
MyOtherViewModel.Post();
}
}
The controller could look like this:
public ActionResult UserList (MyBigViewModel uvm){
if(ModelState.IsValid){
uvm.Post();
return View(uvm);
}
return View(uvm);
}
You can implement the IValidateableObject to do "server side" validation. In the example above however, we want each viewmodel to "contain" it's own model for validation.
Each viewmodel property can use data annotations "contained" in only that viewmodel. It's a very nice way to "Contain" what you want where you want.
I very often use multiple Viewmodels in main VM and pass them in with partial views as needed.