MVC Binding List of Objects in Object - c#

I'm trying to dynamically build a table that needs to be bound to a ViewModel on form submission.
First item is the Action the form is submitting to. Then the Parent ViewModel and then the child ViewModel. Below those are two text fields representing the data I need bound.
When I submit the form all the other fields from the page are bound to their respective property but the complex object of ProgramViewModels is not.
What am I missing?
public ActionResult Add(AddEntityViewModel viewModel){
Code that does something
}
public class AddEntityViewModel
{
public IList<int> Counties { get; set; }
public IList<ProgramViewModel> Programs { get; set; }
public IList<int> Regions { get; set; }
public string EntityName { get; set; }
public bool IsValid()
{
if (string.IsNullOrEmpty(EntityName))
return false;
if (Counties == null || Counties.Count == 0)
return false;
if (Programs == null || Programs.Count == 0)
return false;
if (Regions == null || Regions.Count == 0)
return false;
return true;
}
}
public class ProgramViewModel
{
public int Id;
public string SystemId;
}
<input type="hidden" id="Programs[0]__Id" name="Programs[0].Id" data-programid="3" value="3">
<input type="text" id="Programs[0]__SystemId" name="Programs[0].SystemId" style="width:100%" maxlength="50">
Update:
Changed the fields to properties after adiga's answer. But that too did not fix the issue.
public class ProgramViewModel
{
public int Id { get; set; }
public string SystemId { get; set; }
}

Your ProgramViewModel contains fields. Change them to properties.
public class ProgramViewModel
{
public int Id { get; set; }
public string SystemId { get; set; }
}
The DefaultModelBinder uses reflection and binds only the properties and not fields.

If you have a List of a object, you should be performing a foreach instruction to get all of them:
<% foreach(var x in values) { %>
<div>hello <%= x.name %></div>
<% } %>

Related

Net Core 3.0: multiple checkboxes with integer variables

In my web app I have a self referencing many to many relationship of plants. The M2M relationships are good neighbours and bad neighbours. I want to be able to have a form where a user can check off the both types of neighbours and then save the form.
What I have so far:
For brevity, I will only show code to Good neighbours relation, the bad neighbours is the same.
Models
public class Plant
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<GoodPlants> GoodNeighbours { get; set; }
}
public class GoodPlants
{
public int PlantId { get; set; }
public int GoodNeighbourId { get; set; }
public virtual Plant Plant { get; set; }
public virtual Plant GoodNeighbour {get; set;}
}
My viewmodel EditViewModel:
public class EditViewModel
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<PlantIsSelectedViewModel> AllPlants { get; set; }
}
And the model PlantIsSelected
public class PlantIsSelectedViewModel
{
public int id { get; set; }
public string name { get; set; }
public bool isGoodNeighbour { get; set; }
public bool isBadNeighbour { get; set; }
}
I have an Edit method to display the edited plant and all the other plants:
var plant = await _context.Plants.FindAsync(id);
ICollection<Plant> plants = _context.Plants.ToList();
ICollection<GoodPlants> goodNeighbours = await _context.GoodNeighbours
.Where(g => g.PlantId == id)
.Include(g => g.GoodNeighbour)
.ToListAsync();
GoodPlants ownGoodPlant = goodNeighbours.FirstOrDefault(i => i.GoodNeighbour.Id == plant.Id);
Plant ownPlant = plants.FirstOrDefault(i => i.Id == plant.Id);
goodNeighbours.Remove(ownGoodPlant);
plants.Remove(ownPlant);
//populate the viewmodel
EditViewModel plantModel = new EditViewModel();
plantModel.Id = plant.Id;
plantModel.Name = plant.Name;
plantModel.AllPlants = _mapper.Map<ICollection<PlantIsSelectedViewModel>>(Plants);
foreach (var element in plantModel.AllPlants)
{
if (goodNeighbours.Any(g => g.GoodNeighbour.Id == element.id))
{
element.isGoodNeighbour = true;
}
else if (badNeighbours.Any(g => g.BadNeighbour.Id == element.id))
{
element.isBadNeighbour = true;
}
}
This desperately needs refactoring, but thats not the main issue here.
In my view I contrast the elements of AllPlants collection if it appears on either of the neighbours collections and have the checkbox set to checked or not:
<tbody>
#foreach (var item in Model.AllPlants)
{
<tr>
#if (item.isGoodNeighbour)
{
<td>
#Html.Label(item.name)
</td>
<td>
<input type="checkbox" name="#Model.AllPlants"
value="#item.id"
#(Html.Raw("checked=\"checked\""))
/>
</td>
<td>
<input type="checkbox" name="#Model.AllPlants"
value="#item.id" />
</td>
}
// else statements below for badneighbours and cases without any relation.
// ...
</tr>
}
I want to know how I can keep track of all the selected items (and unselected), get them in my editViewModel and send them back to my [HttpPost] Edit method. My current [HttpPost] method receives the same viewmodel, but the AllPlants property is empty.
How do I receive the correct data back?
Thanks in advance, I'm pretty stuck!

