C# asp.net dynamically populate <select> list - asp.net

.net 5.0
c#
Razor
I have been trying for a few days to dynamically populate a <select> list with 164 country names that sit in a DB column. I have worked through various sources online and 'nearly' have it working, but I'm stuck at the final hurdle.
The list populates one name at a time, overwriting the previous one each time. Only the final value from the DB column displays in the view.
HTML
<div class="form-group">
<label asp-for="Input.Country" class="font-heading" id="CountryHeading">#_loc[Model.CountryHeading]</label>
<div class="input-group">
<label asp-for="Input.Country" class="input-group-text" for="inputGroupCountrySelect">Country</label>
<select asp-for="Input.Country" class="form-control" asp-items="#Model.CountryList" id="inputGroupCountrySelect">
</select>
<br />
</div>
</div>
Controller
public IEnumerable<SelectListItem> CountryList { get; set; }
public class InputModel
{
[ExStringLength(100, MinimumLength = 1)]
[DataType(DataType.Text)]
[Display(Name = "Country")]
public string Country { get; set; }
}
public async Task<IActionResult> OnGetAsync(string email)
{
List<string> countriesList = GetCountriesList.GetCountries();
Debug.WriteLine("************** countriesListReturned: " + countriesList.ToString());
for (int i = 0; i < 195; i++)
{
try
{
Debug.WriteLine("************** i Returned: " + i.ToString());
Debug.WriteLine("************** countriesList[i]: " + countriesList[i]);
CountryList = new SelectListItem[]
{
new SelectListItem(countriesList[i], countriesList[i])
};
}
catch
{
Debug.WriteLine("************** break");
break;
}
}
[normal other code to populate the view goes here]
return Page();
}
GetCountriesList.cs - to retrieve list from DB
Note: I'm planning to change the DB usage anyway because it takes 19 seconds to loop through the table and retrieve the values, which is way too long. But I still need to know why its not working.
using MyApp.Data;
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
namespace MyApp.Areas.FunctionalLogic
{
public class GetCountriesList
{
private static readonly ApplicationDbContext _context = new();
public static List<string> GetCountries()
{
_context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
var Countries = _context.Countries.FirstOrDefault();
//Debug.WriteLine("************** Countries: " + Countries);
List<string> countriesList = new();
if (Countries != null)
{
for (int i = 0; i < 195; i++)
{
countriesList = _context.Countries.Where(c => c.Country != null).Select(c => c.Country).ToList();
Debug.WriteLine("************** countriesList a: " + countriesList[i]);
Debug.WriteLine("************** countriesList b: " + countriesList);
Debug.WriteLine("************** countriesList c: " + i);
}
}
return countriesList;
}
}
}
Any ideas why I'm just getting the last DB value in my dropdown list?

just do this
public List<SelectListItem> GetCountries()
{
return _context.Countries.Select(i=> new SelectListItem{
Value=i.Id.ToString,
Text=i.Name
} ).ToList();
}
use it
CountryList=GetCountries();

The problem is:
CountryList = new SelectListItem[]
{
new SelectListItem(countriesList[i], countriesList[i])
};
Every time you create a new list of items with one element. You should add all items to one list instead. Something like that:
public async Task<IActionResult> OnGetAsync(string email)
{
List<string> countriesList = GetCountriesList.GetCountries();
Debug.WriteLine("************** countriesListReturned: " + countriesList.ToString());
CountryList = countriesList.Select(x => new SelectListItem(x, x));
[normal other code to populate the view goes here]
return Page();
}

Related

Blazor InputText with Required attribute but Disabled

