CheckBoxList does not update the model - asp.net-mvc-3

I defined a Person entity:
public partial class Person
{
public string persID { get; set; }
public string last_name { get; set; }
public string driving_licence { get; set; }
}
where the driving licence is as follows:
public class DrivingLicence
{
public string drivingLicenceValue { get; set; }
public string drivingLicenceText { get; set; }
public DrivingLicence(string paValue, string paText)
{
drivingLicenceValue = paValue;
drivingLicenceText = paText;
}
}
having a repository where is defined this function:
public List<DrivingLicence> GetAll()
{
try
{
var drivingLicenceList = new List<DrivingLicence>();
DrivingLicence oneDrivingLicence = new DrivingLicence("A", "A");
drivingLicenceList.Add(oneDrivingLicence );
oneDrivingLicence = new DrivingLicence("B", "B");
drivingLicenceList.Add(oneDrivingLicence );
oneDrivingLicence = new DrivingLicence("C", "C");
drivingLicenceList.Add(oneDrivingLicence );
oneDrivingLicence = new DrivingLicence("D", "D");
drivingLicenceList.Add(oneDrivingLicence );
return drivingLicenceList;
}
catch (Exception)
{
throw new Exception("An error occured. Failed to Get the list.");
}
}
Now: I want the driving licences displayed as a CheckBoxList and on submit I want the person to get assigned the checked driving licence categories, e.g.: the "A" and "C" categories are selected, the resulting person.driving_licence must be "AC".
The problem is that this does not happen, the person is created but the driving_licence property is empty. I payed attention that the check boxes name be identical to that of the corresponding property (Person.driving_licence).
Is that an error in the present code? Or should I modify the Person entity?
Thank you for your advice.
Here is the view model:
public class PersonFormViewModel
{
// Properties
public Person person { get; set; }
public SelectList DrivingLicenceList { get; set; }
public string ActionToPerform { get; set; }
public PersonFormViewModel() { }
// Constructor
public PersonFormViewModel(Person pPerson, SelectList pDrivingLicenceList)
{
person= pPerson;
DrivingLicenceList = pDrivingLicenceList;
if (String.IsNullOrEmpty(person.persID))
{
ActionToPerform = "Create";
}
else
{
ActionToPerform = "Edit";
}
}
}
The controller:
//
// GET: /Person/Create
[Authorize]
public ActionResult Create()
{
Person person = new Person();
SelectList drvLicenceList = new SelectList(drvLicenceRepository.GetAll(), "drivingLicenceValue", "drivingLicenceText");
return View("Create", new PersonFormViewModel(person, drvLicenceList));
}
//
// POST: /Person/Create
[HttpPost, Authorize]
public ActionResult Create(PersonFormViewModel model)
{
Person person = model.person;
SelectList drvLicenceList = new SelectList(drvLicenceRepository.GetAll(), "drivingLicenceValue", "drivingLicenceText");
if (ModelState.IsValid)
{
try
{
db.Entry(person).State = EntityState.Added;
db.SaveChanges();
return RedirectToAction("Details");
}
catch (...)
{
...
}
}
return View("Create", new PersonFormViewModel(person, drvLicenceList));
}
And the view:
#model MyApp.ViewModels.PersonFormViewModel
#{
ViewBag.Title = "Create";
}
#using (Html.BeginForm())
{
#Html.ValidationSummary(false, "Errors occured.")
<fieldset>
<legend>Fill in your details</legend>
#Html.LabelFor(model => model.person.last_name)
#Html.TextBoxFor(model => model.person.last_name)
#Html.ValidationMessageFor(model => model.person.last_name, "*")
#Html.HiddenFor(model => model.person.persID)
#foreach (var ctg in (Model.DrivingLicenceList))
{
<input type="checkbox" name="driving_licence" value=ctg.value />#ctg.Text
}
<input type="submit" value="Sauvegarder" class="submit" />
</fieldset>
}

