ASP.NET 4.3 Scaffolding: Add Controller vs Add View - different behavior? - asp.net-mvc-3

I am trying to dig into ASP.NET MVC 3, using the standard tutorials in the web, and I encounter a strage problem.
Currently, I am following the samples in a book, using a "Movie" class with movie genres stored in a separate entity, connected with a foreign key (okay, I am from Germany, so my class is named in German). I show only the relevant properties here. It's a database first approach using DbContext, my model was created from the edmx by using the EF 4.x DbContext Generator and the edmx was automatically created from the data base.
public partial class Film
{
public Film() { }
public int ID { get; set; }
public string Titel { get; set; }
public int GenreID { get; set; }
public virtual Genre Genre { get; set; }
}
public partial class Genre
{
public Genre() { }
public int GenreID { get; set; }
public string Name { get; set; }
}
When I create a new Controller with CRUD Views for the Film class, using a DBContext that provides a DBSet, I get an Edit view that uses a DropdownList to edit GenreID, labelled "Genre". Fine. That's what I want.
But then, I tried to create another edit view, separately. So I right-clicked into my Edit Action-Method, selected "Add View", called it "Edit2", used Film as model and "Edit" as scaffold template. In this view, I got a simple "EditorFor(m->m.GenreID)", labelled GenreID. That's not what I want.
Of course, I can change that manually. Of course, I can download a slew of scaffolding tools that claim to do better.
But I want to understand if this is a bug in the EF templates, or if my model is built wrong so that Genre / GenreID gets confused. When I create everything at once, scaffolding creates a DropDown, so there must be "just" some detail that's missing.

You will need to call your Action in your controller "Edit2".

Related

Using a viewmodel which ignores the properties from the model

