MVC4 Razor drop down list binding with foreign key - asp.net-mvc-4

So as I wanted to have a deeper understanding. I added a little bit more functionality to the MSFT tutorial on MVC4 that you can find here (http://www.asp.net/mvc/tutorials/mvc-4/getting-started-with-aspnet-mvc4/intro-to-aspnet-mvc-4)
The model is very simple. You have movies and directors. Every movie has 1 director max.
I want the user to be able to assign a director to a movie from a drop down list and save it but somehow the movie gets saved with a null Director_ID field in the database.
Here are my simple models:
public class Movie
{
public int ID { get; set; }
[Required]
public string Title { get; set; }
public DateTime ReleaseDate { get; set; }
[Required]
public string Genre { get; set; }
public decimal Price { get; set; }
public string Ranking { get; set; }
public Director Director { get; set; }
}
public class Director
{
public int ID { get; set; }
public string Name { get; set; }
}
When the movie table gets generated it comes with a Director_ID field. Sweet!
I would like the user to select a director while editing a movie form a drop down list so
in the movie edit view I managed to bind a drop down list to a list of all directors obtained form the database
<div class="editor-field">
#Html.DropDownListFor(model => model.Director.ID, ViewBag.Directors as List<SelectListItem>, "All")
</div>
Controller:
//GET
public ActionResult Edit(int id = 0)
{
var DirectorsList = new List<SelectListItem>();
var DirQuery = from d in db.Directors select d;
foreach (var d in DirQuery)
{
DirectorsList.Add(new SelectListItem { Value = d.ID.ToString(), Text = d.Name });
}
ViewBag.Directors = DirectorsList;
Movie movie = db.Movies.Find(id);
if (movie == null)
{
return HttpNotFound();
}
return View(movie);
}
I get my list of all directors in my drop down. All right!
Now when I save the movie :
[HttpPost]
public ActionResult Edit(Movie movie)
{
if (ModelState.IsValid)
{
movie.Director = db.Directors.Find(movie.Director.ID);
db.Entry(movie).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(movie);
}
The argument movie that the Edit method receives comes with a Director property (as I specified in the model), when I browse into it I see the two properties form director:
"ID": which comes with the proper value that the user selected form the drop down and "Name": set to null.
As you can see in the code I pick the whole director object form the database matching the drop down value and save it
The problem is that when saving the movie, the foreign key on Movies table (Director_ID) never gets updated.
What am I doing wrong? Is there any better approach for doing this?

Make Id of the Director part of your model, like
public class Movie
{
public int ID { get; set; }
[Required]
public string Title { get; set; }
public DateTime ReleaseDate { get; set; }
...
public int DirectorId { get; set; }
public virtual Director Director { get; set; }
}
Then in your controller:
//GET
public ActionResult Edit(int id = 0)
{
Movie movie = db.Movies.Find(id);
if (movie == null)
{
return HttpNotFound();
}
ViewBag.DirectorId = new SelectList(db.Directors, "DirectorId", "Name", movie.DirectorId);
...
}
And in your view:
<h2>Edit</h2>
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>Movie</legend>
...
<div class="editor-label">
#Html.LabelFor(model => model.DirectorId, "Director")
</div>
<div class="editor-field">
#Html.DropDownList("DirectorId", String.Empty)
#Html.ValidationMessageFor(model => model.DirectorId)
</div>
...

Related

Razor pages binding dropdownlist to complex type database not binding viewmodel

I got a dropdown list that is populated from a database, It renders fine and the items are shown. The issue comes in when i try to save the model and the viewstate says its invalid for the postCategory Title and description as they are null but does have the Id value from the selection.
my db class is as follows.
public class Article
{
public long ArticleId { get; set; }
[Required]
[MaxLength(200)]
public string Title { get; set; }
public ArticleCategories PostCategory { get; set; } //this the problem
}
public class ArticleCategories
{
public long Id { get; set; }
[Required]
[MaxLength(100)]
public string Title { get; set; }
[Required]
[MaxLength(300)]
public string Description { get; set; }
public string Slug { get; set; }
public List<Article> AssociatedPosts { get; set; }
}
In my page model i load the dropdown list as follows.
public ArticleCategories NewArticleCategory { get; set; }
public List<SelectListItem> PostCategories
{
get
{
List<SelectListItem> NewList = new List<SelectListItem>();
NewList = _context.ArticleCategories.Select(a =>
new SelectListItem
{
Value = a.Id.ToString(),
Text = a.Title.ToString(),
}).ToList();
return NewList;
}
}
and on the page
<div class="form-group">
<label asp-for="BlogArticle.PostCategory" class="control-label"></label>
<select asp-for="BlogArticle.PostCategory.Id" class="form-control" asp-items="Model.PostCategories">
<option value="">--Choose a Catergory--</option>
</select>
#Html.HiddenFor(m=>m.BlogArticle.PostCategory.Title )
#Html.HiddenFor(m=>m.BlogArticle.PostCategory.Description )
<span asp-validation-for="BlogArticle.PostCategory" class="text-danger"></span>
</div>
It only select the Id so tried to attach it by retrieving it from the db.
var PostCategory = _context.ArticleCategories.Where(c => c.Id == BlogArticle.PostCategory.Id).FirstOrDefault();
if (PostCategory != null)
{
BlogArticle.PostCategory = PostCategory;
}
if (!ModelState.IsValid)
{
return Page();
}
not sure where i am going wrong, if there any advice or suggestions it would be greatly apricated. thank you in advance.
From your code, When you pass data to backend from view, the ArticleCategories model will only have Id value from selection, the values of Title and Description are null because you do not pass any value to their input tag right? modelsate will only validate the model passed from the view. Now the ArticleCategories model passed from the view only has id value, you also add [Required] tag to Title and Description properties, So Title and Description will be invalid in modelsate.
In your code, I think you want ModelSate to validate other properties, So you need to remove Title and Description properties from ModelSate, Please refer to this code :
if (ModelState.Remove("BlogArticle.PostCategory.Title") && ModelState.Remove("BlogArticle.PostCategory.Description"))
{
if(!ModelState.IsValid)
return Page();
}
return Page();

Mapping problem when using Automapper in an Edit form with select list

I am trying to create an edit form which includes a selectlist that gets the data from the database. I am unable to display the form since I cannot map the viewmodel with the actual model using Automapper.
Contact.cs:
public int ContactId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string? EmailAddress { get; set; }
public int CompanyId { get; set; }
[ForeignKey("CompanyId")]
public Company Company { get; set; }
ContactEditViewModel.cs:
public int ContactId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string? EMailAddress { get; set; }
[Range(1, int.MaxValue, ErrorMessage = "Please select a company.")]
public int CompanyId { get; set; }
public SelectList? Company { get; set; }
Edit View
<div class="form-group">
<label asp-for="Company" class="control-label"></label>
<div class="input-group mb-3">
<select asp-for="CompanyId" class="form-select" asp-items="#Model.Company"></select>
</div>
</div>
ContactsController Edit Action:
public async Task<IActionResult> Edit(int? id)
{
if (id == null)
{
return NotFound();
}
var contact = await _context.Contacts.FirstOrDefaultAsync(c => c.ContactId == id);
var model = new ContactEditVM
{
Company = new SelectList(_context.Companies, "CompanyId", "CompanyName"),
};
//var contact = mapper.Map<ContactEditVM>(await contactRepository.GetAsync(id));
mapper.Map(model, contact);
if (contact == null)
{
return NotFound();
}
//ViewData["CompanyId"] = new SelectList(_context.Companies, "CompanyId", "CompanyName", contact.Company);
return View(model);
}
MappingConfiguration
public class MapConfig : Profile
{
public MapConfig()
{
CreateMap<Contact, ContactListVM>().ReverseMap();
CreateMap<Contact, ContactCreateVM>().ReverseMap();
CreateMap<Contact, ContactEditVM>().ReverseMap();
}
}
The error I get is:
AutoMapperMappingException: Missing type map configuration or unsupported mapping.
Mapping types:
SelectList -> Company
Microsoft.AspNetCore.Mvc.Rendering.SelectList -> ENV.Data.Company
Destination Member:
Company
...
If I create a new instance of my viewmodel and assign values to it manually, without using Automapper, it works as intended. So what is wrong with my mapping?
Does it work if you outcomment the "Company" from your Contact.cs and outcomment the "Company" from your ContactEditViewModel.cs?
I think you need to define a mapping which tells autoMapper how to map a "SelectedList?" to a "Company".
For Example:
var autoMapperConfig = new MapperConfiguration(cfg =>
{
cfg.CreateMap<WalletData/*Source*/, BP_WalletDTO/*Destination*/>()
.ForMember(dest => dest.Id, memberOptions => memberOptions.MapFrom(src => src.Id))
.ForMember(dest => dest.Type, memberOptions => memberOptions.MapFrom(src => src.Type))
.ForMember(dest => dest.Attributes, memberOptions => memberOptions.MapFrom(src => new BP_WalletAttributesDTO
{
CryptocoinId = src.Attributes.Cryptocoin_id,
CryptocoinSymbol = src.Attributes.Cryptocoin_symbol,
Balance = src.Attributes.Balance,
IsDefault = src.Attributes.Is_default,
Name = src.Attributes.Name,
PendingTransactionsCount = src.Attributes.Pending_transactions_count,
Deleted = src.Attributes.Deleted,
IsIndex = src.Attributes.Is_index,
}));
});
Maybe this helps

Dropdown List MVC 4 error

I am trying to get a drop down list to work but its not working for me. This application is mainly a festival based application where you can add a festival along with your events. The error I am getting is on line:
#Html.DropDownList("towns", (IEnumerable<SelectListItem>)ViewData["Town"], new{#class = "form-control", #style="width:250px" })
This is the error I get:
There is no ViewData item of type 'IEnumerable' that has the key 'towns'.
Create.cshtml
<div class="form-group">
#Html.LabelFor(model => model.FestivalTown, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownList("towns", (IEnumerable<SelectListItem>)ViewData["Town"], new{#class = "form-control", #style="width:250px" })
#Html.ValidationMessageFor(model => model.FestivalTown)
</div>
#*#Html.Partial("ddlFestivalCounty");*#
</div>
Controller.cshtml
//Get
List<SelectListItem> Towns = new List<SelectListItem>();
Towns.Add(new SelectListItem { Text = "Please select your Town", Value = "SelectTown" });
var towns = (from t in db.Towns select t).ToArray();
for (int i = 0; i < towns.Length; i++)
{
Towns.Add(new SelectListItem
{
Text = towns[i].Name,
Value = towns[i].Name.ToString(),
Selected = (towns[i].ID == 0)
});
}
ViewData["Town"] = Towns;
//Post
festival.FestivalTown.Town = collection["Town"];
Model.cs
public class Festival
{
public int FestivalId { get; set; }
[Required]
[Display(Name = "Festival Name"), StringLength(100)]
public string FestivalName { get; set; }
[Required]
[Display(Name = "Start Date"), DataType(DataType.Date)]
public DateTime StartDate { get; set; }
[Required]
[Display(Name = "End Date"), DataType(DataType.Date)]
public DateTime EndDate { get; set; }
[Required]
[Display(Name = "County")]
public virtual County FestivalCounty { get; set; }
[Display(Name = "Festival Location")]
public DbGeography Location { get; set; }
[Required]
[Display(Name = "Town")]
public virtual Town FestivalTown { get; set; }
[Required]
[Display(Name = "Festival Type")]
public virtual FestivalType FType { get; set; }
public UserProfile UserId { get; set; }
}
public class Town
{
public int ID { get; set; }
[Display(Name = "Town")]
public string Name { get; set; }
}
I suspect that this error occurs when you submit the form to the [HttpPost] action and not when you are rendering the form, right? And this action renders the same view containing the dropdown, right? And inside this [HttpPost] action you forgot to populate the ViewData["Town"] value the same way you did in your HttpGet action, right?
So, go ahead and populate this property the same way you did in your GET action. When you submit the form to your [HttpPost] action, only the selected value is sent to the controller. So you need to repopulate the collection values if you intend to redisplay the same view, because this view renders a dropdown which is attempting to bind its values from ViewData["Town"].
And here's what I mean in terms of code:
[HttpPost]
public ActionResult SomeAction(Festival model)
{
... bla bla bla
// don't forget to repopulate the ViewData["Town"] value the same way you did in your GET action
// if you intend to redisplay the same view, otherwise the dropdown has no way of getting
// its values
ViewData["Town"] = ... same stuff as in your GET action
return View(model);
}
And all this being said, I would more than strongly recommend you using view models instead of this ViewData/ViewBag weakly typed stuff. Not only that your code will become much more clean, but even the error messages will start making sense.

Poll System in ASP.NET MVC

I want to Display Polling in section of My Page, I have created these POCO classes for do that :
public class Polls
{
public int Id { get; set; }
public string Question { get; set; }
public bool Active { get; set; }
public IList<PollOptions> PollOptions { get; set; }
}
public class PollOptions
{
public int Id { get; set; }
public virtual Polls Polls { get; set; }
public string Answer { get; set; }
public int Votes { get; set; }
}
And I have Used below ViewModel :
public class PollViewModel
{
public int Id { get; set; }
public string Question { get; set; }
public string Answer { get; set; }
}
Then, I passed my model using above ViewModel to my View :
public ActionResult Index()
{
var poll = from p in db.Polls
join po in db.PollOptions on p.Id equals po.Polls.Id
where p.Active == true
select new PollViewModel {
Id=p.Id,
Question=p.Question,
Answer=po.Answer
};
return View(model);
}
in my View I want to display Question and Answer of my Poll, I have tried this code :
#section Polling{
#foreach (var item in Model.Polls)
{
<input type="radio" /> #item.Answer
}
}
above code works correctly but I want to display Question too, something like this :
#section Polling{
**#Model.Polls.Question**
#foreach (var item in Model.Polls)
{
<input type="radio" /> #item.Answer
}
}
How can I do that?
PS: I have one row in my Polls Table for display in Home Page
There is relationship between Polls and PollsOption. So get Polls from your db. And pass it to view. Also you already have PollsOptions that connected to to their Polls. No need to join two tables.
controller
public ActionResult Index()
{
// get active Polls
var poll = from p in db.Poll
where p.Active == true
select p;
// pass it to the view
return View(poll);
}
view
#model IEnumerable<Polls>
#section Polling{
#foreach (var question in Model)
{
<h2>#question.Question</h2>
#foreach(var answer in question.PollOptions)
{
<input type="radio" /> #answer.Answer
}
}
}

