Understanding Forms in MVC: How can I populate the model from a List<> - asp.net-mvc-2

Conclusion in Images, can be found in the bottom
I'm having some trouble to get how Forms work in MVC (as I'm a WebForms Developer and really wanna start using MVC in one big project)
I took the MVC2 Web Project and add a simple ViewModel to it
namespace TestForms.Models
{
public class myFormViewModel
{
public myFormViewModel() { this.Options = new List<myList>(); }
public string Name { get; set; }
public string Email { get; set; }
public List<myList> Options { get; set; }
}
public class myList
{
public myList() { this.Value = this.Name = ""; this.Required = false; }
public string Name { get; set; }
public string Value { get; set; }
public string Type { get; set; }
public bool Required { get; set; }
}
}
Created a Strongly Typed View, passed a new object to the view and run it.
When I press submit, it does not return what's in the Options part... how can I bind that as well?
my view
alt text http://www.balexandre.com/temp/2010-10-11_1357.png
filling up the generated form
alt text http://www.balexandre.com/temp/2010-10-11_1353.png
when I press Submit the Options part is not passed to the Model! What am I forgetting?
alt text http://www.balexandre.com/temp/2010-10-11_1352.png
Conclusion
Changing the View loop to allocate the sequential number, we now have
<%= Html.TextBox("model.Options[" + i + "].Value", option.Value)%>
model is the name of our Model variable that we pass to the View
Options is the property name that is of type List
and then we use the property name

Looking at your UI it seems that you did not put the data from the Options member on it.
<% foreach (myList obj in Model.Options) { %>
// Add the object to your UI. they will be serialized when the form is submitted
<% } %>
Also check that you enclose the data in a form element
EDIT:
Sorry! I did'nt realized that you was filling the object inside the controller. Can you please show the code you have in the view?

Related

Adding Form For Different Model In Same View

Lets's Say that i have a two model classes; Project, and Comment as following :
public class Project
{
Public int ProjectID { get; set; }
public string Title { get; set; }
public virtual List<Comment> Comments { get; set; }
}
public class Comment
{
Public int CommentID { get; set; }
public string Text { get; set; }
}
I used the CRUD creation feature when i created the "controller and the views" for my Project class.
Now, in the 'Details' view for the Project, i want to add form to add comments to this project, i want the 'Details' view to be something like :
Project Name : -- Project Name Goes Here --
Comments : 1. ---------
2. ---------
[Text Area To Enter Comment] and [SUBMIT] button
The submit button should add comment to the project's comments list.
How do I achieve this?
I recommend creating a ViewModel that represents all the data you need for a view. These ViewModels are specific to MVC.
Model:
public class IndexViewModel
{
public Project Project { get; set; }
public IEnumerable<Comment> Comments { get; set; }
public Comment NewComment { get; set; }
}
Controller Method:
public ActionResult Index()
{
var model = new IndexViewModel();
// populate data, including an empty NewComment
model.NewComment = new Comment();
model.NewComment.ProjectId = model.Project.ProjectId;
return View(model);
}
View:
#model IndexViewModel
#using (Html.BeginForm("Comment", "Create"))
{
#Html.EditorFor(m => m.NewComment.CommentText)
#Html.HiddenFor(m => m.NewComment.ProjectId)
}
This means adding or removing data a view needs is pretty straight forward. The form should only need to be around NewComment. The post model would look like:
Model:
public class CreateCommentViewModel
{
public Comment NewComment { get; set; }
}
Control Method:
public ActionResult Create(CreateCommentViewModel model)
{
// logic for creating comment
}
DotNetFiddle Example. The only problem with Dot Net Fiddle is it only supports a single view (that I know of) so when you pass the Text of the new comment, I throw an exception with the text of the comment.
Erik Philips was close to answer, actually his solution works, but i found most accurate answer to my question using Html.Action.
// 1. Add this line to Project's Detail View.
#Html.Action("CreateComment","Comment", new { ProjectID = Model.ProjectID })
// 2. Add this method to the Comment Controller class, and send the id
// to the comment view.
public ActionResult CreateComment(int ProjectID)
{
return View(new Comment() { ProjectID = ProjectID });
}
// 3. Create view for the CreateComment controller action
#using (Html.beginForm("SubmitComment","Comment"))
{
#Html.HiddenFor(ProjectID=>Model.ProjectID) // This value you send it
#Html.EditorFor(model=>Model.Text)
<input type="submit" value="Add Comment" />
}
// 4. add method to the comment controller
// since i alreay public ActionResult Details(int id) in the project controller
// to display the project's details and comments. i will call it after adding comment
public ActionResult SubmitComment(Comment comment)
{
dbContext = new myDatabaseContext();
dbContext.Comments.Add(comment);
dbContext.SaveChanges();
return RedirectToAction("Details","Project", new { id=comment.ProjectID })
}
Thanks for contributing in this post