I would use a collection property in order to store the selected driving licence categories (multiple checkboxes can be selected => collection):
public partial class Person
{
public string persID { get; set; }
public string last_name { get; set; }
public string[] driving_licence { get; set; }
}
and then you will need to fix the name of the checkbox in order for it to bind correctly:
#foreach (var ctg in Model.DrivingLicenceList)
{
<input type="checkbox" name="person.driving_licence" value="#ctg.Value" />
#ctg.Text
}
and if you wanted to preserve the selected values you will need to set the checked property accordingly:
#foreach (var ctg in Model.DrivingLicenceList)
{
<input type="checkbox" name="person.driving_licence" value="#ctg.Value" #((Model.person.driving_licence ?? Enumerable.Empty<string>()).Contains(ctg.Value) ? "checked=\"checked\"" : "") />
#ctg.Text
}
This being said, we now have a working solution but it is far from anything I would content myself with and stop here. From now on we could start refactoring this mess in order to comply with C# naming conventions (things like property names start with capital letter, ...), introduce real view models (which do not reference domain models), custom HTML helpers that will generate this checkbox lists to avoid writing loops in the views and hardcoding checkboxes, ...

Related

Passing property delegate into Blazor component giving 'does not take 1 argument' error

I am trying to pass a property as a delegate into a Blazor component. However, I get a Delegate 'Func<Pen, int, stirng>' does not take 1 argument error. And uncertain what the correct syntax should be.
This is the component:
#typeparam TItem
<div class="MarelAutoComplete">
<div class="MarelAutoCompleteList">
#foreach (var item in Data.Select(Property))
{
...
}
</div>
</div>
#code {
[Parameter]
public List<TItem> Data { get; set; }
[Parameter]
public Func<TItem, string> Property { get; set; }
}
Below is the calling Code:
<MyComponent TItem="Pen"
TValue="myVal"
Data="Pens"
Property="pen => pen.PenNumber" /> // Error On This Line.
#code {
List<Pen> Pens { get; set; }
}
The error you report is Delegate 'Func<Pen, int, string>' does not take 1 argument yet Property is defined as Func<TItem, string>.
Func<TItem, string> Property says I want a function that looks like this:
string MyMethod(TItem item)
Your anonymous function is pen => pen.PenNumber, with Property expecting pen.PenNumber as a string. Is it?
Here's a simplified working version of what you're doing:
TextBox.razor
#typeparam TItem
<h3>#Value</h3>
#foreach (var item in Data.Select(this.Property))
{
<div>#item</div>
}
#code {
[Parameter] public string Value { get; set; } = "Bonjour";
[Parameter] public Func<TItem, string> Property { get; set; }
[Parameter] public List<TItem> Data { get; set; }
}
Index.razor
#page "/"
#using Blazor.Starter.Data
<div>
<TextBox TItem="Model" Data="models" Property="item => item.Value"></TextBox>
</div>
#code {
public string GetProperty(Model model)
{
return model.Value;
}
public List<Model> models => new List<Model>()
{
new Model() { Value = "Fred"},
new Model() { Value = "Jon"},
new Model() { Value = "Bob"},
};
public class Model
{
public string Value { get; set; }
}
}
The issue was a simple syntax issue. When defining input functions this way, the input variables need to be enclosed in brackets.
It worked by changing this:
Property="pen => pen.PenNumber"
to this:
Property="(pen) => pen.PenNumber"

views for select list contained within a viewmodel and validation

