Submitting a ViewModel Derived Form in ASP.NET MVC 4 - forms

I want to create a form that is comprised of fields that come from separate models and then, when submitted, updates the corresponding tables in the database.
I couldn't find the answer anywhere so I tinkered with things until I made something work. Being new to all of this, however, I doubt the efficacy of my solution. I'm hoping that by stating my problem and showing my solution you can better understand what I need and be able to help me find the best solution for my problem.
I like simple, abstract examples so here is what I currently have working:
First, I make a Color Model...
namespace example.Models
{
public class Color
{
public int ColorID { get; set; }
public string Name { get; set; }
}
}
Then I make a Shape Model...
namespace example.Models
{
public class Shape
{
public int ShapeID { get; set; }
public string Name { get; set; }
}
}
Next, I make a simple ViewModel...
namespace example.ViewModels
{
public class ColorAndShapeViewModel
{
public Color Color { get; set; }
public Shape Shape { get; set; }
}
}
Here is my DbContext...
namespace example.DAL
{
public class MyContext : DbContext
{
public DbSet<Color> Colors { get; set; }
public DbSet<Shape> Shapes { get; set; }
}
}
Next, the Controller...
namespace example.Controllers
{
public class MyController : Controller
{
private MyContext db = new MyContext();
public ActionResult Index()
{
return View();
}
public ActionResult Create()
{
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(ColorAndShapeViewModel viewmodel)
{
Color newColor = new Color();
newColor.Name = viewmodel.Color.Name;
Shape newShape = new Shape();
newShape.Name = viewmodel.Shape.Name;
db.Colors.Add(newColor);
db.Shapes.Add(newShape);
db.SaveChanges();
return RedirectToAction("Index");
}
}
}
Lastly, the View...
#model example.ViewModels.ColorAndShapeViewModel
#using (Html.BeginForm()) {
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
#Html.LabelFor(model => model.Color.Name)<br />
#Html.TextBoxFor(model => model.Color.Name)<br />
<br />
#Html.LabelFor(model => model.Shape.Name)<br />
#Html.TextBoxFor(model => model.Shape.Name)<br />
<br />
<input type="submit" value="Create" />
}
Thus far, everything works as expected: I am able to enter data for separate models into a single form and submit it. The database is updated (the color goes into the color table, the shape into the shape table) and all is well—or is it? In reality, I will have a much longer form using several models which are related to each other. My present solution would require a large controller, full of newObject.Property = viewModel.Object.Property statements. Isn't that undesirable? Is there a better way to handle this?
Thanks!

Related

Entity Framework 6 TPT, Multiple collections of same type on parent entity

Is there any way to configure this and get it to work in EF? I'd like to use this scenario if possible, but haven't found any way to do this without getting a the error "The DELETE statement conflicted with the REFERENCE constraint"
I have seen suggestions for handling this using inheritance. Such as in this post... However, is this really not possible to configure using the fluent API?
Multiple collections of same type in entity framework
Here is my test case....
public class ToolSet
{
public int Id { get; set; }
public virtual ICollection<Tool> Tools { get; set; }
}
public class Tool
{
public int Id { get; set; }
public virtual ICollection<Fluid> HeavyFluid { get; set; }
public virtual ICollection<Fluid> LightFluid { get; set; }
}
public class Fluid
{
public int Id { get; set; }
public double Density { get; set; }
}
public class ExampleContext : DbContext
{
public DbSet<ToolSet> ToolSets { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<ToolSet>().HasMany(x => x.Tools).WithRequired().WillCascadeOnDelete(true);
modelBuilder.Entity<Tool>().HasMany(x => x.HeavyFluid).WithOptional().WillCascadeOnDelete(true);
modelBuilder.Entity<Tool>().HasMany(x => x.LightFluid).WithOptional().WillCascadeOnDelete(false);
}
}
public class SeedDb : DropCreateDatabaseAlways<ExampleContext>
{
public override void InitializeDatabase(ExampleContext context)
{
base.InitializeDatabase(context);
var heavyFluids = new List<Fluid> { new Fluid { Density = 1 }, new Fluid { Density = 2 } };
var lightFluids = new List<Fluid> { new Fluid { Density = .1 }, new Fluid { Density = .2 } };
var toolSet = new ToolSet
{
Tools = new List<Tool>
{
new Tool{HeavyFluid =heavyFluids, LightFluid = lightFluids}
}
};
context.ToolSets.Add(toolSet);
context.SaveChanges();
}
}
[TestClass]
public class UnitTest1
{
[TestInitialize]
public void TestInitialize()
{
Database.SetInitializer(new SeedDb());
}
[TestMethod]
public void TestMethod1()
{
using (var a = new ExampleContext())
{
var toRemove = a.ToolSets.First();
a.ToolSets.Remove(toRemove);
a.SaveChanges();
Assert.IsFalse(a.ToolSets.Any());
}
}
}
am not sure if what you are seeking is possible. you want EF To Deferenciate between two collections of the same type,both optional,one with cascading on delete and the other no.
EntityFramework map Fluid to One Table and only one table,this table has some foreign keys columns to represent the relationship with the the Tool.according to your example you will end up having two foreign keys in the same column to the same type(Tool_Id and Tool_Id1 : one of them will be empty at a time).the Fluent Api is a validation Api ,no more no less.so it can't help accomplishing what you want. I think your best friends are enums and Inheritance as in the link you provided (wich i don't understand why you don't want to use them).
one way i can think now, wich i didn't test and i don't either recommand it even if it works. is having some sort of column that can take one of two values, L or H and work around it in your code to deferentiate between Heavy and Light Tools.
what i said now ,is just an opinion and there might be solutions that i don't know about.let's wait and see.

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