Many to Many Relationship doesn't work both ways?

I've been trying to grok EF many-to-many relationships for the past two days now and I'm still missing something even after scouring a dozen different questions here.
I've got a model named Text that can have an arbitrary number of Tag models associated with it, and obviously, at least in theory, each Tag can be associated with an arbitrary number of Texts. Entity Framework seems to understand this well enough to create a table named TextTags in the database without me asking it to do so, and I can access Text.Tags without trouble, but when I attempt to access Tag.Texts in my code, I get a null reference exception.
Now, I could just add every text to every tag manually (or could I? that seems to throw some kind of error), but that would seem to defeat the purpose... Besides which, it also seems error prone. What am I failing to understand?
Code as requested:
Text model:
public class Text
{
public int ID { get; set; }
public Author Author { get; set; }
public string Content { get; set; }
public virtual ICollection<Tag> Tags { get; set; }
}
Tag model:
public class Tag
{
public int ID { get; set; }
public string Name { get; set; }
public ICollection<Text> Texts { get; set; }
}
Data insert:
using (var db = new TextDbContext())
{
db.Authors.Add(new Author()
{
Name = "Poe"
});
db.Tags.Add(new Tag() { Name = "lame" });
db.Tags.Add(new Tag() { Name = "example" });
db.SaveChanges();
db.Texts.Add(new Text()
{
Author = db.Authors.First(),
Tags = db.Tags.ToList(),
Content = "This is the first text by Poe."
});
db.Texts.Add(new Text()
{
Author = db.Authors.First(),
Tags = db.Tags.ToList(),
Content = "This is the second text by Poe."
});
db.Texts.Add(new Text()
{
Author = db.Authors.First(),
Tags = db.Tags.ToList(),
Content = "This is the third text by Poe."
});
db.SaveChanges();
}
Error:
foreach (var tag in db.Tags)
{
foreach (var text in tag.Texts)
{
Console.WriteLine("Tag: {0}\tText: {1}", tag.Name, text.Content);
// Null reference on line above.
}
}
You get a NullReferenceException because your navigation property Tag.Texts is not marked as virtual. As a result lazy loading does not work to load the Tag.Texts collection when you access it and the collection is null. (Text.Tags is virtual, hence no exception here.)

Automapper and lazy loading with EF and performance

I have a model like this
public class Exam
{
public int NewsId { get; set; }
public string Title { get; set; }
public string Description{ get; set; }
public string Program{ get; set; }
}
and a view model like this
public class ExamViewModel
{
public int NewsId { get; set; }
public string Title { get; set; }
}
and I do config Automapper like this
AutoMapper.Mapper.CreateMap<Exam, ExamViewModel>();
and in an ActionResult I used Automapper like this:
public ActionResult Exam()
{
var examsDb = db.Exams;
IEnumerable<ExamViewModel> examViewModel = AutoMapper.Mapper.Map<IEnumerable<Exam>, IEnumerable<ExamViewModel>>(examsDb);
return View(examViewModel);
}
and in view I loop through it
#model IEnumerable<AraParsESOL.ViewModels.ExamViewModel>
<ul>
#foreach (var e in Model)
{
<li>
#Html.ActionLink(e.Title, "Type", "Exam")
</li>
}
</ul>
My problem is that:
As you can see in the Model There are 4 properties but in viewModel there are only 2 properties.
How can i get only those 2 properties in viewModel and not the entire Model?
What happens here is that in view after each loop it goes and get the required column from the database but i want only those 2 properties and not going back to database.
i can get the database like this
db.Exam.ToList();
but it will cause the entire database gets back.
i want to use best practices here?
i know i can get the data from database by anonymouse type and select command but then what is the use of automapper?
what is the best solution here?
Don't use AutoMapper. It's not appropriate for IQueryable<T>. Instead, use LINQ projections:
public ActionResult Exam()
{
var examsDb = db.Exams;
IEnumerable<ExamViewModel> examViewModel =
from e in db.Exams
select new ExamViewModel
{
NewsId = e.NewsId,
Title = e.Title
};
return View(examViewModel);
}
If you look at the generated SQL, you will see that only the NewsId and Title columns are retured. It looks like the AutoMapper folks were interested in addressing this shortcoming, but I haven't heard anything about it since this.

