How to create a CRUD for multi-level child Model with ASP.Core & EF? - asp.net

I'm trying to create a CRUD that can edit a model and all its children, but each time the parent model ends up empty, how to properly do a scaffolded CRUD with ASP.Core 2.2?
//Models
class Book {
int IdBook {get; set;}
string Name {getl set;}
ICollection<Page> PageList {get; set;}
}
class Page {
int IdPage {get; set;}
string Name {get; set;}
ICollection<Line> LineList {get; set;}
}
class Line{
int IdLine {get;set;}
string Content {get; set;}
}
Here's my controller
//Controller
public async Task<IActionResult> Edit(int? id)
{
var book = _context.Book
.Include(b => b.PageList)
.ThenInclude(p => p.LineList)
.First();
return View(book);
}
Here's what I'm trying to do
#model Book
#Model.Name
#for(var indexPage = 0; indexPage < Model.PageList.Count; indexPage++)
{
#Model.PageList[indexPage].Name
#for(var indexLine = 0; indexLine < Model.PageList[indexPage].LineList.Count)
{
Html.EditorFor(x => x.PageList[indexPage].LineList[indexLine].Content)
}
}
But when I post my form, I only get the properties of Book, and Book.PageList is null, what is the proper way do that? Is there any tutorial I would have missed?
UPDATE
The problem seems to be the type, the controller receives the post parameter
(My code is a bit different, but the same, the books were an example)

Can you post entire code for the action method on the controller that processes your request?
From your code on the razor view page, in the inner loop where you are iterating through PageList, your are not incrementing your indexLine. Shouldn't this line
#for(var indexLine = 0; indexLine < Model.PageList[indexPage].LineList.Count)
be
#for(var indexLine = 0; indexLine < Model.PageList[indexPage].LineList.Count, indexLine++)?
Again, On the controller, if the Request.Form property has the entire 'supposed' payload but model binding isn't working, try annotating the Submission parameter with [FromBody] Annotation to clearly inform ASP.NET to bind Submission from bofy of the request - like so
public async Task<IActionResult> Edit(int id, [FromBody] Submission submission) {}
Look through these tiny fixes and let me know if you still have any issues

Related

Entity Frameworok generate guid as id and save it

I'm trying to save to my table Users let's say, string ID, string email, and string password. The problem is that ID must be a guid that I have to create it and save it and not SQL server. Any ideas how?
I searched but I only found how to make SQL server to create the guid.
First of all, tell Entity framework that you will generate the value of the primary key:
Use DatabaseGenerated Attribute
public class School
{
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int Id { get; set; }
public string Name {get; set;}
...
}
The None option prevents values from being generated by the database automatically in cases where they would otherwise be created.
Furthermore, consider to overwrite DbContext.SaveChanges(). In this procedure ask the ChangeTracker for all elements that are Added. Generate an Id for every Added element. It might be dangerous to let others generate an Id, because they might be adding a constant value or just an auto-increment.
Another possibility would be to generate it within the Add function, but if you do that, then users could change your generated Id. So the proper place is within SaveChanges:
public override int SaveChanges()
{
var addedElements = this.ChangeTracker.Entries
.Where(entry => entry.State == EntityState.Added);
foreach(var addedElement in addedElements)
{
// This will fail: the added element doesn't have a property Id:
addedElement.Id = GenerateId();
}
return base.SaveChanges();
}
For this you have to be certain that every added element has a property Id. The simplest way is to create an interface and let all your tables implement this interface:
public interface IID
{
string Id {get; set;}
}
public class School : IID {...}
public class Student : IID {...}
public class Teacher : IID {...}
public class DbContext
{
public DbSet<School> Schools {get; set;}
public DbSet<Student> Students{get; set;}
public DbSet<Teacher> Teachers {get; set;}
public override int SaveChanges()
{
var addedElements = this.ChangeTracker.Entries.Cast<IID>
.Where(entry => entry.State == EntityState.Added);
foreach(var addedElement in addedElements)
{
addedElement.Id = GenerateId(); // Every Added element implements IId
}
return base.SaveChanges();
}
private string GenerateId()
{
... // TODO: return unique ID, for instance a GUID
}
}

How to sort data in an asp.net gridview by properties of sub-objects?