So I have a model, other properties removed for brevity:
public class OutOfLimitReasonViewModel
{
[Required]
public int ReasonId { get; set; }
[Required(ErrorMessage = "Other Reason is required")]
public string OtherReason { get; set; }
}
Of course I have an EditForm on the .razor page and the InputText I care about looks like this:
<InputText #bind-Value="model.OtherReason" class="form-control" disabled="#(!OtherReasonRequired)" />
<ValidationMessage For="#(() => model.OtherReason)" />
There is also a select that has the list of available Reason objects, one of which is Other.
I do have a property called OtherReasonRequired which graphically does what I want (either Enable or Disable the input based on if the selected Reason == "Other") so that code works:
public bool OtherReasonRequired
{
get
{
var result = false;
if (model.ReasonId > 0)
{
var reason = Reasons.Find(x => x.Id == model.ReasonId);
result = reason.Reason == "Other";
}
return result;
}
}
This works perfectly if I select Other and give OtherReason a value, the Save/Submit button is valid and it works.
My issue is when I have NOT selected Other. Graphically, the InputField does get disabled and grayed out. But the Save/Submit believes the model is invalid because there is no value in the OtherReason field.
Is there something I can do to get this to work?
Would love to have a dynamic attribute called RequiredIf.
But logically to me, I see this as a bug. If a control is disabled, it's value is irrelevant in my mind.
Please check the following. I don't use any of the built-in Data Annotation, but it's still very easy to check the input and let the user know what I want them to do:
<EditForm Model="#DisplayModel" OnValidSubmit="HandleValidSubmit" >
<InputRadioGroup TValue=int? #bind-Value=DisplayModel.ReasonID>
#for (int i=1; i< Reasons.Count(); i++){
<InputRadio Value=Reasons[i].ID /> #Reasons[i].Description <br/>
}
<InputRadio Value=0 />Other <br/>
</InputRadioGroup>
<input #bind=DisplayModel.OtherText disabled=#((DisplayModel.ReasonID??1) !=0 ) />
<button type="submit">Submit</button>
<div class="text-danger">#DisplayMessage</div>
</EditForm>
#code {
string DisplayMessage="";
class Reason { public int? ID; public string? Description; }
List<Reason> Reasons = new List<Reason> {
new Reason { ID = 0, Description = "Other"},
new Reason { ID = 1, Description = "You're lame" },
new Reason { ID = 2, Description = "I'm too busy" }
};
class MyDisplayModel
{
public int? ReasonID;
public string OtherText="";
}
MyDisplayModel DisplayModel = new MyDisplayModel();
private async Task HandleValidSubmit()
{
if(DisplayModel.ReasonID is not null) if (DisplayModel.ReasonID==0) if (DisplayModel.OtherText == "")
{
DisplayMessage = "You must type a description of your issue.";
StateHasChanged();
await Task.Delay(1500);
DisplayMessage = "";
}
}
}

Testing Model is valid

Working on a web app, MVC 5 in VS 2015.
Here is my model:
public class InfoFormulaireEmployeModele
{
[Required(ErrorMessage =" *You must provide a date")]
[RegularExpression("^[0-9]{8}$", ErrorMessage ="The date must be of the format AAAAMMJJ")]
public string dateEvenementInitial { get; set; }
}
When I try to enter some invalid input, let's say '12ab' , it works fine, I'm back on the page with the error message.
Here is my view:
#using PortailLibreService.Models
#model InfoFormulaireEmployeModele
#{
ViewBag.Title = "ChampsFormulaireInvalidite";
}
#using (Html.BeginForm())
{
<div class="editor-field">
<b>Initial event </b><br>
<p>When did the initial event occured?</p>
#Html.TextBoxFor(x => x.dateEvenementInitial, new { #placeholder = "AAAAMMJJ" }) #Html.ValidationMessageFor(x => x.dateEvenementInitial, null, new { #class = "error" })
</div>
<input type="submit" name="Save" value="Submit" />
}
I wanted to create a unit test to be sure that my model wouldn't be valid with an invalid date entry.
Here is my unit test:
[TestMethod]
public void testRetourPossibleInvalide()
{
InfoFormulaireEmployeModele modelDuplicat = new InfoFormulaireEmployeModele();
modelDuplicat.dateEvenementInitial = "abc";
InvFormController controlleur = lib.getController("100237");
var result = controlleur.ChampsFormulaireInvalidite(modelDuplicat);
Assert.AreEqual(false, controlleur.ModelState.IsValid);
}
For some reason the test fails, controlleur.ModelState.IsValid is true while it should be false. If I test it with only numerical values it's fine. For example, this test passed:
[TestMethod]
public void testRetourPossibleInvalide()
{
InfoFormulaireEmployeModele modelDuplicat = new InfoFormulaireEmployeModele();
modelDuplicat.dateRetourPossible = "129";
InvFormController controlleur = lib.getController("100237");
var result = controlleur.ChampsFormulaireInvalidite(modelDuplicat);
Assert.AreEqual(false, controlleur.ModelState.IsValid);
}
controlleur.ModelState.IsValid is false while running the test, so the test passes.
I tried to put a breakpoint inside my controller method but I can't reach it while debugging..
You would have to act as the framework, which usually does all the checking for you.
[TestMethod]
public void testRetourPossibleInvalide() {
var model = new InfoFormulaireEmployeModele();
model.dateEvenementInitial = "abc";
var controller = new InvFormController();
var bindingContext = new ModelBindingContext() {
ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, model.GetType()),
ValueProvider = new NameValueCollectionValueProvider(new NameValueCollection(), CultureInfo.InvariantCulture)
};
var boundModel = new DefaultModelBinder().BindModel(new ControllerContext(), bindingContext);
controller.ModelState.Clear();
controller.ModelState.Merge(bindingContext.ModelState);
var result = controller.ChampsFormulaireInvalidite(model);
Assert.AreEqual(false, controller.ModelState.IsValid);
}