How do I specify a strongly-typed html helper that uses a nested class of the model instead of the model itself?

I am creating a strongly-typed search form in ASP.NET MVC 2 that posts to a results page via FormMethod.Get (i.e. the form inputs and their values are posted to the search results query string). How do I specify strongly-typed html helpers that use a nested class of the model instead of the model itself so that I don't get the dot notation for the input names in the query string?
My strongly-typed view model class looks like:
public class SearchViewModel
{
public SearchQuery SearchQuery { get; set; }
public IEnumerable<SelectListItem> StateOptions { get; set; }
...
}
The SearchQuery class looks like:
public class SearchQuery
{
public string Name { get; set; }
public string State { get; set; }
...
}
Doing this:
<%= Html.TextBoxFor(m => m.SearchQuery.Name)%>
will generated an input with name SearchQuery.Name, which will place &SearchQuery.Name=blah in the query string when the form is posted. Instead, I would prefer just &Name=blah, as only SearchQuery properties will have associated form elements.
I'm assuming I have to do something with the Html.TextBoxFor Linq expression, but I can't seem to get the syntax right..
Thanks for your help!!
One way around this is to make Name a propery of the ViewModel ie:
public string Name{
get{
return this.SearchQuery.Name;
}
}
And in the view:
<%= Html.TextBoxFor(m => m.Name)%>
Whether or not this is a good idea is another question.

MVC 2 Validation and Entity framework

I have searched like a fool but does not get much smarter for it..
In my project I use Entity Framework 4 and own PoCo classes and I want to use DataAnnotations for validation. No problem there, is how much any time on the Internet about how I do it. However, I feel that it´s best to have my validation in ViewModels instead and not let my views use my POCO classes to display data.
How should I do this smoothly? Since my repositories returns obejekt from my POCO classes I tried to use AutoMapper to get everything to work but when I try to update or change anything in the ModelState.IsValid is false all the time..
My English is really bad, try to show how I am doing today instead:
My POCO
public partial User {
public int Id { get; set; }
public string UserName { get; set; }
public string Password { get; set; }
}
And my ViewModel
public class UserViewModel {
public int Id { get; set; }
[Required(ErrorMessage = "Required")]
public string UserName { get; set; }
[Required(ErrorMessage = "Required")]
public string Password { get; set; }
}
Controller:
public ActionResult Edit(int id) {
User user = _userRepository.GetUser(id);
UserViewModel mappedUser = Mapper.Map<User, UserViewModel>(user);
AstronomiGuidenModelItem<UserViewModel> result = new AstronomiGuidenModelItem<UserViewModel> {
Item = mappedUser
};
return View(result);
}
[HttpPost]
public ActionResult Edit(UserViewModel viewModel) {
User user = _userRepository.GetUser(viewModel.Id);
Mapper.Map<UserViewModel, User>(viewModel, user);
if (ModelState.IsValid) {
_userRepository.EditUser(user);
return Redirect("/");
}
AstronomiGuidenModelItem<UserViewModel> result = new AstronomiGuidenModelItem<UserViewModel> {
Item = viewModel
};
return View(result);
}
I've noticed now that my validation is working fine but my values are null when I try send and update the database. I have one main ViewModel that looks like this:
public class AstronomiGuidenModelItem<T> : AstronomiGuidenModel {
public T Item { get; set; }
}
Why r my "UserViewModel viewModel" null then i try to edit?
If the validation is working, then UserViewModel viewModel shouldn't be null... or is it that the client side validation is working but server side isn't?
If that's the case it could be because of the HTML generated.
For instance, if in your view you have:
<%: Html.TextBoxFor(x => x.Item.UserName) %>
The html that gets rendered could possibly be:
<input name="Item.UserName" id="Item_UserName" />
When this gets to binding on the server, it'll need your action parameter to be named the same as the input's prefix (Item). E.g.
public ActionResult Edit(UserViewModel item) {
To get around this, do as above and change your action parameter to item OR you could encapsulate the form into a separate PartialView which takes the UserViewModel as it's model - that way the Html.TextBoxFor won't be rendered with a prefix.
HTHs,
Charles
Ps. If I'm totally off track, could you please post some code for the view.