I have a List<Role> (see below) that I am binding to an asp.net gridview. I want to sort this data using SortExpression, such that it is sorted by two properties of sub-objects of the rows. Specifically, I want to sort by the Application's Name, then the ApplicationType's ApplicationTypeName.
How can I do this?
The classes here are:
public class Application
{
public string Name {get; set;}
public int Status {get; set;}
}
public class ApplicationType
{
public string ApplicationTypeName {get; set;}
public int ApplicationTypeStatus {get; set;}
}
public class Role
{
public Application oApplication {get; set;}
public ApplicationType oApplicationType {get; set;}
}
Edit: note that I was responding to the earlier verison of the question, before it related to gridview; still, this might be useful...
Worst case: you can use the approach here to pre-sort the list before binding it to the gridview.
Various options:
implement IComparable[<T>]
implement IComparer[<T>]
use an ad-hoc sort
I'm guessing you just need the last, so perhaps:
list.Sort((x,y) => {
int delta = string.Compare(x.Application.Name, y.Application.Name);
if (delta == 0) delta = string.Compare(
x.ApplicationType.ApplicationTypeName, y.ApplicationType.ApplicationTypeName);
return delta;
});
Alternatively, you can perhaps do it via LINQ in the source data - note however that this is done when creating a new list - it isn't an in-place sort of an existing list:
var list = source.OrderBy(x => x.Application.Name)
.ThenBy(x => x.ApplicationType.ApplicationTypeName)
.ToList();

Passing additional parameters with a form submission to a controller with ASP.NET MVC 4

I have an Edit view that allows my user to update the technical information about a vehicle. When the edit is carried out I want to pass a Customer Number and a Vehicle Number to the controller so that I can make an audit log entry against the customer and vehicle to record the changes.
I have this code in my controller:
GET:
public ActionResult Edit(int id, int? AuditLogID, int? ErrorLogID, int CustomerNo, int VehicleNo)
POST:
public ActionResult Edit(int id, VehicleAssetTechnicalInformation technicalInformation, int CustomerNo, int VehicleNo)
The CustomerNo and VehicleNo should be required - an update should not be able to proceed without these details as everything must be audited.
In my Edit.cshtml view I have this:
#using (Html.BeginForm("Edit",
"VehicleAssetTechnicalInformation",
FormMethod.Post,
new { CustomerNo = (int)ViewBag.CompanyID,
VehicleNo = (int)ViewBag.VehicleID }))
Inside my View I even show the ViewBag.CompanyID and ViewBag.VehicleID to the user so I know they exist when the view is loaded.
However, when I submit the form I get the following:
The parameters dictionary contains a null entry for parameter 'CustomerNo' of non-nullable type 'System.Int32' for method 'System.Web.Mvc.ActionResult Edit(Int32, VehicleAssetTechnicalInformation, Int32, Int32)
For some reason the CustomerNo and VehicleNo are not being submitted as attributes with the form.
What can I do to make sure these values are passed along?
As far as I am aware you can't bind single parameters after you have defined a model parameter (all parameters are assumed to belong to the model). My advice would be to merge all your parameters into a single view model e.g.
public class VehicleSubmissionViewModel
{
public int Id { get; set; }
public int CustomerNo { get; set; }
public int VehicleNo { get; set; }
public VehicleAssetTechnicalInformation TechnicalInfo { get; set; }
}
...
public ActionResult Edit(VehicleSubmissionViewModel viewModel)
{
...
}

ASP.net MVC3 Select Foreign Key in Veiw using DropDownList