How do I create a dynamic Linq query to fill an ASP.NET databound ListView?

I am having some trouble figuring out the right way to go about creating a dynamic query that I can use values from DropDownList controls to filter and sort/order the results of a database query to fill a ListView. I am able to hard code individual queries, which works ok, except for the fact that it takes an incredible amount of effort, and is not easily changed.
My code is as follows (using all filters):
queryResult = From product In myEntities.InventoryProducts
Where product.VendorID = ddlFilterVendor.SelectedValue And product.ItemType = ddlItemType.SelectedValue And product.LabelSize = ddlLabelSize.SelectedValue And product.PrintLabel = boolPrint And product.Edited = boolEdited
Order By product.ID Ascending
Select product
Return queryResult
Is there a better method to this? I would like to be able to select the value from each DropDownList and generate a custom WHERE clause, as well as an ORDER BY clause.
Any help would be greatly appreciated, thanks.
I can give you a simple example as to how to to proceed with your idea. I am sure if you look through StackOverflow or search via google you will get code that does a better job of dynamic expression building. The same concept can be used for order by.
void Main()
{
var ops = new List<Ops>
{
new Ops
{
OperandType = typeof(string),
OpType=OpType.Equals,
OperandName = "Name",
ValueToCompare = "MM" // in your case this will be the values from the dropdowns
},
new Ops
{
OperandType = typeof(int),
OpType=OpType.Equals,
OperandName = "ID",
ValueToCompare = 1
},
};
var testClasses = new List<TestClass>
{
new TestClass { ID =1, Name = "MM", Date = new DateTime(2014,12,1)},
new TestClass { ID =2, Name = "BB", Date = new DateTime(2014,12,2)}
};
// this will produce prop => ((prop.Name == "MM") And (prop.ID == 1))
var whereDelegate = ExpressionBuilder.BuildExpressions<TestClass>(ops);
foreach(var item in testClasses.Where(whereDelegate))
{
Console.WriteLine("ID " +item.ID);
Console.WriteLine("Name " +item.Name);
Console.WriteLine("Date" + item.Date);
}
}
// Define other methods and classes here
public enum OpType
{
Equals
}
public class Ops
{
public Type OperandType {get; set;}
public OpType OpType {get; set;}
public string OperandName {get;set;}
public object ValueToCompare {get;set;}
}
public class TestClass
{
public int ID {get;set;}
public string Name {get; set;}
public DateTime Date {get;set;}
}
public class ExpressionBuilder
{
public static Func<T,bool> BuildExpressions<T>( List<Ops> opList)
{
Expression currentExpression= null;
var parameterExpression = Expression.Parameter(typeof(T), "prop");
for(int i =0; i< opList.Count; i++)
{
var op = opList[i];
Expression innerExpression = null;
switch(op.OpType)
{
case OpType.Equals :
{
var propertyExpression = Expression.Property(parameterExpression ,
op.OperandName);
var constExpression = Expression.Constant(op.ValueToCompare);
innerExpression = Expression.Equal(propertyExpression,
constExpression);
break;
}
}
if (i >0)
{
currentExpression = Expression.And(currentExpression, innerExpression);
}
else
{
currentExpression = innerExpression;
}
}
var lambdaExpression = Expression.Lambda<Func<T,bool>>(currentExpression,
new []{parameterExpression });
Console.WriteLine(lambdaExpression);
return lambdaExpression.Compile() ;
}
}

e.GetListSourceFieldValue returns null in the CustomUnboundColumnData event handler