My Scenario :
My Model is "Item" which contain 0 or many number of taxes (Model "Tax")
"Item" --> has 0 or n --> "Tax"
I have a View Model for MVC named "VMItem" which has an object of "Item", all taxes ("TaxDic") and selected taxes ("Taxes"). MVC page is bind to this view model ("ViewItem").
Requirement :
I want the MVC to display a list box of all taxes and enable user to select relevant tax for each item when he create the item.
Below is my code,
Reference to the following codes, ListBox which is for Taxes is not mandatory field. But when I submitted the form with some selection on ListBox, it shows red box around the ListBox and if I select nothing on ListBox and submit, there is no error it seems.
please have a look at the following scenario.. if there is any better way to achieve this, please someone guide me.
model
public class Item
{
[Display(Name="Item ID")]
public virtual Guid ItemID { get; set; }
[Required]
[Display(Name = "Name")]
public virtual string Name { get; set; }
[Required]
[Display(Name="Price")]
public virtual decimal Price { get; set; }
public virtual IEnumerable<Tax> Taxes { get; set; }
}
View Model
public class VMItem
{
public Item Item { get; set; }
public IEnumerable<Tax> Taxes { get; set; }
public IEnumerable<SelectListItem> TaxDic { get; set; }
}
Function to generate Select list item in the controller class
private VMItem GenerateViewModel(Item Item)
{
IEnumerable<Tax> Taxes = TaxServices.FindAll();
IList<SelectListItem> taxDic = new List<SelectListItem>();
// Generating Taxes and taxDic here..
VMItem VmItem = new VMItem
{
Item = Item,
Taxes = Taxes,
TaxDic = taxDic
};
return VmItem;
}
Contorller Action
// GET: /Product/Add
public ActionResult Add()
{
return View(GenerateViewModel(new Item()));
}
[HttpPost]
public ActionResult Add(VMItem collection)
{
Item item = new Item();
try
{
if(ModelState.IsValid)
{
item = collection.Item;
var taxes = collection.Taxes;
return View(GenerateViewModel(collection.Item));
}
// else..
}
// catch...
}
Views
<% using (Html.BeginForm()) { %>
<!-- more codes for other fields -->
<%: Html.ListBoxFor(model => model.Taxes, Model.TaxDic) %>
<!-- submit button goes below -->
<% } %>
This looks like a many-to-many relationship so first check if your tax class looks like this:
public class Tax
{
public Guid TaxId { get; set;}
public virtual ICollection<Item> Items { get; set;}
}
Then inside your OnModelCreating function in the context class add this:
modelBuilder.Entity<Item>()
.HasMany(i => i.Taxes).WithMany(t => t.Items)
.Map(t => t.MapLeftKey("ItemId")
.MapRightKey("TaxId")
.ToTable("ItemsTaxes"));
ViewModel
public class ItemTaxViewModel
{
public Item item { get; set; }
public virtual ICollection<AssignedTaxes> Taxes { get; set; }
}
public class AssignedTaxes
{
public int TaxId { get; set; }
public bool Assigned { get; set; }
}
Controller Action
public ActionResult Create()
{
var newItemVM = new ItemTaxViewModel
{
Item = new Item(),
Taxes = PopulateTaxes()
};
return View(newItemVM);
}
[HttpPost]
public ActionResult Create(ItemTaxViewModel itemTaxViewModel)
{
if(ModelState.IsValid)
{
var item = new Item();
item = itemTaxViewModel.Item;
AddOrUpdateTaxes(item, itemTaxViewModel.Taxes);
context.Items.Add(item);
context.SaveChanges();
return RedirectToAction("Index");
}
return View(itemTaxViewModel);
}
Helper Methods
private List<AssignedTaxes> PopulateTaxes()
{
var taxes = context.Taxes;
var assignedTaxes = new List<AssignedTaxes>();
foreach(var tax in taxes)
{
assignedTaxes.Add(new AssignedTaxes
{
TaxId = tax.TaxId,
Assigned = false
});
}
return assignedTaxes;
}
private void AddOrUpdateTaxes(Item item, ICollection<AssignedTaxes> assignedTaxes)
{
foreach(var assignedTax in assignedTaxes)
{
if(assignedTax.Assigned)
{
item.Taxes.Add(context.Taxes.Single(t => t.TaxId == assignedTax.TaxId));
}
}
}
EditorTemplate under the /Views/Shared/EditorTemplates
#model AssignedTaxes
#using projectName.ViewModels
<fieldset>
#Html.HiddenFor(model => model.TaxId)
#Html.CheckBoxFor(model => model.Assigned)
</fieldset>
View
<div class="editor-field">
#Html.EditorFor(model => model.Taxes)
<div class="editor-field">
Seems likes a lot of work but it's one of the cleanest ways to implement many-to-many checkboxes that I've come up with after going through many tutorials.
#Luis, your way looks well but it is too complicated and at the end you ended it with checkbox. May be it is better way. I came up with a solution and it was simple enough to me. I post it below. Please give me a feedback on it whether I should keep going with it or need improvement.
Controller
IEnumerable<Tax> Taxes = _TaxServices.FindTax();
IList<SelectListItem> taxDic = new List<SelectListItem>();
foreach (Tax tax in Taxes)
{
SelectListItem item = new SelectListItem();
item.Value = tax.TaxID.ToString();
item.Text = tax.Name;
taxDic.Add(item);
}
VMItem VmItem = new VMItem
{
Item = Item,
TaxDic = taxDic,
};
// Adding selected taxes to the list
IList<int> SelectedTaxes = new List<int>();
if (Item.Taxes != null && Item.Taxes.Count > 0)
{
foreach (Tax tax in Item.Taxes)
{
SelectedTaxes.Add(tax.TaxID);
}
}
VmItem.SelectedTax = SelectedTaxes;
return view(VmItem);
View
<%: Html.ListBoxFor(model => model.SelectedTax, Model.TaxDic) %>
Controller [HttpPost]
item.Taxes = new Iesi.Collections.Generic.HashedSet<Tax>();
if (vmItem.SelectedTax.Count() > 0)
{
IEnumerable<int> SelectedTaxesIDs = vmItem.SelectedTax.ToList();
foreach (int n in SelectedTaxesIDs)
{
item.Taxes.Add(_TaxServices.FindTax(n));
}
}
Please give feedback on this way.
Thanks.