ASP.net MVC4 Multiselect ListBox with Many-to-Many relationship

I'm new to ASP.net, MVC4 and EF and having trouble making the leap from relatively simple samples to something (only slightly) more complex. I have simplified my model for the question here so consider the following. Forgive me if I have some of the terminology incorrect.
I have an Employee object and a Division object. An employee can be in many divisions and a division can have many employees.
public class Employee
{
public int EmployeeId { get; set; }
public String FirstName { get; set; }
public String LastName { get; set; }
public virtual ICollection<Division> Divisions { get; set; }
}
public class Division
{
public int DivisionId { get; set; }
public String DivisionName { get; set; }
public virtual ICollection<Employee> Employees { get; set; }
}
I have these mapped in the context file via the following:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Employee>()
.HasMany(e => e.Divisions)
.WithMany(d => d.Employees)
.Map(m =>
{
m.ToTable("EmployeesDivisionsId");
m.MapLeftKey("EmployeeId");
m.MapRightKey("DivisionId");
});
}
For my View Controller I have the following. I have used a ViewBag (what a name) for many to one relationships and they have worked fine, so I'm trying to modify that to work with the many to many here:
public ActionResult Index()
{
return View(db.Employees).ToList());
}
//
// GET: /Employees/Details/5
public ActionResult Details(int id = 0)
{
Employee employee = db.Employees.Find(id);
if (employee == null)
{
return HttpNotFound();
}
return View(employee);
}
//
// GET: /Employees/Create
public ActionResult Create()
{
ViewBag.Divisions = new MultiSelectList(db.Divisions, "DivisionId", "DivisionName");
return View();
}
//
// POST: /Employees/Create
[HttpPost]
public ActionResult Create(Employee employee)
{
ViewBag.Divisions = new MultiSelectList(db.Divisions, "DivisionId", "DivisionName");
if (ModelState.IsValid)
{
db.Employees.Add(employee);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(employee);
}
Finally in the Edit View I have the following code. Again for a many to one relationship, a simple DropDownList works fine with the ViewBag in the controller, and with this many to many multiselect/ListBox method the 'Divisions' show up in the view, however when the Save button is hit, the validation of '1, 2' is not valid is displayed.
<div class="editor-label">
#Html.LabelFor(model => model.Divisions)
</div>
<div class="editor-field">
#Html.ListBox("Divisions")
#Html.ValidationMessageFor(model => model.Divisions)
</div>
So as I understand it, the list of 'Divisions' are being grabbed properly from the database and selected correctly in the view but when saving to the database the association through the mapped relationship isn't being made.
So my questions are:
How do I make this work so when I save, the correct 'Divisions' are saved to the Employee?
Also, I've heard people don't like using ViewBags, is there a (better?) alternate way?
This answer maybe a bit late. However, here's one or two things that might be helpful to others finding this question.
You are using ListBox() instead of ListBoxFor() so the data isn't being automatically bound to your model. So in your controller you need:
[HttpPost]
public ActionResult Create(Employee employee, string[] Divisions)
{
// iterate through Divisions and apply to your model
...
}

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.

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.