First off, sorry for brinning up, yet again, a topic that has been coverd many times before on this site - As much as I try, I just can't get my code to work.
I have the following model classes:
public class ProjectsTable {
public int ID {get; set;}
public String ProjectName {get; set;}
...
public virtual Manager Manager_Id {get; set;} // Stores foreign key of Manager
}
public class Manager {
public int ID {get; set;}
public String ManagerName {get; set;}
...
public virtual ICollection<ProjectsTable> Projects {get; set;}
}
The ProjectsTable Controller class contains the following method to double as a display and edit page:
private SynthContext db = new SynthContext();
...
// GET: /ProjectsTable/Parameters/5
public ActionResult Parameters(int id) {
ProjectsTable projectstable = db.ProjectsTable.Find(id);
ViewBag.Manager_Id = new SelectList(db.Manager, "ID", "ManagerName");
return View(projectstable);
}
// POST: /ProjectsTable/Parameters/5
public ActionResult Parameters(ProjectsTable projectstable) {
if(ModelState.IsValid) {
db.Entry(projectstable).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Parameters");
}
ViewBag.Manager_Id = new SelectList(db.Manager, "ID", "ManagerName");
return View(projectstable);
}
Finally, Parameters.cshtml contains the following code to display the dropdown
#model Synth.Models.ProjectsTable
...
<fieldset>
...
<div class="editor-lable">
#Html.LabelFor(model => model.Manager_ID)
</div>
<div class="editor-field">
#HTML.DropDownList("Manager_ID", String.Empty)
</div>
<input type="submit" value="Update">
</fieldset>
When I test the code, I can navigate to /ProjectsTable/Parameters/x without any problem; the drop-down is correctly generated containing a list of the manager names. However, when I click on the "Update" button, the Manager_ID is highlighted in red indicating the change has not registers. I can confirm that the changes have not registered by navigating to /ProjectsTable/list where I find no changes have taken effect.
I have tried adding #Html.ValidationMessageFor(Model.Manager_ID), the result is alway "The value 'x' is invalid", where x is a valid ID value (I have checked it against the database).
I have looked at the generated HTML source and can't see anything wrong there neither.
I have a suspicion that I need to replace #HTML.DropDownList with #HTML.DropDownListFor, but I can't figure out what parameters it needs in order to get it working.
EDIT - I have now managed to get DropDownListFor working, but the problem persists. A related question to help me resolve this issue might be how to view the post data sent back to the server.
Any help would be much appreciated.
Thanking you all in advance...
Chopo
I think you are confusing yourself because you have a model property Manager_Id and a viewBag object Manager_Id. Your modelState error is referring to the model property, not any of the values in the drop down list, which are bound to the viewBag object. I suggest re-naming your ViewBag to avoid confusion (or, better yet, not using ViewBag at all), and adding a hidden field for the model property:
#Html.HiddenFor(m => m.Manager_Id)
As a side note, from your descriptions of your troubleshooting efforts above, I also suggest becoming comfortable debugging in visual studio, setting breakpoints and inspecting values. It will make your life so much easier in the long run.
So I finally found the problem. It had nothing to do with the view or the controller. Apparently I hadn't properly written up the model: The foreign key should be stored as an int? as well as a pointer to the model in question.
public class ProjectsTable {
public int ID {get; set;}
public String ProjectName {get; set;}
...
public int? ManagerID {get; set;} // Stores foreign key of Manager
public virtual Manager Manager // I have no idea if this line does anything
}
public class Manager {
public int ID {get; set;}
public String ManagerName {get; set;}
...
public virtual ICollection<ProjectsTable> Projects {get; set;}
}
Whilst not necessary, the View also benefits from using the function:
Html.DropDownListFor(model => model.Manager_ID, ViewBag.Manager_Id as SelectList, String.Empty)
The function #HTML.DropDownList("ManagerID", String.Empty) also works, but in order for it to do so the name of the ViewBag object being passed from the controller (ManagerID) must exactly match the foreign key of the model. This is convoluted and confusing for my taste. I prefer the strongly typed approach.
For completeness, I include the fully corrected code:
Controller:
private SynthContext db = new SynthContext();
...
// GET: /ProjectsTable/Parameters/5
public ActionResult Parameters(int id) {
ProjectsTable projectstable = db.ProjectsTable.Find(id);
// The following line has been amended, but this change is not strictly necessary
ViewBag.m_id = new SelectList(db.Manager, "ID", "ManagerName", projectstable.ManagerID);
return View(projectstable);
}
// POST: /ProjectsTable/Parameters/5
public ActionResult Parameters(ProjectsTable projectstable) {
if(ModelState.IsValid) {
db.Entry(projectstable).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Parameters");
}
ViewBag.m_id = new SelectList(db.Manager, "ID", "ManagerName", projectstable.ManagerID);
return View(projectstable);
}
View:
#model Synth.Models.ProjectsTable
...
<fieldset>
...
<div class="editor-lable">
#Html.LabelFor(model => model.ManagerID)
</div>
<div class="editor-field">
Html.DropDownListFor(model => model.ManagerID, ViewBag.m_id as SelectList, String.Empty)
#Html.ValidationMessageFor(model => model.ManagerID)
</div>
<input type="submit" value="Update">
</fieldset>

ActionResult parameters with no default constructor