I'm using entity framework and MVC (both CORE if that matters) to make a site. Using the model and tying it directly to the view is fine all the CRUD actions work, everything is lovely.
I wanted to use a couple of pages to access the model so the site looked better, so split the controls out onto separate views and added a corresponding viewmodel for each, so my project looks like this
-Model
--CustomerModel
-ViewModel
--CustomerNameVM
--CustomerAddressVM
-View
--CustomerNameView
--CustomerAddressView
The CustomerModel has a number of properties
Forename
Surname
Address
Postcode
with Forename and Surname in the CustomerNameVM and Address and Postcode in CustomerAddressVM. Surname is defined as [Required] in the model but not in CustomerNameVM which I believe is the correct way to do it.
I'm struggling to get the model loaded into the viewmodel and then trying to save it when I'm editing the address details in CustomerAddressView because it errors when I try and save as the viewmodel doesn't contain Surname (from the model), so it's null and therefore the [Required] criteria isn't being met.
I've tried a few methods of trying to get past this like :-
Jeffrey Palermo's Onion Architecture
Repositories
domain models
amongst others which all end up with the same problem, I can't save the Address as the Surname is null.
How do I ignore validation criteria for the properties of the model that aren't being referenced in the viewmodel?
Or how do I load and reference only those properties of the model that are present in viewmodel?
Edit
For those who keep asking for code, which codeset? I've tried 30 of them now, none of which do the job. Which one of these do you want? I'm trying to get a general idea of how this is supposed to work as none of the methods, documentation and associated examples function.
Here's a starter for 10, it's unlike the other 29 codesets but it's code and probably the shortest.
The controller
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Step2Address(int? id, [Bind("CustomerID,txtAddress,txtPostcode")] VMAddress VMAddress) {
if (ModelState.IsValid) {
//the saving code
}
return View(VMAddress);
}
the model
public class clsCustomer {
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int CustomerID { get; set; }
public string Forename { get; set; }
[Required]
public string Surname { get; set; }
public string Address { get; set; }
public string Postcode { get; set; }
the viewmodel
public class VMAddress {
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int CustomerID { get; set; }
public string Address { get; set; }
public string Postcode { get; set; }
}
the view
#model theProject.Models.VMStep2Contact
<form asp-action="Step2Address">
<input type="hidden" asp-for="ComplaintID" />
<input asp-for="txtAddress"/>
<input asp-for="txtPostcode"/>
<input type="submit" value="Save" />
the context
public class ContextCustomer : DbContext {
public ContextCustomer(DbContextOptions<ContextCustomer> options) : base(options) {
}
public DbSet<clsCustomer> Customer{ get; set; }
}
Clicking "Save" on the webpage calls the controller straight away, which hits the first line if (ModelState.IsValid) and as the Surname isn't set and is [Required] the ModelState is not valid so no save is attempted.
I don't actually understand what the problem is, and without code, it's impossible to say what you might be doing wrong. Generally speaking, you shouldn't have any issues since you're using view models.
A view model is, of course, not saved directly to the database, so it has to be mapped over to an actual entity class that you will be saving. In the case of an edit, you should retrieve all relevant entities from the database, map any posted values onto the appropriate properties on those entities, and then save those entities back to the database. If you're doing this, presumably, the customer model should already contain the Surname and other required properties, and you'd only be modifying/setting the address properties.
If you're doing a create, then, simply you can't just take address information. You need the name as well, so for this, you'd need to pass a view model that contains at least all required fields, such that you have all the information you need to actually save the entity.
If you're trying to do a multi-step form, where you collect all the information over multiple posts, then you simply must persist the posted data somewhere other than the database until you have enough of it to actually save an entity to the database. This is usually accomplished via Session/TempData.

How to make single controller for two database classes - MVC3

I have two database classes as defined below:
public class TopDate
{
[Key]
public int DateId { get; set; }
public DateTime Date { get; set; }
}
public class TopSong
{
[Key]
public int SongId { get; set; }
public string Title { get; set; }
public int DateId { get; set; }
}
where DateId is foreign key to TopSong
I am creating a controller through which i can create, delete or edit these database values.
When i right click on controller class and add controller i can only select one of the two classes defined above. Is there a way to make 1 controller to handle database updates to both these tables on one page?
Error Image:
Your controller should not be dealing directly with domain objects (meaning those things that are directly associated with your database). Create a ViewModel that contains the properties that you need, use your service layer to populate the ViewModel and your controller will use that as the Model for its base. An example of your ViewModel could be something like the following given your description above:
public class MusicViewModel
{
public int SongId {get;set;}
public string Title {get;set;}
public IEnumerable<DateTime> TopDates {get;set;}
}
This view model would contain a list of all dates that a specific song was a Top Song.
The objects you showing (code) are database classes (so called domain objects).
What you need to do is to define a view model, a standard ASP MVC practice:
you define a class, that is tailored for specific view and only containing data relevant to that particular view. So you will have a view model for a view that will create a song, another that will update it etc.
Actually situation you describing is classical situation to use view models. Using domain objects in the views, however, is really really bad practice and prone to more problems than you want to deal with.
Hope this helps.

Giving error while creating partial class

I am developing MVC application in which , I am trying to create the partial class of class generated by MVC application lets say Location class.
Now I want to create the partial class of Location class in new class file.
The below class code is auto genrated by MVC of Location code.
namespace CRM
{
public partial class Location
{
public int Id { get; set; }
public string Name { get; set; }
public string Remark { get; set; }
}
}
I have added new class file which contain the partial class of above file
namespace CRMEntities.Partial_Class
{
public interface ILocation
{
[StringLength(50, ErrorMessage = "Region can accept maximum 50 characters.")]
string Region { get; set; }
[Key]
int Id { get; set; }
[Required]
string Name { get; set; }
string Remark { get; set; }
}
public partial class Location : ILocation
{
}
}
Its giving the below error...
CRMEntities.Partial_Class.Location' does not implement interface member 'CRMEntities.Partial_Class.ILocation.Name
First, you don't need to do this, what I understand is you are trying to do validation right? Think about, the object generated by EF is not ViewModel, they are domain model. Data annotation should be in View Model, not domain model.
Most of cases, often mis-use is to use domain model as view model, but it is not correct much. Because sometime, view models need more than one domain model to provide data for your UI.
So for separation of concerns, you need to define your View Model different with domain model.
Example: you have Location class, you need to add LocationViewModel class and put data annotation in here.
You can map manually or use AutoMapper for mapping bettween View Model and Domain Model.
Another solution is you can use Fluent Validation, with this way, needless to have more partial class just for validation.
You don't show the definition of ILocation in your question, but the error says that the Location.Name property is declared differently than the ILocation.Name member.
Edit: Your two partial classes appear to be in two different namespaces, hence they are actually two entirely different classes, not two parts of the same class. That would explain the compiler error.
Having said that, I do agree with the other answer (+1!) that you should do your UI validation on a view model instead.

How to work with navigation properties (/foreign keys) in ASP.NET MVC 3 and EF 4.1 code first

I started testing a "workflow" with EF code first.
First, I created class diagram. Designed few classes - you can see class diagram here
Then I used EF Code First, created EntsContext..
public class EntsContext : DbContext
{
public DbSet<Project> Projects { get; set; }
public DbSet<Phase> Phases { get; set; }
public DbSet<Iteration> Iterations { get; set; }
public DbSet<Task> Tasks { get; set; }
public DbSet<Member> Members { get; set; }
}
Next step was creating a ProjectController (ASP.NET MVC3) with simple action:
public ActionResult Index()
{
using (var db = new EntsContext())
{
return View(db.Projects.ToList());
}
}
The problem is: I am not getting a ProjectManager in view (List/Create scaffolding used). I would like to know if I am doing this wrong or scaffolding generation just ignores my properties, that aren't basic types.
Hmm... It is probably quite obvious.. because generator doesn't know what property of that Type should be used, right?
Well then I could modify my question a bit: What's a solid way to create a Project entity in this scenario (I want to choose a project manager during project creation)? Should I make a ViewModel for this?
ProjectManager will not be loaded by default. You must either use lazy loading or eager loading. Eager loading will load ProjectManager when you query Projects:
public ActionResult Index()
{
using (var db = new EntsContext())
{
return View(db.Projects.Include(p => p.ProjectManager).ToList());
}
}
Lazy loading will load ProjectManager once the property is accessed in the view. To allow lazy loading you must create all your navigation properties as virtual but in your current scenario it isn't good appraoch because:
Lazy loading requires opened context. You close context before view is rendered so you will get exception of disposed context.
Lazy loading in your case results in N+1 queries to DB where N is number of projects because each project's manager will be queried separately.
I believe you'll need a ProjectManager class, and your Project entity will need to have a property that points to the ProjectManager class.
Something like:
public class Project
{
public string Description {get; set;}
public Member ProjectManager {get; set;}
}

How to bypass validation when using viewmodels for search filtering in ASP.NET MVC (3RC2)

I'm working on an ASP.NET MVC app (using MVC3 RC2). Say I have 2 entities, Product and Category. A Category must have a CategoryTitle, which is denoted via metamodel attributes like so:
public class CategoryModel
{
public int CategoryID { get; set; }
[Required("{0} is required.")]
public int CategoryTitle { get; set; }
}
There is also a relationship such that each Product has an association with Category. When searching Products, users must be able to filter the results by selecting a Category from a drop-down HTML select list. I've tried different ways of doing this, and the following seems to promote the most code reuse:
public class SearchModel
{
public CategoryModel Category { get; set; }
public string Keyword { get; set; }
}
public class ProductController
{
public ActionResult Search(SearchModel searchModel)
{
if (ModelState.IsValid)
{
// logic to return view with viewmodel
}
return HttpNotFound();
}
}
In the view, a drop-down list is rendered using the SearchModel, and it sends requests via HTTP GET in the form of /Product/Search?Keyword=my+keywords&Category.CategoryID=69. The SearchModel object is populated as intended, creating a new CategoryModel with CategoryID == 69.
The problem is that the ModelState.IsValid always returns false, since the Category.Title is null. What is the appropriate way to do this in ASP.NET MVC? Do I have to resort to creating a different SearchModel that doesn't have a CategoryModel instance?
The proper way to do this is to use view models instead of your models to and from the views. View models are classes which are specifically tailored to the needs of a given view: they contain only the properties required for the view and the validation attributes in the context of the given view. Thus you might have multiple view models for the same model. To map between the model and the view models you could use AutoMapper.

Resources