Create a checkbox list from a List<SelectListItem> collection - checkboxlist

I worked on a project where I needed to create a checkbox list from a series of model values, and then retrieve the values from the controller on post. Unfortunately, unchecked checkboxes are simply not sent to the form collection on post, and testing each for null makes for clunky and ungainly code. But it turns out that adding hidden fields for the text and value of each SelectListItem makes grabbing the selected values on post a snap, so I built upon some code I found online for creating a checkbox list via a TagHelper. Works great, and I've included comments to show how the TagHelper is used in the view, how to register it, and how to collect the selected values in the controller. Hope it helps someone else.

/// <summary>
/// Creates a checkbox list that can be easily accessed in the model.
/// <para>Example use in view: <checkboxlist asp-items="#Model.SomeSelectList" asp-model-name="SomeSelectList" asp-container="ul" asp-item="li"></checkboxlist></para>
/// <para>Example registration in _ViewImports file: #addTagHelper *, SomeNamespace</para>
/// <para>Example of retrieving selected values from model: model.SomeSelectList.Where(sl => sl.Selected == true).Select(sl => sl.Value).ToList()</para>
/// </summary>
[HtmlTargetElement("checkboxlist", Attributes = "asp-items, asp-model-name, asp-container, asp-item")]
public class CheckboxListTagHelper : TagHelper
{
[HtmlAttributeName("asp-items")]
public IEnumerable<SelectListItem> Items { get; set; }
[HtmlAttributeName("asp-model-name")]
public string ModelName { get; set; }
[HtmlAttributeName("asp-container")]
public string Container { get; set; }
private string _containerId;
[HtmlAttributeName("asp-container-id")]
public string ContainerId
{
get { return !string.IsNullOrWhiteSpace(_containerId) ? $"id=\"{_containerId}\"" : ""; }
set { _containerId = value; }
}
private string _containerName;
[HtmlAttributeName("asp-container-name")]
public string ContainerName
{
get { return !string.IsNullOrWhiteSpace(_containerName) ? $"name=\"{_containerName}\"" : ""; }
set { _containerName = value; }
}
private string _containerClass;
[HtmlAttributeName("asp-container-class")]
public string ContainerClass
{
get { return !string.IsNullOrWhiteSpace(_containerClass) ? $"class=\"{_containerClass}\"" : ""; }
set { _containerClass = value; }
}
[HtmlAttributeName("asp-item")]
public string Item { get; set; }
private string _itemId;
[HtmlAttributeName("asp-item-id")]
public string ItemId
{
get { return !string.IsNullOrWhiteSpace(_itemId) ? $"id=\"{_itemId}\"" : ""; }
set { _itemId = value; }
}
private string _itemName;
[HtmlAttributeName("asp-item-name")]
public string ItemName
{
get { return !string.IsNullOrWhiteSpace(_itemName) ? $"id=\"{_itemName}\"" : ""; }
set { _itemName = value; }
}
private string _itemClass;
[HtmlAttributeName("asp-item-class")]
public string ItemClass
{
get { return !string.IsNullOrWhiteSpace(_itemClass) ? $"id=\"{_itemClass}\"" : ""; }
set { _itemClass = value; }
}
public override void Process(TagHelperContext context, TagHelperOutput output)
{
var sb = new StringBuilder();
sb.Append($#"<{Container} {ContainerId} {ContainerName} {ContainerClass}>");
var index = 0;
foreach (var item in Items)
{
var selected = item.Selected ? #"checked=""checked""" : "";
var disabled = item.Disabled ? #"disabled=""disabled""" : "";
sb.Append($#"<{Item} {ItemId} {ItemName} {ItemClass}>");
sb.Append($#"<input type=""checkbox"" {selected} {disabled} id=""{ModelName}_{index}__Selected"" name=""{ModelName}[{index}].Selected"" value=""true"" /> ");
sb.Append($#"<label for=""{ModelName}_{index}__Selected"">{item.Text}</label>");
sb.Append($#"<input type=""hidden"" id=""{ModelName}_{index}__Text"" name=""{ModelName}[{index}].Text"" value=""{item.Text}"">");
sb.Append($#"<input type=""hidden"" id=""{ModelName}_{index}__Value"" name=""{ModelName}[{index}].Value"" value=""{item.Value}"">");
sb.Append($#"</{Item}>");
index++;
}
sb.Append($#"</{Container}>");
output.Content.AppendHtml(sb.ToString());
}
}
My new project uses the full .NET Framework, which for some reason doesn't support TagHelpers, so I created an extension method for the SelectListItem collection. This is a stripped-down version of the TagHelper class I posted above, since I didn't need all the bells and whistles. I'm using Bootstrap 3.4.1 for scaffolding on the page that uses this extension method, so I used a default container of a div with class "form-check". Here's the code for the extension method:
public static string GenerateCheckboxList(this List<SelectListItem> selectListItems, string modelName)
{
var sb = new StringBuilder();
var index = 0;
foreach (var item in selectListItems)
{
var selected = item.Selected ? #"checked=""checked""" : "";
var disabled = item.Disabled ? #"disabled=""disabled""" : "";
sb.Append($#"<div class=""form-check"">");
sb.Append($#"<label for=""{modelName}_{index}__Selected"">{item.Text}</label> ");
sb.Append($#"<input type=""checkbox"" {selected} {disabled} id=""{modelName}_{index}__Selected"" name=""{modelName}[{index}].Selected"" value=""true"" class=""form-check-input"" />");
sb.Append($#"<input type=""hidden"" id=""{modelName}_{index}__Text"" name=""{modelName}[{index}].Text"" value=""{item.Text}"">");
sb.Append($#"<input type=""hidden"" id=""{modelName}_{index}__Value"" name=""{modelName}[{index}].Value"" value=""{item.Value}"">");
sb.Append($#"</div>");
index++;
}
return sb.ToString();
}
I forgot to mention... when rendering the generated string in the view, you need to include a call to render the raw content, or the rendering engine will encode the HTML and it will look bizarre. This is all that's needed in the view:
#Html.Raw(Model.CheckboxList)

Related

How to get Binding-Context on creating DataTemplate

Is there a way to get the Binding-Context itself in an enumerated template?
this.CollectionView.ItemTemplate = new DataTemplate(() =>
{
var view = new SubView();
view.SetBinding(SubView.SourceProperty, new Binding()
{
Source = ??? // <- here
});
return view;
};
Note:
Path = "." works fine on Android. but on iOS, the source is duplicated.
I have already checked that there are no duplicates in CollectionView.ItemsSource.
I'm new to the platform I hope I can help.
I understand that you want to get the BindingContext of each element in the list. If I am interpreting correctly, then this is what you can do:
public partial class NotificationsPage : ContentPageBase
{
public NotificationsPage()
{
InitializeComponent();
CollectionView.ItemTemplate = new DataTemplate(() =>
{
return new Item();
});
}
}
public class Item : ContentView
{
protected override void OnBindingContextChanged()
{
base.OnBindingContextChanged();
if (BindingContext is ItemModel item)
{
var name = item.Name;
}
}
}
public class ItemModel
{
public string Name { get; set; }
}
DataTemplate does not contain an accessible BindingContext, the BindingContext is passed to the element containing the DataTemplate.

Problem with checkbox helper returning null values ASP.NET MVC

Model:
public class ItemModel
{
public bool IsChecked { get; set; }
}
ViewModel:
public class CategoryItemViewModel
{
public List<ItemModel> Item { get; set; }
}
Index Controller:
public List<ItemModel> GetItemModel()
{
//Get fie path
var ItemFile = Server.MapPath("~/App_Data/Items.txt");
//Read from the Categories.txt file and display the contents in the List view
List<ItemModel> item = new List<ItemModel>();
//Read the contents of Category.txt file
string txtData = System.IO.File.ReadAllText(ItemFile);
//Execute a loop over the rows.
foreach (string row in txtData.Split('\n'))
{
if (!string.IsNullOrEmpty(row))
{
item.Add(new ItemModel
{
IsChecked = Convert.ToBoolean(row.Split(',')[0])
});
}
}
return item;
}
The code above is basically reading items in a text file and then setting them to the model.
I am having an issue when wanting to change the checked value of a checkbox, as the actual checkbox is returning a null value when I try check it, Problem code is below.
Controller to add a new line item with a checkbox:
[HttpPost]
public ActionResult Item(bool ItemCheck)
{
//Get file path of Categories.txt
var ItemFile = Server.MapPath("~/App_Data/Items.txt");
var ItemData = ItemCheck + Environment.NewLine;
System.IO.File.AppendAllText(ItemFile, ItemData);
return View();
}
bool ItemCheck is returning a null value.
Index View code:
foreach (var item in Model.Item)
{
#using (Html.BeginForm("Item", "Item", FormMethod.Post))
{
#Html.CheckBoxFor(ItemModel => ItemModel.IsChecked)
}
}
It is saying that ItemModel does not contain a definition for IsChecked.
This could be due to the following reasons
Referring wrong namespace for model, ItemModel. To ensure that you are using correct model use #model yourNamespace.ItemModel
Build issue. clean and build the project.

updating a document's complex property in DocumentDB in C#

When I make updates inside a property of SortedList type of a document (haven't checked other Dictionary / List types) Those updates are ignored by the serialization process (even by the ToString method of the object).
Example:
using Microsoft.Azure.Documents;
public class BusinessStats : Document
{
public BusinessStats()
{
}
public BusinessStats(int businessId)
{
this.Id = businessId.ToString();
Counts = new SortedList<int, int>();
}
/// <summary>
/// Business id
/// </summary>
public override string Id
{
get
{
return base.Id;
}
set
{
base.Id = value;
}
}
[JsonProperty()]
public SortedList<int, int> Counts { get; set; }
}
public static class Test
{
BusinessStats bizStats = DocumentDB.Client.CreateDocumentQuery<BusinessStats>(DocumentDB.BusinessStats.SelfLink).Where(s => s.Id == p.BusinessId.ToString()).ToArray().FirstOrDefault();
if (bizStats == null)
{
bizStats = new BusinessStats(p.BusinessId);
}
}
if ( ! bizStats.Counts.ContainsKey(1))
{
bizStats.Counts.Add(1, 10);
}
else
{
bizStats.Counts[1] = bizStats.Counts[1] + 1;
}
var updateOperation = await DocumentDB.Client.UpsertDocumentAsync(DocumentDB.BusinessStats.DocumentsLink, bizStats);
First time this runs and inserts a proper Json, second time it reads the proper json, and deserialization goes well.
then, after it updates Counts[1] to be 11, it doesn't effect the ToString() and serialization of bizStats.
DocumentDB's .NET SDK makes me sad

How do I find settable properties with Json.NET using a ContractResolver? [duplicate]

Is there a way to ignore get-only properties using the Json.NET serializer but without using JsonIgnore attributes?
For example, I have a class with these get properties:
public Keys Hotkey { get; set; }
public Keys KeyCode
{
get
{
return Hotkey & Keys.KeyCode;
}
}
public Keys ModifiersKeys
{
get
{
return Hotkey & Keys.Modifiers;
}
}
public bool Control
{
get
{
return (Hotkey & Keys.Control) == Keys.Control;
}
}
public bool Shift
{
get
{
return (Hotkey & Keys.Shift) == Keys.Shift;
}
}
public bool Alt
{
get
{
return (Hotkey & Keys.Alt) == Keys.Alt;
}
}
public Modifiers ModifiersEnum
{
get
{
Modifiers modifiers = Modifiers.None;
if (Alt) modifiers |= Modifiers.Alt;
if (Control) modifiers |= Modifiers.Control;
if (Shift) modifiers |= Modifiers.Shift;
return modifiers;
}
}
public bool IsOnlyModifiers
{
get
{
return KeyCode == Keys.ControlKey || KeyCode == Keys.ShiftKey || KeyCode == Keys.Menu;
}
}
public bool IsValidKey
{
get
{
return KeyCode != Keys.None && !IsOnlyModifiers;
}
}
Do I need to add [JsonIgnore] to all of them (I also have many other classes), or there is better way to ignore all get-only properties?
You can do this by implementing a custom IContractResolver and using that during serialization. If you subclass the DefaultContractResolver, this becomes very easy to do:
class WritablePropertiesOnlyResolver : DefaultContractResolver
{
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
IList<JsonProperty> props = base.CreateProperties(type, memberSerialization);
return props.Where(p => p.Writable).ToList();
}
}
Here is a test program demonstrating how to use it:
using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
class Program
{
static void Main(string[] args)
{
Widget w = new Widget { Id = 2, Name = "Joe Schmoe" };
JsonSerializerSettings settings = new JsonSerializerSettings
{
ContractResolver = new WritablePropertiesOnlyResolver()
};
string json = JsonConvert.SerializeObject(w, settings);
Console.WriteLine(json);
}
}
class Widget
{
public int Id { get; set; }
public string Name { get; set; }
public string LowerCaseName
{
get { return (Name != null ? Name.ToLower() : null); }
}
}
Here is the output of the above. Notice that the read-only property LowerCaseName is not included in the output.
{"Id":2,"Name":"Joe Schmoe"}
Use the OptIn mode of JSON.net and you'll only need to decorate the properties you want to serialize. This isn't as good as automatically opting out all read only properties, but it can save you some work.
[JsonObject(MemberSerialization.OptIn)]
public class MyClass
{
[JsonProperty]
public string serializedProp { get; set; }
public string nonSerializedProp { get; set; }
}
Udate: Added another possibility using reflection
If the above solution still isn't quite what you're looking for, you could use reflection to make dictionary objects which would then be serialized. Of course the example below will only work for simple classes, so you would need to add recursion if your classes contain other classes. This should at least point you in the right direction.
The subroutine to put the filtered result into a dictionary:
private Dictionary<String, object> ConvertToDictionary(object classToSerialize)
{
Dictionary<String, object> resultDictionary = new Dictionary<string, object>();
foreach (var propertyInfo in classToSerialize.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance))
{
if (propertyInfo.CanWrite) resultDictionary.Add(propertyInfo.Name, propertyInfo.GetValue(classToSerialize, null));
}
return resultDictionary;
}
A snippet showing its use:
SampleClass sampleClass = new SampleClass();
sampleClass.Hotkey = Keys.A;
var toSerialize = ConvertToDictionary(sampleClass);
String resultText = JsonConvert.SerializeObject(toSerialize);
You can use a contract resolver like this:
public class ExcludeCalculatedResolver : DefaultContractResolver
{
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
var property = base.CreateProperty(member, memberSerialization);
property.ShouldSerialize = _ => ShouldSerialize(member);
return property;
}
internal static bool ShouldSerialize(MemberInfo memberInfo)
{
var propertyInfo = memberInfo as PropertyInfo;
if (propertyInfo == null)
{
return false;
}
if (propertyInfo.SetMethod != null)
{
return true;
}
var getMethod = propertyInfo.GetMethod;
return Attribute.GetCustomAttribute(getMethod, typeof(CompilerGeneratedAttribute)) != null;
}
}
It will exclude calculated properties but include C#6 get only properties and all properties with a set method.
Json.net does have the ability to conditionally serialize properties without an attribute or contract resolver. This is especially useful if you don't want your project to have a dependency on Json.net.
As per the Json.net documentation
To conditionally serialize a property, add a method that returns boolean with
the same name as the property and then prefix the method name with
ShouldSerialize. The result of the method determines whether the
property is serialized. If the method returns true then the property
will be serialized, if it returns false then the property will be
skipped.

Bind Menu To a List in ASP.NET

How to bind a list to ASP.NET Menu control?
try something like this .
This is just example how you bind data to the menu control using asp.net.. you can bind list also same way like this....
Start with a IHierarcyData class that will store each string from the StringCollection...
public class MyMenuItem : IHierarchyData
{
public MyMenuItem(string s)
{
Item = s;
}
public override string ToString()
{
return Item.ToString();
}
#region IHierarchyData Members
public IHierarchicalEnumerable GetChildren()
{
return null;
}
public IHierarchyData GetParent()
{
return null;
}
public bool HasChildren
{
get { return false; }
}
public object Item
{
get; set;
}
public string Path
{
get { return string.Empty; }
}
public string Type
{
get { return string.Empty; }
}
#endregion
}
Build a class that will be the collection...
public class MyMenu : StringCollection, IHierarchicalEnumerable
{
List<IHierarchyData> _list = new List<IHierarchyData>();
public void Add(StringCollection strings)
{
foreach (string s in strings)
{
MyMenuItem i = new MyMenuItem(s);
_list.Add(i);
}
}
#region IHierarchicalEnumerable Members
public IHierarchyData GetHierarchyData(object enumeratedItem)
{
return enumeratedItem as IHierarchyData;
}
#endregion
#region IEnumerable Members
public System.Collections.IEnumerator GetEnumerator()
{
return _list.GetEnumerator();
}
#endregion
}
In the page you can now construct the menu...
MyMenu pos = new MyMenu();
StringCollection sc = new StringCollection();
sc.Add("First");
sc.Add("Second");
pos.Add(sc);
Menu1.DataSource = pos;
Menu1.DataBind();

Resources