How to use EditorForModel and DataAnnotations for complex types in a wrapping ViewModel?

I have a ViewModel wrapping two complex types:
public class EditProductViewModel
{
public ProductData ProductData { get; set; }
public FridgeContent FridgeContent { get; set; }
}
and this view:
#model EditProductViewModel
#using (Html.BeginForm("Edit", "ProductData", FormMethod.Post))
{
#Html.EditorForModel()
[...]
}
ProductData and FridgeContent contain POCO properties with DataAnnotations like this:
public class FridgeContentMetadata : DatabaseEntityMetadataBase
{
[Required]
[HiddenInput(DisplayValue = false)]
public int ProductDataId { get; set; }
[Required]
[UIHint("StringReadOnly")]
public int ScaleId { get; set; }
[Required]
[UIHint("StringReadOnly")]
[Range(0.01, float.MaxValue, ErrorMessage = "The weight of a product must be positive.")]
public float Weight { get; set; }
[...]
}
I want to edit both ProductData and FridgeContent in the EditProductView using the appropriate data annotations from those classes and the EditorForModel() method (I don't want to generate the templates myself). I therefore created the templates ProductData.cshtml and FridgeContent.cshtml in /Views/Shared/EditorTemplates/:
#model FridgeContent
#Html.EditorForModel()
Unfortunately, the view for EditProductViewModel is empty (no errors raised). If I use EditorForModel for either FridgeContent or ProductData alone, it's working fine. I also tried adding [UIHInt("..")] annotations to EditProductViewModel but that doesn't make a difference.
What am I missing?
#model EditProductViewModel
#using (Html.BeginForm("Edit", "ProductData", FormMethod.Post))
{
#Html.EditorFor(o=> o.ProductData )
#Html.EditorFor(o=> o.FridgeContent )
}
or create an edit template for you ViewModel containing these two lines
#Html.EditorFor(o=> o.ProductData )
#Html.EditorFor(o=> o.FridgeContent )
UPADTE:
Oh got it finally because the rendering engine will not go more that one step in object hierarchy, you can find it in asp.net mvc code also.
Check the MVC 3.0 Source Code Here:
There is a file named DefaultEditorTemplates.cs which contains this method:
internal static string ObjectTemplate(HtmlHelper html, TemplateHelpers.TemplateHelperDelegate templateHelper) {
ViewDataDictionary viewData = html.ViewContext.ViewData;
TemplateInfo templateInfo = viewData.TemplateInfo;
ModelMetadata modelMetadata = viewData.ModelMetadata;
StringBuilder builder = new StringBuilder();
if (templateInfo.TemplateDepth > 1) { // DDB #224751
return modelMetadata.Model == null ? modelMetadata.NullDisplayText : modelMetadata.SimpleDisplayText;
}
foreach (ModelMetadata propertyMetadata in modelMetadata.Properties.Where(pm => ShouldShow(pm, templateInfo))) {
if (!propertyMetadata.HideSurroundingHtml) {
string label = LabelExtensions.LabelHelper(html, propertyMetadata, propertyMetadata.PropertyName).ToHtmlString();
if (!String.IsNullOrEmpty(label)) {
builder.AppendFormat(CultureInfo.InvariantCulture, "<div class=\"editor-label\">{0}</div>\r\n", label);
}
builder.Append("<div class=\"editor-field\">");
}
builder.Append(templateHelper(html, propertyMetadata, propertyMetadata.PropertyName, null /* templateName */, DataBoundControlMode.Edit, null /* additionalViewData */));
if (!propertyMetadata.HideSurroundingHtml) {
builder.Append(" ");
builder.Append(html.ValidationMessage(propertyMetadata.PropertyName));
builder.Append("</div>\r\n");
}
}
return builder.ToString();
}
which clearly states that if the TemplateDepth > 1 just render a simple text.
As the above answer shows, this problem seems related to the framework limiting the depth of nesting it will consider.
One way to work around the problem is to use your own editor template. Create the partial view, Object.cshtml, in Views/Shared/EditorTemplates. Here's an example template taken from here:
#{
Func<ModelMetadata, bool> ShouldShow = metadata =>
metadata.ShowForEdit && !ViewData.TemplateInfo.Visited(metadata);
}
#if (ViewData.TemplateInfo.TemplateDepth > 5) {
if (Model == null) {
#ViewData.ModelMetadata.NullDisplayText
} else {
#ViewData.ModelMetadata.SimpleDisplayText
}
} else {
foreach (var prop in ViewData.ModelMetadata.Properties.Where(ShouldShow)) {
if (prop.HideSurroundingHtml) {
#Html.Editor(prop.PropertyName)
} else {
if (string.IsNullOrEmpty(Html.Label(prop.PropertyName).ToHtmlString())==false) {
<div class="editor-label">
#Html.Label(prop.PropertyName)
</div>
}
<div class="editor-field">
#Html.Editor(prop.PropertyName)
#Html.ValidationMessage(prop.PropertyName)
</div>
}
}
}
In the above example, you can set the maximum nesting depth by changing the 5 constant.

