EditorForModel not working with ICollection MVC 4 - asp.net-mvc-4

We have the below model:
public class Sample
{
public int SampleId { get; set; }
public int ToTestId { get; set; }
public int Name { get; set; }
public virtual ICollection<SampleCondition> Child_SampleConditions { get; set; }
}
I am rendering them in my view as below:
#model Sample
#{
string cntlrName = ViewContext.RouteData.GetRequiredString("controller");
List<SampleCondition> sampleConditions = Model.Child_SampleConditions.ToList();
if (d2l.NullOrEmpty(sampleConditions))
{
Model.Child_SampleConditions .Add(new SampleCondition());
}
List<SampleCondition> sampleConditions = Model.Child_SampleConditions as List<SampleCondition>;
}
#Html.ValidationSummary(true)
#Html.d2_HiddenFor(O => O.SampleId)
<div class="#BOOTSTRAP.ROW">
#Html.ManyToOneFieldFor(Model, O => O.ToTestId, allowed: CrudFlag.Editable)
#Html.FieldFor(O => O.Name, allowed: CrudFlag.Editable)
#Html.FieldFor(O => sampleConditions[0].ConditionValue, allowed:CrudFlag.Editable)
</div>
Please look at the third filed in div, sampleConditions[0].ConditionValue. The html input field for this control is rendering with the name "[0].ConditionValue" instead of "Child_SampleConditions[0].ConditionValue". Thats why when I post this form, the Child_SampleConditions are not automatically binded to the model object into post method.
can somebody advise how to render ICollection input field names correctly so that they can automatically gets model binded.
NOTE: Please exclude the custom model binder options.
EDIT: I am providing some more information here.
In the ManyToOneFieldFor mehtod, the name retrieved using the below helper:
string fullName = htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(name);
It just returns, [0].ConditionValue but not Child_SampleConditions[0].ConditionValue.
Please advise..
Thanks.

Instead of :
O => sampleConditions[0].ConditionValue
write:
O => O.Child_SampleConditions[0].ConditionValue
EDIT:
Create a ViewModel with IList as ICollection cannot be indexed:
public class SampleVM
{
public int SampleId { get; set; }
public int ToTestId { get; set; }
public int Name { get; set; }
public virtual IList<SampleCondition> Child_SampleConditions { get; set; }
}
Reference link:Cannot apply indexing with [] to an expression of type ICollection

Related

Model Binding in Web API for .NET Core Type Mismatch

I have the following controller which is supposed to create a new object in the database:
[HttpPost]
public ActionResult<Panels> CreateNewPanel(Panels panel)
{
_context.Panels.Add(panel);
_context.SaveChanges();
return CreatedAtAction(nameof(GetPanelById), new { id = panel.ID }, panel);
}
It is receiving some JSON data, example:
{
"desc": "test5",
"frame": 2,
"aC240v": false
}
Which maps to the following model:
public class Panels
{
public int ID { get; set; }
public string Desc { get; set; }
public PanelFrames Frame { get; set; }
public bool AC240v { get; set; }
}
It works for the most part if "frame" isn't set, but if it is set to an integer like the code above it fails because it is type PanelFrames not an integer.
PanelFrames is another model that has a one to many relationship with Panels, each Panel can have only one PanelFrame so in the database this is recorded as simply an integer, the PanelFrames ID.
How do I reconcile this so that the integer (which is the PanelFrame ID) get's passed through the API and recorded in the database. The MS documentation doesn't seem to cover this, though it seems like it would be a pretty common occurrence, so I must not be understanding something, or doing something very wrong.
If you use EF Core one-to-many relationships and save the principle entity(PanelFrames) id,you just need to add a foreign key for your navigation property in your Panel model.Refer to my below demo:
1.Models
public class Panels
{
[Key]
public int ID { get; set; }
public string Desc { get; set; }
public int FrameID { get; set; }
[ForeignKey("FrameID")]
public PanelFrames Frame { get; set; }
public bool AC240v { get; set; }
}
public class PanelFrames
{
[Key]
public int PanelFramesID { get; set; }
public string Name { get; set; }
public List<Panels> Panels { get; set; }
}
2.In my case, I pass json data using postman, so I need to use [FromBody] on action parameters.
json:
{
"desc": "test5",
"frameid": 2,
"aC240v": false
}
Action:
[HttpPost]
public ActionResult<Panels> CreateNewPanel([FromBody]Panels panel)
Then a new Panel with FrameId would be added into database.
3.If you need to get panels with their Frame, just use Include method in action like
using Microsoft.EntityFrameworkCore;//Add necessary namespaces before
//...
var panels= _context.Panels
.Include(p => p.Frame)
.ToList();