asp.net mvc 3 EF query from one to many relationship

I am new to asp.net mvc and i have problems that i think i would solve them very easy with asp.net web forms. However the project have to be in mvc, so here is the problem.
I got X tables
table1 Users
int user_ID
string username
table2 Friends
int friendshipID
int user_ID
int friend_ID
In table 2, user_ID represents the current user that is logged in. friend_ID represents ids from his friends. Its one to many relationship.
Now what i want to do, is, in user/details/ID view, show all friends of that user.
The query that i want to make is: first select the friend_IDs from table2 where user_ID=id(from querystring), then select every username from table1 where user_ID = friend_ID.
I think this is really easy in SQL, but dont know how to do it with the mvc syntax.
The controller:
//
// GET: /User/Details/5
public ViewResult Details(int id)
{
User user = db.Users.Find(id);
return View(user);
}
The view:
#model Social2.Models.User
<div class="display-label">Friends</div>
<div class="display-field">
#foreach (var friend in #Model.Friends)
{
#friend.User.username;
}
</div>
The view returns wrong results.
Models
public partial class User
{
public User()
{
this.Albums = new HashSet<Album>();
this.Friends = new HashSet<Friend>();
this.Messages = new HashSet<Message>();
this.Posts = new HashSet<Post>();
this.Groups = new HashSet<Group>();
}
public int user_ID { get; set; }
public System.Guid user_UniqueID { get; set; }
public string username { get; set; }
public virtual ICollection<Album> Albums { get; set; }
public virtual aspnet_Users aspnet_Users { get; set; }
public virtual ICollection<Friend> Friends { get; set; }
public virtual ICollection<Message> Messages { get; set; }
public virtual ICollection<Post> Posts { get; set; }
public virtual ICollection<Group> Groups { get; set; }
}
and from friends table
public partial class Friend
{
public int friendship_ID { get; set; }
public int user_fr_ID { get; set; }
public int friend_ID { get; set; }
public virtual User User { get; set; }
}
also the context
public partial class ASPNETDBEntities : DbContext
{
public ASPNETDBEntities()
: base("name=ASPNETDBEntities")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
}
public DbSet<Album> Albums { get; set; }
public DbSet<aspnet_Users> aspnet_Users { get; set; }
public DbSet<Friend> Friends { get; set; }
public DbSet<Group> Groups { get; set; }
public DbSet<Message> Messages { get; set; }
public DbSet<Picture> Pictures { get; set; }
public DbSet<Post> Posts { get; set; }
public DbSet<sysdiagram> sysdiagrams { get; set; }
public DbSet<User> Users { get; set; }
}
As the Friends list property is virtual it will not be included with your query. Try using below query to include the Friends.
public ViewResult Details(int id)
{
User user = db.Users.Include("Friends").FirstOrDefault(u => u.user_ID == id);
//Also for each friend get the User:
foreach (var friend in user.Friends.ToList())
{
friend.User = db.Users.Find(friend.friend_ID);
}
return View(user);
}
View:
<table>
#foreach (var friend in #Model.Friends)
{
<tr>
#Html.DisplayFor(modelItem => friend.User.username)
</tr>
}
</table>
Your model classes doesn't appear to be following the convention for the Entity keys. The fields "user_ID" and "friendship_ID" should be UserId and FriendId. Or if you want to key them like that annotate them with [key] attribute.
Make the ViewModel class of your own. Retrieve the data from database and build the model class object. Pass this model class to view i.e. create your view based on this model class.