MVC3 Display a dropdown list from one datasource and save to another datasource

I'm getting back to an MVC3 project after a 3 month hiatus. I need to display a drop down list that pulls from Database A, but saves to Database B. The property I need to persist is the NAICS/SIC code. Right now I just provide the user a text box to key in freeform text. So, I have the mechanics of that down. But instead it should provide only a valid list of codes from a source database.
The tricky thing to is I'm using a custom model binder to generate my ViewModels on the fly, so I don't have a distinct .cshtml file to customize.
[Serializable]
public class Step4ViewModel : IStepViewModel
{
public Step4ViewModel()
{
}
//load naics codes from somewhere
[Display(Name = "Describe the nature of your business.")]
public String NatureOfBusiness { get; set; }
[Display(Name="NAICS/SIC CODE")]
public String BusinessTypeCode { get; set; }
Tricky ViewModel
#using Microsoft.Web.Mvc;
#using Tangible.Models;
#model Tangible.Models.WizardViewModel
#{
var currentStep = Model.Steps[Model.CurrentStepIndex];
var progress = ((Double)(Model.CurrentStepIndex) / Model.Steps.Count) * 100;
}
<script type="text/javascript">
$(function () {
$("#progressbar").progressbar({
value: #progress
});
});
</script>
<div id="progressbar" style="height:20px;">
<span style="position:absolute;line-height:1.2em; margin-left:10px;">Step #(Model.CurrentStepIndex + 1) out of #Model.Steps.Count</span>
</div>
#Html.ValidationSummary()
#using (Html.BeginForm())
{
#Html.Serialize("wizard", Model)
#Html.Hidden("StepType", Model.Steps[Model.CurrentStepIndex].GetType())
#Html.EditorFor(x => currentStep, null, "")
if (Model.CurrentStepIndex > 0)
{
<input type="submit" value="Previous" name="prev" />
}
if (Model.CurrentStepIndex < Model.Steps.Count - 1)
{
<input type="submit" value="Save & Continue" name="next" />
}
else
{
<input type="submit" value="Finish" name="finish" />
}
#*<input type="submit" value="Save" name="Save" />*#
}
Controller
[HttpPost]
public ActionResult Index([Deserialize] WizardViewModel wizard, IStepViewModel step)
{
wizard.Steps[wizard.CurrentStepIndex] = step;
if (ModelState.IsValid)
{
//Always save.
var obj = new dr405();
//wire up to domain model;
foreach (var s in wizard.Steps)
{
Mapper.Map(s,obj,s.GetType(), typeof(dr405));
}
using (var service = new DR405Service())
{
//Do something with a service here.
service.Save(db, obj);
}
if (!string.IsNullOrEmpty(Request["next"]))
{
wizard.CurrentStepIndex++;
}
else if (!string.IsNullOrEmpty(Request["prev"]))
{
wizard.CurrentStepIndex--;
}
else
{
return View("Upload", obj);
}
}
else if (!string.IsNullOrEmpty(Request["prev"]))
{
wizard.CurrentStepIndex--;
}
return View(wizard);
}
WizardViewModel
[Serializable]
public class WizardViewModel
{
public String AccountNumber { get; set; }
public int CurrentStepIndex { get; set; }
public Boolean IsInitialized { get { return _isInitialized; } }
public IList<IStepViewModel> Steps { get; set; }
private Boolean _isInitialized = false;
public void Initialize()
{
try
{
Steps = typeof(IStepViewModel)
.Assembly.GetTypes().Where(t => !t.IsAbstract && typeof(IStepViewModel).IsAssignableFrom(t)).Select(t => (IStepViewModel)Activator.CreateInstance(t)).ToList();
_isInitialized = true;
//rewrite this. get the profile and wire them up or something.
this.AccountNumber = Tangible.Profiles.DR405Profile.CurrentUser.TangiblePropertyId;
}
catch (Exception e)
{
_isInitialized = false;
}
}
}
You can specify a template for a specific property on your view model by adding the UIHint attribute to the field. Since your view calls EditorFor on the model it will use the template you specified with UIHint.
BusinessTypeDropdown.ascx - (placed in Views/Shared/EditorTemplates
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<string>" %>
<% var businessTypes = ViewData["businessTypes"] as IEnumerable<string>; %>
<%= Html.DropDownListFor(m => m , new SelectList(businessTypes, Model))%>
In your View Model
[Serializable]
public class Step4ViewModel : IStepViewModel
{
public Step4ViewModel()
{
}
//load naics codes from somewhere
[Display(Name = "Describe the nature of your business.")]
public String NatureOfBusiness { get; set; }
[Display(Name="NAICS/SIC CODE")][UIHint("BusinessTypeDropdown")]
public String BusinessTypeCode { get; set; }
Then in your controller just set ViewData["businessTypes"] to your list of business types.
Without understanding your "tricky" view model code, it will be hard to make helpful suggestions.
However, there shouldn't be much problem here. You need to somehow create your dropdown list in yoru view, and populate it from data passed from your controller.
All the work happens in your controller. Populate your list or IEnumerable or whatever data source from your first database, then in your post handler save the selection it to your second database (the second part should not be much different from what you already have).

Render Partial View from within a System.Web.Helpers.WebGrid

I am trying to render a Partial from within a System.Web.Helpers.WebGrid
my model class looks like this:
class GameInfo
{
public List<AppUser> Team1 { get; set; }
public List<AppUser> Team2 { get; set; }
// and more properties
}
class AppUser
{
public string PictureUrl { get; set; }
public string ProfileUrl { get; set; }
public long GamesWon { get; set; }
public long GamesLost { get; set; }
public int Points { get; set; }
// and more properties
}
I want my GridView to show a list of GameInfo's in my grid view.
What is turning out be to be tougher than expected is rendering the Teams (List).
To stay DRY I created a partial view to render a Team (_Team.cstml).
This is my razor code:
#if (Model != null)
{
var webgrid = new WebGrid(source: Model.Games,
rowsPerPage: 10);
<div id="grid">
#webgrid.GetHtml(
columns: webgrid.Columns(
webgrid.Column(header: "Score", format: #<text>#item.Score1/#item.Score1</text>),
webgrid.Column(header: "Team 1", format: (item) =>
{
return "hello sb"; // this line works!
//return Html.Partial("_Team", item.Team1); // this gives an error
})
)
)
</div>
}
Any idea how I can get this to work?
Thank you!
In case someone else runs into this, I managed to solve it this morning.
This works:
webgrid.Column(header: "Team 1", format: (item) =>
{
List<Cuarenta.Web.Models.AppUser> team = ((Cards.Cloud.WebRole.Admin.GameInfo)item.Value).Team1;
return Html.Partial("_Team", team);
})

Resources