Linq not seeing properties

I have the following class and I'm trying to access it's properties from a different related class as follows:
var nuInfo = recipe.RECIPE_INGREDIENT
.Select(i => i.INGREDIENT.INGREDIENT_NUTRITIONAL_INFO)
.Where(ni => ni.NUTRITIONAL_INFO.Main == 1);
However, I can't access any (virtual or not) properties of INGREDIENT_NUTRITIONAL_INFO.
The INGREDIENT_NUTRITIONAL_INFO class is as follows:
public class INGREDIENT_NUTRITIONAL_INFO
{
public int IngredientId { get; set; }
public int Nutritional_InfoId { get; set; }
public double Amount { get; set; }
public DateTime DateSubmitted { get; set; }
public DateTime DateModified { get; set; }
public string SubmittedBy { get; set; }
public string ModifiedBy { get; set; }
public virtual AspNetUsers AspNetUsers { get; set; }
public virtual AspNetUsers AspNetUsers1 { get; set; }
public virtual INGREDIENT INGREDIENT { get; set; }
public virtual NUTRITIONAL_INFO NUTRITIONAL_INFO { get; set; }
}
Error Code is as follows:
CS1061 'ICollection' does not contain a definition for 'NUTRITIONAL_INFO' and no extension method 'NUTRITIONAL_INFO' accepting a first argument of type 'ICollection' could be found (are you missing a using directive or an assembly reference?)
Am I missing something in Linq? Am I trying to traverse across too many relationships?
The error is because you're not adding .First() or .FirstOrDefault() on the end of your query:
var nuInfo = recipe.RECIPE_INGREDIENT.Select(i => i.INGREDIENT.INGREDIENT_NUTRITIONAL_INFO)
.Where(ni => ni.NUTRITIONAL_INFO.Main == 1)
Your code is trying to access NUTRITIONAL_INFO as a property of the collection, not as the property of a member in the collection.
The problem is that nuInfo is not just one INGREDIENT_NUTRITIONAL_INFO object, your LINQ query returns a result as a IEnumerable<T>.
If you want to get a single result you can use First(),FirstOrDeafult, Single() or SingleOrDefault to instead return a single result.
var nuInfo = recipe.RECIPE_INGREDIENT
.Select(i => i.INGREDIENT.INGREDIENT_NUTRITIONAL_INFO)
.FirstOrDefault(ni => ni.NUTRITIONAL_INFO.Main == 1);

MVC with properties not mapped to the database

I'm creating a view with a drop list and some other fields that will update a database. In the model there are properties that map to the database and some properties that are used for the dropdownlistfor. The unmapped properties throws an exception. Is there a good way to exclude the drop list properties from being mapped? I tried putting them in a separate class in the model and that didn't work.
The model:
[Table("cardata")]//Links the external table to this model object
public class Cardata
{
//Maps to the database
public int id { get; set; }
public int dealerID { get; set; }
public string model { get; set; }
public int numCyl { get; set; }
public double weight { get; set; }
// UNMAPPED Used for a drop list of car names
public string carModel { get; set; }
public IEnumerable<SelectListItem> carList
{
get
{
cartableContext ctc = new cartableContext();
IEnumerable<SelectListItem> retVal = ctc.cardata.GroupBy(c => c.model).Select(cl => cl.FirstOrDefault()).Select(cars => new SelectListItem { Value = cars.id.ToString(), Text = cars.model.ToString() });
return retVal;
}
set { }
}
}
Could you use [NotMapped]?
[NotMapped]
public string carModel { get; set; }
[NotMapped]
public IEnumerable<SelectListItem> carList{...}