MVC Razor check radio input to enable

I am trying to have this input radio button checked base on the model but I am getting string error. Is this the correct way to set the checked enable?
Error
'string' does not contain a definition for 'Regimens'
debugger
View
<label for="RegimenReferencesC_#Model">
<input type="radio" id="RegimenReferencesC_#Model" checked="#(Model.Regimens == (int)RegimenReferences.D ? "true" : "false")" name="RegimenReferences" value="#((int)RegimenReferences.C)" class="regimen-reference">
(c) #RegimenReferences.C.ToDescriptionString()
</label>
Tried it this way too
<label for="RegimenReferencesD_#Model">
<input type="radio" id="RegimenReferencesD_#Model" name="RegimenReferences" checked="#(Model.Regimens == (int)RegimenReferences.D ? true : false)" value="#((int)RegimenReferences.D)" class="regimen-reference">
(d) #RegimenReferences.D.ToDescriptionString()
</label>
Model
public class ReferencesModel
{
public long id { get; set; }
public string Link { get; set; }
public string Text { get; set; }
public string Type { get; set; }
public int Regimens { get; set; }
public Guid? GuidelineId { get; set; }
public int SortOrder { get; set; }
}
It seems clear from <label for="RegimenReferencesC_#Model"> that #Model in the Razor view is a string, or that line would not work.
Therefore, when you try #(Model.Regimens ..., you get the error, since a string does not have a property or method named Regimens.
In other words, check your Model in the view. It is probably a string and not the object you are looking for.
You are missing some relevant code in your post so I am going to fill in the blanks with assumptions:
Given the following Class and Enum:
public class ReferencesModel
{
public long id { get; set; }
public string Link { get; set; }
public string Text { get; set; }
public string Type { get; set; }
public int Regimens { get; set; }
public Guid? GuidelineId { get; set; }
public int SortOrder { get; set; }
}
public enum RegimenReferences
{
[Description("This is A")]
A = 0,
[Description("This is B")]
B = 1,
[Description("This is C")]
C = 2,
[Description("This is D")]
D = 3
}
and the following Helper method for displaying enum annotation text:
public static string ToDescriptionString<T>(this T e) where T : IConvertible
{
if (e is Enum)
{
Type type = e.GetType();
Array values = System.Enum.GetValues(type);
foreach (int val in values)
{
if (val == e.ToInt32(CultureInfo.InvariantCulture))
{
var memInfo = type.GetMember(type.GetEnumName(val));
var descriptionAttribute = memInfo[0].GetCustomAttributes(typeof(DescriptionAttribute), false).FirstOrDefault() as DescriptionAttribute;
if (descriptionAttribute != null)
{
return descriptionAttribute.Description;
}
}
}
}
return null;
}
Your Razor code would be as follows to use a radio button and base checked on the value:
<label for="RegimenReferencesD_#Model">
<input type="radio" id="RegimenReferencesC_#Model" #if (Model.Regimens == (int)RegimenReferences.D) { Html.Raw("checked"); } name="RegimenReferences" value="#((int)RegimenReferences.D)" class="regimen-reference">
(D) #RegimenReferences.D.ToDescriptionString()
</label>

DotVVM - Code only component DataSource property is null when collection is passed in