Obviously there are a number of ways to do this, but I thought I'd ask for a little feedback on benefits and drawbacks of the approaches.
First of all, the NerdDinner tutorial's Edit Action is in the form (say Form A):
[HttpPost]
public ActionResult Edit(int id, FormCollection collection) {
It seems to me that if you shape your ViewModels well to match your views, that the approach Form B:
[HttpPost]
public ActionResult Edit(MyViewModel mvm) {
just seems like a better, cleaner approach. I then just map the VM properties to the Model properties and save. However, if this ViewModel has other entities embedded in it that are initialized via the constructor (for example in the nerddinner tutorial), then this edit action fails if there is no default constructor and you'd have to use the first approach.
So, the first question is do you agree that generally Form B is usually better? Are there drawbacks?
Secondly, it seems then if Form B is used, the decorator type validation would need to be in the ViewModel. Are there advantages of embedding entities in ViewModels and keeping the validation at the entity level only?
This is a pretty general SO question.
the first question is do you agree that generally Form B is usually better?
The only time I do not use Form B is when I upload files. Otherwise, I don't believe anyone should ever need to use Form A. The reason I think people use Form A is a lack of understanding of the abilities of ASP.Net's version of MVC.
Secondly, it seems then if Form B is used, the decorator type validation would need to be in the ViewModel.
Sort of / it Depends. I'll give you an example:
public IValidateUserName
{
[Required]
string UserName { get; set; }
}
public UserModel
{
string UserName { get; set; }
}
[MetadataType(typeof(IValidateUserName))]
public UserValiationModel : UserModel
{
}
The validation decorator is in an interface. I'm using the MetadataType on a derived class to validate the derived type. I personally like this practice because it allows reusable validation and the MetadataType/Validation is NOT part of the ASP.NET core functionality, so it can be used outside of ASP.Net (MVC) application.
Are there advantages of embedding entities in ViewModels ..
Yes, I do my absolute best to never pass a basic model to the view. This is an example of what I don't do:
public class person { public Color FavoriteColor { get; set; } }
ActionResult Details()
{
Person model = new Person();
return this.View(model);
}
What happens when you want to pass more data to your view (for partials or layout data)? That information is not Person relevant most of the time so adding it to the Person model makes no sense. Instead, my models typically look like:
public class DetailsPersonViewModel()
{
public Person Person { get; set; }
}
public ActionResult Details()
{
DetailsPersonViewModel model = new DetailsPersonViewModel();
model.Person = new Person();
return this.View(model);
}
Now I can add required data the DetailsPersonViewModel that view needs beyond what a Person knows. For example, lets say this is going to display a for with all the colors for the Person to pick a favorite. All the possible colors aren't part of a person and shouldn't be part of the person Model, so I'd add them to the DetailPersonViewModel.
public class DetailsPersonViewModel()
{
public Person Person { get; set; }
public IEnumerable<Color> Colors { get; set; }
}
.. and keeping the validation at the entity level only?
System.ComponentModel.DataAnnotations weren't designed to validate properties' properties, so doing something like:
public class DetailsPersonViewModel()
{
[Required(property="FavoriteColor")]
public Person Person { get; set; }
}
Doesn't exist and doesn't make sense. Why ViewModel shouldn't contain the validation for the entity that needs validation.
this edit action fails if there is no default constructor and you'd have to use the first approach.
Correct, but why would a ViewModel or a Entity in a ViewModel not have a parameterless constructor? Sounds like a bad design and even if there is some requirement for this, it's easily solved by ModelBinding. Here's an example:
// Lets say that this person class requires
// a Guid for a constructor for some reason
public class Person
{
public Person(Guid id){ }
public FirstName { get; set; }
}
public class PersonEditViewModel
{
public Person Person { get; set; }
}
public ActionResult Edit()
{
PersonEditViewModel model = new PersonEditViewModel();
model.Person = new Person(guidFromSomeWhere);
return this.View(PersonEditViewModel);
}
//View
#Html.EditFor(m => m.Person.FirstName)
//Generated Html
<input type="Text" name="Person.FirstName" />
Now we have a form that a user can enter a new first name. How do we get back the values in this constructor? Simple, the ModelBinder does NOT care what model it is binding to, it just binds HTTP values to matching class properties.
[MetadataType(typeof(IPersonValidation))]
public class UpdatePerson
{
public FirstName { get; set; }
}
public class PersonUpdateViewModel
{
public UpdatePerson Person { get; set; }
}
[HttpPost]
public ActionResult Edit(PersonUpdateViewModel model)
{
// the model contains a .Person with a .FirstName of the input Text box
// the ModelBinder is simply populating the parameter with the values
// pass via Query, Forms, etc
// Validate Model
// AutoMap it or or whatever
// return a view
}
I have not yet taken a look at the NerDinner project, however, I generally try to avoid having a ViewModel in the POST of an action and instead, only have the elements of the "form" submitted.
For instance, if the ViewModel has a Dictionary that is used in some kind of dropdown, the entire dropdown will not be submitted, only the selected value.
My general approach is:
[HttpGet]
public ActionResult Edit(int id)
{
var form = _service.GetForm(id);
var pageViewModel = BuildViewModel(form);
return View(pageViewModel);
}
[HttpPost]
public ActionResult Edit(int id, MyCustomForm form)
{
var isSuccess = _service.ProcessForm(id);
if(isSuccess){
//redirect
}
//There was an error. Show the form again, but preserve the input values
var pageViewModel = BuildViewModel(form);
return View(pageViewModel);
}
private MyViewModel BuildViewModel(MyCustomForm form)
{
var viewModel = new MyViewModel();
viewModel.Form = form;
viewModel.StateList = _service.GetStateList();
return viewModel;
}

Resources