I am using DevExpress control within an ASP.NET MVC 4 project.
I am using unbound columns in the GridView extension for ASP.NET MVC. In the CustomUnboundColumnData event handler, the e.GetListSourceFieldValue always returns null for me.
I then tried getting the value directly from the model instead of calling this method (please see the commented line just above the call to the method) but even though that worked, it had side-effects, which I won't get into just now.
I am using ASP.NET MVC 4 with Visual Web Developer Express 2010 edition. I am using DevExpress extensions for MVC v12.2.10.0.
My OS is Windows 7, 64-bit. However, the extensions I am using are 32-bit only.
I cannot ship my whole solution as it is broken down into multiple projects, most of which have lots of IP code I've written for my client. But here are the relevant pieces from my code.
Index.cshtml (Razor View Engine)
-------------------------------------------------------------------
#model List<GlobalizationUI.Presentation.ViewModels.StringTableRow>
#{
ViewBag.Title = "Strings";
}
<div id = "pageCaption">Strings</div>
#Html.Partial("_StringsPartial", Model)
_StringsPartial.cshtml (Razor View Engine)
-------------------------------------------------------------------
#using System.Web.UI.WebControls;
#using System.Data;
#model List<GlobalizationUI.Presentation.ViewModels.StringTableRow>
#Html.DevExpress().GridView(settings =>
{
settings.Name = "gvStrings";
settings.CallbackRouteValues = new { Controller = "Strings", Action = "StringsPartial" };
settings.Width = 1200;
settings.SettingsPager.Position = PagerPosition.TopAndBottom;
settings.SettingsPager.FirstPageButton.Visible = true;
settings.SettingsPager.LastPageButton.Visible = true;
settings.SettingsPager.PageSizeItemSettings.Visible = true;
settings.SettingsPager.PageSizeItemSettings.Items = new string[] { "10", "20", "50", "100", "200" };
settings.SettingsPager.PageSize = 50;
settings.Settings.ShowFilterRow = true;
settings.Settings.ShowFilterRowMenu = true;
settings.CommandColumn.Visible = true;
settings.CommandColumn.ClearFilterButton.Visible = true;
settings.Settings.ShowHeaderFilterButton = true;
settings.KeyFieldName = "ResourceKeyId";
settings.Columns.Add("Key");
var categoryColumn = settings.Columns.Add("CategoryId", "Category");
categoryColumn.ColumnType = MVCxGridViewColumnType.ComboBox;
var categoryColumnEditProperties = categoryColumn.PropertiesEdit as ComboBoxProperties;
categoryColumnEditProperties.DataSource = ViewBag.AllCategories;
categoryColumnEditProperties.TextField = "Name";
categoryColumnEditProperties.ValueField = "Id";
categoryColumnEditProperties.ValueType = typeof(long);
if (Model != null && Model.Count > 0 &&
Model[0] != null && Model[0].StringValues != null && Model[0].StringValues.Count > 0)
{
foreach (var kvp in Model[0].StringValues)
{
settings.Columns.Add(col =>
{
col.FieldName = kvp.CultureShortName;
col.Caption = kvp.CultureShortName;
col.UnboundType = DevExpress.Data.UnboundColumnType.Object;
col.SetDataItemTemplateContent(container => { ViewContext.Writer.Write(DataBinder.Eval(container.DataItem, col.FieldName + ".StringValue")); });
col.SetEditItemTemplateContent(container =>
{
Html.DevExpress().TextBox(s =>
{
s.Name = string.Format("txt{0}", kvp.CultureShortName);
}).Bind(kvp.StringValue).Render();
});
});
}
}
settings.CustomUnboundColumnData = (sender, e) =>
{
var fixedColumns = new List<string> { "ResourceKeyId", "Key", "CategoryId" };
if (!fixedColumns.Contains(e.Column.FieldName))
{
if (e.IsGetData)
{
try
{
// var values = Model[e.ListSourceRowIndex].StringValues;
var values = e.GetListSourceFieldValue(e.ListSourceRowIndex, "StringValues") as IList<GlobalizationUI.Presentation.ViewModels.CultureNameAndStringValue>;
if (values != null)
{
var value = values.FirstOrDefault(pair => pair.CultureShortName == e.Column.FieldName);
var defaultValue = default(GlobalizationUI.Presentation.ViewModels.CultureNameAndStringValue);
e.Value = value.Equals(defaultValue) ? defaultValue : new GlobalizationUI.Presentation.ViewModels.CultureNameAndStringValue();
}
}
catch (Exception ex)
{
System.Diagnostics.Debugger.Break();
System.Diagnostics.Debug.Print(ex.ToString());
}
}
}
};
foreach (GridViewDataColumn column in settings.Columns)
{
column.Settings.HeaderFilterMode = HeaderFilterMode.CheckedList;
}
settings.SettingsEditing.AddNewRowRouteValues = new { Controller = "Strings", Action = "CreateNew" };
settings.SettingsEditing.UpdateRowRouteValues = new { Controller = "Strings", Action = "Edit" };
settings.SettingsEditing.DeleteRowRouteValues = new { Controller = "Strings", Action = "Delete" };
settings.SettingsEditing.Mode = GridViewEditingMode.Inline;
settings.SettingsBehavior.ConfirmDelete = true;
settings.CommandColumn.Visible = true;
settings.CommandColumn.NewButton.Visible = true;
settings.CommandColumn.EditButton.Visible = true;
settings.CommandColumn.UpdateButton.Visible = true;
settings.CommandColumn.DeleteButton.Visible = true;
}).Bind(Model).GetHtml()
StringsController
-------------------------------------------------------------------
using System.Data;
using System.Web.Mvc;
using GlobalizationUI.BusinessObjects;
using Resources.BaseServices.Caching;
using System.Collections.Generic;
using System.ComponentModel;
using GlobalizationUI.Presentation.ViewModels;
using Resources.Util;
namespace GlobalizationUI.Presentation.Controllers
{
public class StringsController : Controller
{
private static string CacheKey_StringTable = "CacheKey_StringTable";
private static string CacheKey_AllCategories = "CacheKey_AllCategories";
private static object padLock = new object();
public ActionResult Index()
{
var stringTable = GetStringTable();
ViewBag.AllCategories = GetCategoryList();
return View(stringTable);
}
public ActionResult StringsPartial()
{
var stringTable = GetStringTable();
ViewBag.AllCategories = GetCategoryList();
return PartialView("_StringsPartial", stringTable);
}
[HttpPost]
public ActionResult CreateNew(StringTableRow row)
{
System.Diagnostics.Debugger.Break();
foreach (PropertyDescriptor prop in TypeDescriptor.GetProperties(row))
{
System.Diagnostics.Debug.Print(prop.Name);
}
return Content("Hello, there!");
}
[HttpPost]
public ActionResult Edit(DataRow row)
{
return new EmptyResult();
}
[HttpPost]
public ActionResult Delete(long resourceKeyId)
{
return new EmptyResult();
}
private IEnumerable<Category> GetCategoryList()
{
lock (padLock)
{
if (CacheManager.Contains(CacheKey_AllCategories))
{
return CacheManager.Get<IEnumerable<Category>>(CacheKey_AllCategories);
}
}
var list = Category.All;
lock (padLock)
{
CacheManager.Add(CacheKey_AllCategories, list);
}
return list;
}
private List<StringTableRow> GetStringTable()
{
List<StringTableRow> stringTable;
lock (padLock)
{
if (CacheManager.Contains(CacheKey_StringTable))
{
return CacheManager.Get<List<StringTableRow>>(CacheKey_StringTable);
}
}
stringTable = new StringTable().ToListOfStringTableRows();
lock (padLock)
{
CacheManager.Add(CacheKey_StringTable, stringTable);
}
return stringTable;
}
}
}
View Models
-------------------------------------------------------------------
using System.Collections.Generic;
namespace GlobalizationUI.Presentation.ViewModels
{
public class StringTableRow
{
public long ResourceKeyId { get; set; }
public string Key { get; set; }
public long CategoryId { get; set; }
public List<CultureNameAndStringValue> StringValues { get; set; }
}
}
namespace GlobalizationUI.Presentation.ViewModels
{
public class CultureNameAndStringValue
{
public CultureNameAndStringValue() : this(null, null) { }
public CultureNameAndStringValue(string cultureShortName, string stringValue)
{
CultureShortName = cultureShortName;
StringValue = stringValue;
}
public string CultureShortName { get; set; }
public string StringValue { get; set; }
}
}
Model:
-------------------------------------------------------------------
using System.Data;
using GlobalizationUI.Data;
namespace GlobalizationUI.BusinessObjects
{
public class StringTable : DataTable
{
public StringTable()
{
var sql = #"
declare #stmt nvarchar(max)
select #stmt =
isnull(#stmt + ', ', '') +
'max(case when s.CultureId = ' + cast(c.Id as nvarchar(max)) +
' then s.ResourceValue end) as ' + quotename(c.ShortName)
from Culture as c
where c.Supported = 1
select #stmt = '
select
rk.Id AS ResourceKeyId,
rk.Name AS [Key],
c.Id AS CategoryId,
c.Name as CategoryName, ' + #stmt + '
from StringCategory as sc
LEFT OUTER join Category as c on c.Id = sc.CategoryId
RIGHT OUTER JOIN ResourceKey as rk on rk.Id = sc.ResourceKeyId
inner join Strings as s on s.ResourceKeyId = rk.Id
group by rk.Id, rk.Name, c.Id, c.Name
'
exec sp_executesql #stmt = #stmt;";
this.Merge(Database.DefaultDatabase.GetDataTable(sql));
}
}
}
Model to View Model conversion:
-------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Data;
using System.Data.SqlTypes;
namespace GlobalizationUI.Presentation.ViewModels
{
public static class DataTableExtensions
{
public static List<StringTableRow> ToListOfStringTableRows(this DataTable dataTable)
{
var ret = new List<StringTableRow>();
if (dataTable == null || dataTable.Rows.Count == 0) return ret;
foreach (DataRow row in dataTable.Rows)
{
StringTableRow stringTableRow = new StringTableRow();
foreach (DataColumn column in dataTable.Columns)
{
if (string.Compare(column.ColumnName, "ResourceKeyId", true) == 0)
{
stringTableRow.ResourceKeyId = (long)row[column.ColumnName];
}
else if (string.Compare(column.ColumnName, "Key", true) == 0)
{
stringTableRow.Key = (string)row[column.ColumnName];
}
else if (string.Compare(column.ColumnName, "CategoryId", true) == 0)
{
var categoryId = row[column.ColumnName];
stringTableRow.CategoryId = categoryId == DBNull.Value ? 0 : (long)categoryId;
}
else if (string.Compare(column.ColumnName, "CategoryName", true) == 0)
{
continue;
}
else
{
if (stringTableRow.StringValues == null)
stringTableRow.StringValues = new List<CultureNameAndStringValue>();
stringTableRow.StringValues.Add(new CultureNameAndStringValue(column.ColumnName, (string)row[column.ColumnName]));
}
}
ret.Add(stringTableRow);
}
return ret;
}
}
}
The user interface is supposed to look somewhat like the the picture shown above. The number of columns returned by the SQL query is variable depending upon the number of cultures supported by a particular installation/deployment/business client. Hence the need for using unbound columns.
I took this picture not through this code but through an older version of my code when I bound data directly to a System.Data.DataTable and did not use any unbound columns. That was all well until I had to edit the data in the actions at the MVC/server side. So, I switched from using a DataTable to a POCO and used unbound columns for all cultures.
Kindly help.
Okay, though I posted this question only 2 hours or so ago, I've had this trouble for over 8 hours now and have been trying various ways to solve this problem.
And just now, one of the things I did made this problem go away. Here it is.
If you are binding your DevExpress GridViewExtension for ASP.NET MVC to a custom POCO like mine, and that POCO has one or more members that are collections of any kind, then, you must and absolutely must initialize those properties that represent collections in the constructor of your POCO.
For e.g., in my case, the model was a list of StringTableRow's, where StringTableRow was a POCO.
In the StringTableRow class, I had a property named StringValues which was of type IList<CultureNameAndStringValue>, which is a collection. Therefore, for my code to work, I initialized the StringValues property in the construct of my StringTableRow class and everything started to work.
using System.Collections.Generic;
namespace GlobalizationUI.Presentation.ViewModels
{
public class StringTableRow
{
public StringTableRow()
{
// I added this ctor and initialized the
// StringValues property and the broken
// custom binding to unbound columns started
// to work.
StringValues = new List<CultureNameAndStringValue>();
}
public long ResourceKeyId { get; set; }
public string Key { get; set; }
public long CategoryId { get; set; }
public IList<CultureNameAndStringValue> StringValues { get; set; }
}
}

ASP.Net MVC 3 - CheckBoxList - Need some suggestions

I am pretty new to ASP.Net MVC (and razor) and I have a few questions.
1)
I created an HTML extension to create a check box list as below:
public static HtmlString CheckBoxList(this HtmlHelper htmlHelper, string name, List<InputItemInfo> ItemInfo)
{
if (String.IsNullOrEmpty(name))
throw new ArgumentException("The argument must have a value", "name");
if (ItemInfo == null)
throw new ArgumentNullException("ItemInfo");
if (ItemInfo.Count < 1)
throw new ArgumentException("The list must contain at least one value", "ItemInfo");
StringBuilder sb = new StringBuilder();
ItemInfo.Insert(0, new InputItemInfo("*", "Select All", ItemInfo.All(i => i.IsChecked)));
foreach (InputItemInfo info in ItemInfo)
{
TagBuilder builder = new TagBuilder("input");
if (info.IsChecked) builder.MergeAttribute("checked", "checked");
builder.MergeAttribute("type", "checkbox");
builder.MergeAttribute("value", info.Value);
builder.MergeAttribute("name", name);
builder.InnerHtml = info.DisplayText;
sb.Append(builder.ToString(TagRenderMode.Normal));
sb.Append("<br />");
}
return new HtmlString(sb.ToString());
}
I was able to use this in my views and also get the values in the controller as shown below:
#model List<AppTest.Models.InputExtensionsViewModel>
#{
ViewBag.Title = "Check";
}
<h2>Check</h2>
#using (Html.BeginForm())
{
<table border="0" style="border:0px;">
<tr>
<td valign="top">
#Html.Partial("CheckBoxList", Model[0])
</td>
</tr>
</table>
<br />
<input type="submit" value="Go" />
}
<div style="font-weight:bolder">
#ViewData["data"]
</div>
controller:
public ActionResult Check()
{
var model = new List<InputExtensionsViewModel>();
var model1 = new InputExtensionsViewModel
{
Title = "Facilities",
InputElementName = "facilities",
InputElements = // a list
};
model.Add(model1);
return View(model);
}
[HttpPost]
public ActionResult Check(string[] facilities)
{
...
}
The model is:
public class InputExtensionsViewModel
{
public string Title { get; set; }
public string InputElementName { get; set; }
public List<InputItemInfo> InputElements { get; set; }
public void SetSelected(string[] items)
{
if (items == null)
return;
this.InputElements.ForEach(delegate(InputItemInfo info)
{
if (items.Contains(info.Value))
info.IsChecked = true;
});
}
}
My question is, is there a way by which I could bind the array items to a property in the InputExtensionsViewModel model? If I just add a property called facilities to the view model, it's not bound automatically and I can understand why, as I am not binding that in my view. But I cannot seem to think of a way by which I could do that.
This check box list is a user control and I just wanted to avoid having too many string[] array for my action methods.
[EDIT] - Okay, I was able to do this when I tried now. Not sure why it didn't work before.
2) And, I was checking for alternatives and found out this answer in SO:
CheckboxList in MVC3.0
And I was able to replicate this but my question is, how do i bind a label to this checkbox? My labels are dynamic and part of the model and so cannot be hard-coded. I was trying to use a Html.LabelFor but that didn't work. In the editor template, if I just #Model.Text, it won't work and will be lost after a post-back as its not bound to a property
I googled and found suggestions to create HTML helpers which is what I did earlier (my 1st question is about that).
Please let me know if something is unclear. I could elaborate. Any input is appreciated!
Thanks in advance!
Ah, I found the solutions!
1) As indicated in my edit - adding a property with a similar name to a model and using it in the [HttpPost] enabled action method works fine. Guess last time I missed the getter and setters.
2) For this, in the editor template for MyViewModel, we just need to add this (** and **, needless to say, remove **!):
#model AppName.Models.MyViewModel
#Html.HiddenFor(x => x.Id)
#Html.CheckBoxFor(x => x.IsChecked) **#Model.Text
#Html.HiddenFor(x => x.Text)**
EDIT:
I have changed this template to do more. Now there is a label control and it is associated to the checkboxes through jquery as shown below.
#model EncorPlusTest.Infrastructure.InputItemInfo
#Html.HiddenFor(model => model.Value)
#Html.CheckBoxFor(model => model.IsChecked) <label for="">#Model.Text</label>
#Html.HiddenFor(model => model.Text)
<br />
Then in jquery:
$('input:checkbox').each(function () {
var lbl = $(this).next('input:hidden').next('label');
var forID = $(this).attr('id');
$(lbl).attr('for', forID);
});
Hope its helpful for others!
To answer part 2, you can easily add your label text as a property such as:
public class MyViewModel
{
public int Id { get; set; }
public bool IsChecked { get; set; }
public string Text { get; set; }
}
Then your template would look similar to this:
#model AppName.Models.MyViewModel
#Html.HiddenFor(x => x.Id)
#Html.CheckBoxFor(x => x.IsChecked)
#Html.LabelFor(x => x.Text)
The only downside to the above is that the label wouldn't be linked directly to the checkbox. You can accomplish this by doing something such as: CheckboxList in MVC3
Depending on the chance for re-usability, you can always create your own HtmlHelper as you were doing in the first part of this and wrap in the suggestions from the URL I pasted above.
You don't jQuery to solve this problem. If you wrap the input with a label you get the same behavior.
By the way, another option, instead of a editor template, is a HTML Helper. Take a look at this:
public static class HtmlHelperExtensions
{
#region CheckBoxList
public static MvcHtmlString CheckBoxList(this HtmlHelper htmlHelper, string name, List<SelectListItem> listInfo)
{
return htmlHelper.CheckBoxList(name, listInfo, ((IDictionary<string, object>)null));
}
public static MvcHtmlString CheckBoxList(this HtmlHelper htmlHelper, string name, List<SelectListItem> listInfo, object htmlAttributes)
{
return htmlHelper.CheckBoxList(name, listInfo, ((IDictionary<string, object>)new RouteValueDictionary(htmlAttributes)));
}
public static MvcHtmlString CheckBoxList(this HtmlHelper htmlHelper, string name, List<SelectListItem> selectListItems, IDictionary<string, object> htmlAttributes)
{
// Verify arguments
if (String.IsNullOrEmpty(name))
throw new ArgumentNullException("name", "Name cannot be null");
if (selectListItems == null)
throw new ArgumentNullException("selectList", "Select list cannot be null");
if (selectListItems.Count() < 1)
throw new ArgumentException("Select list must contain at least one value", "selectList");
// Define items
StringBuilder items = new StringBuilder();
int index = 0;
// Loop through items)
foreach (SelectListItem i in selectListItems)
{
// hidden value
TagBuilder hiddenValue = new TagBuilder("input");
hiddenValue.MergeAttribute("type", "hidden");
hiddenValue.MergeAttribute("value", i.Value);
hiddenValue.MergeAttribute("id", string.Format("{0}_{1}__Value", name, index));
hiddenValue.MergeAttribute("name", string.Format("{0}[{1}].Value", name, index));
// check box
TagBuilder checkbox = new TagBuilder("input");
if (i.Selected)
checkbox.MergeAttribute("checked", "checked");
checkbox.MergeAttribute("id", string.Format("{0}_{1}__Selected", name, index));
checkbox.MergeAttribute("name", string.Format("{0}[{1}].Selected", name, index));
checkbox.MergeAttribute("type", "checkbox");
checkbox.MergeAttribute("value", "true");
// wrapper label
TagBuilder wrapperLabel = new TagBuilder("label");
wrapperLabel.InnerHtml = checkbox.ToString(TagRenderMode.SelfClosing);
wrapperLabel.InnerHtml += i.Text;
// hidden selected
TagBuilder hiddenSelected = new TagBuilder("input");
hiddenSelected.MergeAttribute("type", "hidden");
hiddenSelected.MergeAttribute("value", i.Selected.ToString().ToLower());
hiddenSelected.MergeAttribute("name", string.Format("{0}[{1}].Selected", name, index));
// label for checkbox
TagBuilder checkBoxLabel = new TagBuilder("label");
checkBoxLabel.MergeAttribute("for", checkbox.Attributes["id"]);
checkBoxLabel.MergeAttribute("id", string.Format("{0}_{1}__Text", name, index));
checkBoxLabel.MergeAttribute("name", string.Format("{0}[{1}].Text", name, index));
// hidden text
TagBuilder hiddenText = new TagBuilder("input");
hiddenText.MergeAttribute("type", "hidden");
hiddenText.MergeAttribute("value", i.Text);
hiddenText.MergeAttribute("id", string.Format("{0}_{1}__Text", name, index));
hiddenText.MergeAttribute("name", string.Format("{0}[{1}].Text", name, index));
// Add item
items.AppendLine(hiddenValue.ToString(TagRenderMode.SelfClosing));
items.AppendLine(wrapperLabel.ToString(TagRenderMode.Normal));
items.Append(hiddenSelected.ToString(TagRenderMode.SelfClosing));
items.AppendLine(hiddenText.ToString(TagRenderMode.SelfClosing));
items.AppendLine();
index++;
}
return MvcHtmlString.Create(items.ToString());
}
public static MvcHtmlString CheckBoxListFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression)
{
var name = ExpressionHelper.GetExpressionText(expression);
var metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
return CheckBoxList(htmlHelper, name, metadata.Model as List<SelectListItem>);
}
#endregion
}

Resources