I have my own Accordion code-only component
Here is my view where I have repeater which loads list of article sections. Each article section have list of articles. So with that I want to archieve that every article section will have his own accordion, which will contain articles. Thats why I have it in repeater
<div class="box box-primary">
<dot:Repeater DataSource="{{value: AccordionList}}">
<ItemTemplate>
<coc:Accordion DataSource="{{value: Articles}}"></coc:Accordion>
</ItemTemplate>
</dot:Repeater>
</div>
Accordion code-only component. My DataSource is always null even when I clearly see, that AccordionList contains List of Articles which is never null, but is never passed into my DataSource. When I change type of AccordionList to ArticleListDTOand pass it directly into my Accordion component, it worked well, but thats not what I want.
public class Accordion : HtmlGenericControl
{
public Accordion() : base("div")
{
}
public static readonly DotvvmProperty DataSourceProperty;
static Accordion()
{
DataSourceProperty = DotvvmProperty.Register<List<ArticleListDTO>, Accordion>(c=>c.DataSource);
}
//DataSource is always null
public List<ArticleListDTO> DataSource
{
get => (List<ArticleListDTO>)GetValue(DataSourceProperty);
set => SetValue(DataSourceProperty, value);
}
protected override void AddAttributesToRender(IHtmlWriter writer, IDotvvmRequestContext context)
{
Attributes.Add("class", "accordion");
base.AddAttributesToRender(writer, context);
}
public void DataBind(IDotvvmRequestContext context)
{
Children.Clear();
foreach (var item in DataSource)
{
DataBindItem(this, item, context);
}
}....etc
ViewModel
public List<ArticleSectionListDTO> AccordionList { get; set; } = new List<ArticleSectionListDTO>();
public List<ArticleSectionListDTO> AccordionListUnsorted { get; set; } = new List<ArticleSectionListDTO>();
protected override void OnItemLoading()
{
AccordionListUnsorted = Task.Run(() => articleSectionFacade.GetAllNotModifiedArticleSections()).Result;
AccordionList = Task.Run(() => articleSectionFacade.CreateTree(AccordionListUnsorted, null)).Result.ToList();
}
DTOs - I deleted rest of properties to make it clear
public class ArticleListDTO
{
public string Name { get; set; }
public int? ParentArticleId { get; set; }
public bool HasCategories => AssignedToArticle?.Count > 0;
public List<ArticleListDTO> AssignedToArticle { get; set; }
//Can contain sub articles
public List<ArticleListDTO> Articles { get; set; } = new List<ArticleListDTO>();
}
public class ArticleSectionListDTO : ListDTO
{
public string Name { get; set; }
public int? ParentArticleSectionId { get; set; }
public bool HasCategories => AssignedToMenuItem?.Count > 0;
public List<ArticleSectionListDTO> AssignedToMenuItem { get; set; }
public List<ArticleListDTO> Articles { get; set; } = new List<ArticleListDTO>();
}
The problem is that Repeater probably uses the client-rendering mode (it's the default). When it renders the HTML, it renders something like this:
<div data-bind="foreach: something">
<!-- template -->
</div>
When the template is rendered, its DataContext is null (becasue the template must not contain data from an item - it is a template).
So you have two options here:
Turn on server rendering by adding RenderSettings.Mode="Server" to the Repeater.
Update your control so it doesn't call DataBind when DataContext is null.

How to get selected checkbox in a controller using model

i am trying to get the value of selected check-box using model but not able to get as i want ;
Below is the table image
below is my code for this VIEW
And below is the code for result.I get null value
And below is my model declaration
public class RoleDetail
{
[Key]
public int RoleDetailID { get; set; }
public bool IsCreate { get; set; }
public bool IsDelete { get; set; }
public bool IsView { get; set; }
public bool IsEdit { get; set; }
public bool IsDownload { get; set; }
public string ControllerName { get; set; }
public System.DateTime CreateDate { get; set; }
public Int32 UserTypeId { get; set; }
}
public enum ControllerName
{
Account, Candidate, Career, ChooseUs, ContactUs, DocumentType, Employee, Gallery, GalleryType, GetTouch, Home, JobCategory, Jobs, Portfolio, ResumeUpload, RoleDetail, Services, User, UserRoleType
}
Replace the foreach loop in your view with a for:
#for (var i = 0; i < lst.Count; i++)
{
...
#Html.CheckBoxFor(x => lst[i].IsCreate)
#Html.CheckBoxFor(x => lst[i].IsView)
#Html.CheckBoxFor(x => lst[i].IsDelete)
...
}
For this to work make sure that the variable you are iterating over is an IList<T> or T[].
Also your controller action argument should be named accordingly:
public ActionResult Create(IEnumerable<RoleDetail> lst)
{
...
}
You should not be creating RoleDetail in the view. In the GET method create a List<RoleDetail>, populate it with the objects you want to display and return it to the view.
Controller
public ActionResult Create()
{
List<RoleDetail> model = new List<RoleDetail>();
// populate the collection, for example
foreach(var name in Enum.GetNames(typeof(ControllerName)))
{
model.Add(new RoleDetail()
{
ControllerName = name,
IsCreate = true // etc
});
}
return View(model);
}
public ActionResult Create(IEnumerable<RoleDetail> model)
{
}
View
#model List<RoleDetail>
#using(Html.BeginForm())
{
for(int i = 0; i < Model.Count; i++)
{
#Html.HiddenFor(m => m.ControllerName) // needed for postback
#Html.DisplayFor( => m.ControllerName)
#Html.CheckBoxFor(m => m.IsCreate)
....
}
<input type="submit" />
}
Side notes
Do not try to override the name (or value) attribute. The html
helper set these correctly for model binding (and in any case you
were only setting it to the value the helper generated anyway)
The reason the foreach loop does not work is your generating
duplicate name attributes (and also invalid html due to duplcate
id attributes). The for loop correctly generates the correct
names with indexers (e.g. <input name="[0].IsCreate " ..>, <input
name="[1].IsCreate " ..> etc.
You don't appear to be rendering controls for all of you model
properties so use a view model containing only those properties you
need to display/edit. What is a view model in MVC
You have public enum ControllerName so I suspect property
ControllerName in RoleDetail should be public ControllerName ControllerName { get; set; }?
And in future, post the code, not an image of it!

Grab post back collection and show it on list over webpage

I have an MVC webpage with a DropDownList full of items.
Every item is an object from my Database that represent a file on disk.
My object class:
namespace CapturesMVC.Models
public class Capture : IEquatable<Capture>
{
public int id { get; set; }
[Display(Name = "File Name")]
public string fileName { get; set; }
[Display(Name = "Browser")]
public string browser { get; set; }
[Display(Name = "Mobile")]
public string mobile { get; set; }
[Display(Name = "Protocol")]
public string protocol_site { get; set; }
public string family { get; set; }
public sealed override bool Equals(object other)
{
return Equals(other as Capture);
}
public bool Equals(Capture other)
{
if (other == null)
{
return false;
}
else
{
return this.protocol_site == other.protocol_site;
}
}
public override int GetHashCode()
{
return protocol_site.GetHashCode();
}
}
CaptureDBContext class:
namespace CapturesMVC.Models
public class CaptureDBContext : DbContext
{
public DbSet<Capture> Captures { get; set; }
}
This is my controller:
[HttpPost]
public ActionResult Index(string File)
{
var list = db.Captures.Where(x => x.protocol== File).ToArray();
ViewBag.Files = list;
return View();
}
Index.cshtml:
#using (Html.BeginForm())
{
<div>
#Html.DropDownList("File", new SelectList(ViewBag.Files, "protocol_site", "protocol_site"), "Select webmail site", new { style = "vertical-align:middle;" })
<button type="submit">Select</button>
</div>
}
</body>
After choosing an item from my DropDownList and hitting the button, the Index action is executed and returns list of objects that match one of my object properties and this list I want to show over my webpage inside a list, but the current situation is that this list is inserted into my DropDownList.
You want to implement Cascading DropDownList
check this example 'Cascading DropDownList in ASP.Net MVC' on msdn code or this on c-sharpcorner
The problem is that you put the objects in the same ViewBag property that your Dropdownlist gets its values from.
You could make a List and put that in your ViewBag:
List<Capture> list = db.Captures.Where(x => x.protocol== File).ToList();
ViewBag.data = list;
And enumerate over these and display some html in your view (within an unordered list for example). But you have to cast it back to a list first:
#using Namespace.Capture
...
<ul>
foreach (var item in (ViewBag.data as List<Capture>))
{
<li>#item.Property</li>
}
</ul>
ViewBag is a C# 4 dynamic type. You need to cast the entities from it to use them in a type-safe way.
But I would recommend using a view model with the list as a property and sending that to the view from your controller action.

Categories