Posting DropDownList value from View to Controller in MVC4

I have a MVC4 application and although I have get parameters for my DropDownList from the database, I encounter some kind of problems while posting the DropDownList value to the database. There is lots of samples for different approach, but I would like to apply a method without using an extra approach i.e. Ajax, Javascript, etc. On the other hand, I have run into "FormCollection" to pass data, but I am not sure if FormCollection is the best way in this scene. Here are some part of the view, controller and model I use:
View:
#using (Html.BeginForm("Add", "Product", FormMethod.Post,
new { enctype = "multipart/form-data" }))
{
<p>Product Type : #Html.DropDownListFor(m => m.SelectedLookupId, new SelectList(Model.Lookups.Where(x => x.LookupType == "Product Type"), "LookupID", "LookupValue"), "--- Select ---") </p>
Controller:
[HttpPost]
public ActionResult Add(Product product)
{
if (ModelState.IsValid)
{
product.ProductType = // ??? Cannot get the SelectedLookupId
...
repository.SaveProduct (product);
TempData["message"] = string.Format("{0} has been saved", product.Name);
return View("Completed");
}
else
{
//there is something wrong with the data values
return View(product);
}
}
ViewModel:
public class ProductViewModel
{
public IEnumerable<Product> Products { get; set; }
public IEnumerable<Lookup> Lookups { get; set; } //Lookup for Product Types
public int SelectedLookupId { get; set; }
public Product Product { get; set; }
}
Thanks in advance for your helps.
Your action method should be receiving the view model, not the Product itself, like so:
[HttpPost]
public ActionResult Add(ProductViewModel productViewModel)
Unless I'm confused. But I assume the view snippet you posted above is from the Add view and that view's model is of type ProductViewModel. In your action method you are returning the Add view when the model state is invalid however you are passing a Product to that view. Again I may be confused because this should give you a runtime error that the types don't match.
Thanks for reply. Actually by using ViewModel rather than View, I have managed to solve the problem. On the other hand, after some research, I have applied another effective method in order to populate Dropdownlist without needing ViewModel. Furthermore with this example, I could use multiple foreign keys on the same Lookup table as shown below. Here is an an Applicant entity having 3 foreign keys and Lookup entity related to these keys. What I wanted to achieve with this example is exactly to use a Lookup table for only several Dropdownlist parameters i.e. Gender, Yes/No, Status,... due to no needing to create a table for the several parameters (these parameters are distinguished LookupType property on Lookup table). Here is the full example (I have shorted unrelated properties for brevity) below:
Applicant Entity:
public class Applicant
{
[Key]
public int ApplicantID { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
// for using "Multiple foreign keys within same table using Fluent API"
public int? HasDoneAnyProject { get; set; }
public int? IsInterestedAnyProgramme { get; set; }
public int? InterestedProgrammeId { get; set; }
public virtual Lookup PrimaryLookup { get; set; }
public virtual Lookup SecondaryLookup { get; set; }
public virtual Lookup TertiaryLookup { get; set; }
}
Lookup Entity:
public class Lookup
{
[Key]
public int LookupID { get; set; }
public string LookupType { get; set; }
public string LookupValue { get; set; }
// for using "Multiple foreign keys within same table using Fluent API"
public virtual ICollection<Applicant> PrimaryLookupFor { get; set; }
public virtual ICollection<Applicant> SecondaryLookupFor { get; set; }
public virtual ICollection<Applicant> TertiaryLookupFor { get; set; }
}
DbContext:
public class EFDbContext : DbContext
{
public DbSet<Applicant> Applicants { get; set; }
public DbSet<Lookup> Lookups { get; set; }
//for using "Multiple foreign keys within same table using Fluent API"
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Applicant>()
.HasOptional(b => b.PrimaryLookup)
.WithMany(a => a.PrimaryLookupFor)
.HasForeignKey(b => b.HasDoneAnyProject)
.WillCascadeOnDelete(false);
modelBuilder.Entity<Applicant>()
.HasOptional(b => b.SecondaryLookup)
.WithMany(a => a.SecondaryLookupFor)
.HasForeignKey(b => b.IsInterestedAnyProgramme)
.WillCascadeOnDelete(false);
modelBuilder.Entity<Applicant>()
.HasOptional(b => b.TertiaryLookup)
.WithMany(a => a.TertiaryLookupFor)
.HasForeignKey(b => b.InterestedProgrammeId)
.WillCascadeOnDelete(false);
}
}
Controller:
private void PopulateLookupsDropDownList(string lookupType, string foreignKey, object selectedLookups = null)
{
var lookupsQuery = repository.Lookups
.Select(x => x)
.Where(x => x.LookupType == lookupType)
.OrderBy(x => x.ParentLookupID).ToList();
ViewData[foreignKey] = new SelectList(lookupsQuery, "LookupID", "LookupValue", selectedLookups);
}
and for calling the Method for each of three Dropdownlist:
PopulateLookupsDropDownList("YesNo", "HasDoneAnyProject", applicant.HasDoneAnyProject);
PopulateLookupsDropDownList("YesNo", "IsInterestedAnyProgramme", applicant.IsInterestedAnyProgramme);
PopulateLookupsDropDownList("Programme", "InterestedProgrammeId", applicant.InterestedProgrammeId);
View: : Populating each of three Dropdownlist from the same Lookup table with different LookupType parameter:
<label>Has done any project before?</label>
#Html.DropDownList("HasDoneAnyProject", "---- Select ----")
<label>Are you interested in any programme?</label>
#Html.DropDownList("IsInterestedAnyProgramme", "---- Select ----")
<label>Interested programme name?</label>
#Html.DropDownList("InterestedProgrammeId", "---- Select ----")
I hope this approach will be useful for those who want to populate Dropdownlists from the same Lookup table. On the other hand, it is not only suitable for this, also can be used for populating Dropdownlists from different tables.
Regards.

Use build in validation to validate dynamic created View

I want to make a task system where the content data is able to expand dynamically (With different kinds of inputs (Text, Images, Dropdowns etc.). My plan is to create the following model, and with the help from some custom HtmlHelpers generate my view.
Model:
public class Task
{
public int TaskId { get; set; }
public string Name { get; set; }
public virtual List<TaskData> TaskData { get; set; }
}
public enum TaskDataType
{
TextType1, TextType2, E-mail, Image
}
public class TaskData
{
public int TaskDataId { get; set; }
public int TaskId { get; set; }
public TaskDataType Type { get; set; }
public string Name { get; set; }
[Required]
public string Data { get; set; }
public bool Required { get; set; }
public virtual Task Task { get; set; }
}
View:
Validation for the Task, works out of the box with the default HtmlHelpers, but when i try to make something like:
#if (Model.TaskData != null)
{
foreach (var taskData in Model.TaskData)
{
#Html.DisplayTextFor(td => taskData.Name)
#Html.TextBoxFor(td => taskData.Data)
#Html.ValidationMessageFor(td => taskData.Data)
}
}
In my View, the validation starts to go wrong, client site validation only validates the first input in the forech loop, and the ModelState in the controller does not contain any data regarding the TaskData fields.
So I have 2 quetions:
Is this the right path, or do I need to think of this in a different way?
If this is valid method of making what i want, is there a way to make the build in validation work?
You have to use for loop in order to post field values as well as get the in build validation work for you.
#if (Model.TaskData != null)
{
for(int i=0; i< Model.TaskData.Count; i++)
{
#Html.DisplayTextFor(model => model.TaskData[i].Name)
#Html.TextBoxFor(model => model.TaskData[i].Data)
#Html.ValidationMessageFor(model => model.TaskData[i].Data)
}
}