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);
})
Related
I am having issue of using Kendo UI scheduler,
When I schedule task ,
Kendo UI start and end date not returning on server side.
Start and end date always return default date.
Here a Razor Code :
#model IEnumerable<Web.Models.PlantColor>
#{
ViewBag.Title = "Schedule View";
}
<h2>Schedule View</h2>
#(Html.Kendo().Scheduler<WorkScheduler.Web.Models.KendoSchedular>()
.Name("scheduler")
.Date(new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day))
.StartTime(new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day, 08, 00, 00))
.Height(600)
.Views(views =>
{
views.DayView();
views.WorkWeekView();
views.WeekView();
views.MonthView();
views.AgendaView();
})
.Resources(resource =>
{
resource.Add(m => m.PlantId)
.Title("Owner")
.DataTextField("Text")
.DataValueField("Value")
.DataColorField("Color")
.BindTo(Model);
})
.DataSource(d => d
.Model(m =>
{
m.Id(f => f.id);
})
.Read("ReadSchedule", "ScheduleView")
.Create("CreateSchedule", "ScheduleView")
.Destroy("Destroy", "ScheduleView")
.Update("Update", "ScheduleView")
)
)
Make sure that you have the start and end fields defined in your Model that you are posting back (and model inherits from ISchedulerEvent):
public class CalendarAppointmentViewModel : ISchedulerEvent
{
public int Id { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public string Recurrence { get; set; }
public string StartTimezone { get; set; }
public string EndTimezone { get; set; }
private DateTime start;
public DateTime Start
{
get
{
return start;
}
set
{
start = value.ToUniversalTime();
}
}
private DateTime end;
public DateTime End
{
get
{
return end;
}
set
{
end = value.ToUniversalTime();
}
}
public string RecurrenceRule { get; set; }
public int? RecurrenceID { get; set; }
public string RecurrenceException { get; set; }
public bool IsAllDay { get; set; }
I can see this question is already accepted as best answer. Perhaps following might be helpful also for someone struggling to return Start/End dates from Editor of Kendo Scheduler.
I had same issue, and followed the solutions provided here, still no success. In my case defining the culture and creating a Model inherited from "ISchedulerEvent" was still returning default date to the server side in Create & Update events handler methods of the Controller.
For me problem was a missing .js file.
kendo.timezones.min.js
So for anyone who is in the same shoes like me, kindly look at your Scripts folder in the project, and if that .js file is missing.
And please follow these steps in setting up the project.
I am trying to implement the scheduler control to show live calculation of material usage on daily bases (in a week by week view).
I am unable to have the Usage data displayed in the cells although I managed to have to Materials displayed on the left hand side. I wonder if any one could help or give me some hints on what I am doing wrong. Here is my code so far:
I can be sure that data is being return to the view but the view is not showing the usage data, which is simply numeric values corresponding to a material on a specific day of the week. I have also attached a screenshot of how it looks like:
the Controller method to read the data:
public JsonResult Read([DataSourceRequest] DataSourceRequest request)
{
try
{
var usageList = new List<ReportUsageViewModel>();
var imports = _importRespository.GetImports();
foreach (var usageReportStoredProcedure in imports)
{
var usageViewModel = new ReportUsageViewModel();
usageViewModel.MaterialID = usageReportStoredProcedure.MaterialID;
usageViewModel.Start = usageReportStoredProcedure.ScanDate;
usageViewModel.End = usageReportStoredProcedure.ScanDate;
usageViewModel.DailyUsage = usageReportStoredProcedure.UsageQuantity;
usageViewModel.Title = usageReportStoredProcedure.UsageQuantity.ToString();
usageList.Add(usageViewModel);
}
return Json(usageList.ToDataSourceResult(request));
}
catch (Exception exc)
{
ErrorHelper.WriteToEventLog(exc);
return null;
}
}
The actual control
<div id="StockViewer">
#(Html.Kendo().Scheduler<WorcesterMarble.ViewModels.ReportUsageViewModel>()
.Name("StockViewer")
.Timezone("Europe/London")
.Resources(resource => resource.Add(m => m.MaterialID)
.Title("Materials")
.Name("Materials")
.DataTextField("Name")
.DataValueField("MaterialID")
.BindTo(Model.MaertiaList))
.MajorTick(270)
.MinorTickCount(1)
.StartTime(DateTime.Now.Date.AddHours(8))
.EndTime(DateTime.Now.Date.AddHours(17))
.AllDaySlot(false)
.Date(DateTime.Now.Date)
.Editable(false)
.Views(x => x.WeekView(v =>
{
v.Footer(false);
v.Selected(true);
v.DateHeaderTemplate("<span class='k-link k-nav-day'>#=kendo.toString(date, 'ddd dd/M')#</span>");
}))
.Group(group => group.Resources("Materials").Orientation(SchedulerGroupOrientation.Vertical))
.DataSource(d => d
.Model(m => {
m.Id(f => f.MaterialID);
m.Field(f => f.Title).DefaultValue("No title");
})
.Read("Read", "ReportUsage")
)
)
Update: This is the ViewModel implementing the ISchedulerEvent
public class ReportUsageViewModel : ISchedulerEvent
{
public int MaterialID { get; set; }
public string MaterialName { get; set; }
public int? DailyUsage { get; set; }
public List<MaterialViewModel> MaertiaList { get; set; }
public string Description { get; set; }
public System.DateTime End { get; set; }
public bool IsAllDay { get; set; }
public string RecurrenceException { get; set; }
public string RecurrenceRule { get; set; }
public System.DateTime Start { get; set; }
public string Title { get; set; }
}
The issue was in these two lines:
.StartTime(DateTime.Now.Date.AddHours(8))
.EndTime(DateTime.Now.Date.AddHours(17))
The data was there but these two lines were hiding it. The data was being logged at times outside the time range of 8 to 17 so I removed these two lines and then set the Major ticks to 1440, which is the total number of ticks in 24 hrs, which helped me hiding the time column as I didn't need it..
After spending the last couple days searching, I'm officially stuck. I'm working on a binding an object to the Telerik MVC 3 Grid, but the catch is that it needs to have dynamically created columns (not auto generated). Three of the columns are known, the others are unknown, and this is the tricky part. Basically, it can be like these examples:
KnownColumn1 | KnownColumn2 | UnknownColumn1 | KnownColumn3
KnownColumn1 | KnownColumn2 | UnknownColumn1 | UnknownColumn2 | UnknownColumn3 | KnownColumn3
etc.
Because I'm putting the unknown columns in a list (I've tried a dictionary too so I can get the column names), this has complicated things for me when binding. My code is below:
Model (There can be zero to hundreds of rows, but this model is in a view model of type List, there can also be 0 to 20 plus columns that are dynamically added)
public class VendorPaymentsGLAccount
{
public string GeneralLedgerAccountNumber { get; set; }
public string GeneralLedgerAccountName { get; set; }
public string DisplayName { get { return string.Format("{0} - {1}", GeneralLedgerAccountNumber, GeneralLedgerAccountName); } }
public Dictionary<string, double> MonthAmount { get; set; }
public double Total { get { return MonthAmount.Sum(x => x.Value); } }
public List<string> Columns { get; set; }
public List<double> Amounts { get; set; }
public VendorPaymentsGLAccount() { }
}
View (The section that's commented out was trying to use the dictionary)
<fieldset>
<legend>General Ledger Account Spend History</legend>
#if (Model.VendorPaymentsGLAccounts != null)
{
<br />
#(Html.Telerik().Grid(Model.VendorPaymentsGLAccounts)
.Name("Grid")
.Columns(columns =>
{
columns.Bound(gl => gl.DisplayName).Title("General Ledger Account").Width(200).Filterable(false).Sortable(false);
//foreach (var month in Model.VendorPaymentsGLAccounts[0].MonthAmount)
//{
// //columns.Bound(gl => gl.MonthAmount[month.Key.ToString()].ToString()).Title(month.Key.ToString()).Width(100).Filterable(false).Sortable(false);
// //columns.Template(v => Html.ActionLink(v.VoucherID, "VoucherSummary", new { id = v.VoucherID, bu = v.BusinessUnitID, dtt = v.InvoiceDate.Ticks })).Title("Voucher").Width(100);
// columns.Template(gl => Html.ActionLink(gl.MonthAmount[month.Key.ToString()].ToString(), "VoucherSummary")).Title(month.Key.ToString()).Width(100);
//}
for (int i = 1; i <= (Model.VendorPaymentsGLAccounts[0].Columns.Count() - 1); i++)
{
string colTemp = Model.VendorPaymentsGLAccounts[0].Columns[i - 1];
columns.Template(gl => gl.Amounts[i - 1]).Title(colTemp).Width(100);
}
columns.Template(gl => String.Format("{0:C}", gl.Total)).Title("Total");
})
.Sortable()
.Pageable()
.Filterable()
.Footer(true))
}
else
{
<br />
#:There are no records that match your selected criteria.
}
</fieldset>
Using the dictionary approach, I was able to get the columns generated correctly with the right header text, but the values for the columns (in my testing there were only 2 columns) were the same. Can anyone help with this? This seems to be an oddball issue. Just trying to figure out how to do this correctly.
Update: Here is a screen shot using the dictionary approach that shows the issue. The column headings are correct, but the values are the same for both of the dynamic columns.
Using dynamically defined columns with the Telerik grid control can be tricky. But in your case, it's mainly a typical pitfall of closures.
In the following loop, the compiler will bind each instance of gl => gl.Amounts[i - 1] to the variable i and evaluate it later:
for (int i = 1; i <= (Model.VendorPaymentsGLAccounts[0].Columns.Count() - 1); i++)
{
string colTemp = Model.VendorPaymentsGLAccounts[0].Columns[i - 1];
columns.Template(gl => gl.Amounts[i - 1]).Title(colTemp).Width(100);
}
In fact, it's evaluated after the loop has finished. So i will always have the value that lead to the completion of the loop.
The fix is to use a temporary variable:
for (int i = 1; i <= (Model.VendorPaymentsGLAccounts[0].Columns.Count() - 1); i++)
{
string colTemp = Model.VendorPaymentsGLAccounts[0].Columns[i - 1];
int columnIndex = i - 1;
columns.Template(gl => gl.Amounts[columnIndex]).Title(colTemp).Width(100);
}
I had the same problem, and was googling plenty of hours around, and have done a lot of attempts from various assistance lines. But even so, it was not so trivial to solve!
And for that reason and to have also another working example here, I will provide also my solution!
Information: It only works at my place with an IList Model. Other collections had also caused problems!
#model IList<CBS.Web.Models.Equipment.EquipmentViewModel>
#(Html.Telerik().Grid(Model)
.Name("Grid")
.DataKeys(keys =>
{
keys.Add(m => m.ID);
})
.DataBinding(dataBinding =>
{
dataBinding.Ajax()
// renders the grid initially
.Select("EquipmentGrid", "Equipment");
})
.Columns(columns =>
{
// Equipment IDs
columns.Bound(m => m.ID).Hidden(true);
columns.Bound(m => m.Name).Title("Equipments").Width(200);
// Every item (EquipmentViewModel) of the Model has the same Count of Fields
for (int i = 0; i < (Model[0].Fields.Count()); i++)
{
// Name of the column is everytime same as in Model[0]
string columnName = Model[0].Fields.ElementAt(i).FieldDefinition.Name;
// Constructs i-counted columns, dynamically on how much
// Fields are owned by an Equipment. But note, that all Equipment-items
// in the Model must have the same Count and disposal of Fields!
columns.Template(m => m.Fields
.Where(f => f.FieldDefinition.Name == columnName)
.Where(f => f.EquipmentId == m.ID).First().Value)
.Title(columnName)
.Width(columnName.Length * 8); // * 8 was the optimal lenght per character
}
})
.ClientEvents(events => events.OnRowSelect("onRowSelected"))
.Selectable()
.Resizable(resizing => resizing.Columns(true))
.Pageable()
.Scrollable()
.Groupable()
.Filterable()
)
Controller:
public ActionResult EquipmentGrid(Guid id)
{
var belongingEquipments = _equipmentRepository.GetNotDeleted()
.OrderBy(e => e.Name).ToList()
.Where(e => e.RevisionId == id);
List<EquipmentViewModel> equVMList = new List<EquipmentViewModel>();
for (int i = 0; i < belongingEquipments.Count(); i++)
{
var equVM = new EquipmentViewModel
{
ID = belongingEquipments.ElementAt(i).ID,
Name = belongingEquipments.ElementAt(i).Name,
RevisionId = belongingEquipments.ElementAt(i).RevisionId,
EquipmentTypeId = belongingEquipments.ElementAt(i).EquipmentTypeId,
Fields = SortFields(belongingEquipments.ElementAt(i).Fields.ToList())
};
equVMList.Add(equVM);
}
return PartialView("EquipmentGrid", equVMList);
}
Models:
namespace CBS.Web.Models.Equipment
{
public class EquipmentViewModel
{
public Guid ID { get; set; }
public string Name { get; set; }
public Guid RevisionId { get; set; }
public Guid EquipmentTypeId { get; set; }
public virtual ICollection<FieldEntity> Fields { get; set; }
}
}
FieldDefinition
namespace CBS.DataAccess.Entities
{
public class FieldDefinitionEntity : EntityBase
{
[Required]
public virtual Guid EquipmentTypeId { get; set; }
public virtual EquipmentTypeEntity EquipmentType { get; set; }
[Required(AllowEmptyStrings = false)]
public virtual string Name { get; set; }
public virtual int Numbering { get; set; }
[Required]
public virtual Guid TypeInformationId { get; set; }
public virtual TypeInformationEntity TypeInformation { get; set; }
public virtual ICollection<FieldEntity> Fields { get; set; }
}
}
Field
namespace CBS.DataAccess.Entities
{
public class FieldEntity : EntityBase
{
[Required]
public virtual Guid EquipmentId { get; set; }
public virtual EquipmentEntity Equipment { get; set; }
[Required]
public virtual Guid FieldDefinitionId { get; set; }
public virtual FieldDefinitionEntity FieldDefinition { get; set; }
public virtual string Value { get; set; }
}
}
I dynamically bind the columns at runtime with reflection:
#model IEnumerable<object>
#using System.Collections
#using System.Collections.Generic
#using System.Reflection;
#(Html.Telerik().Grid(Model)
.Name("Grid")
.Columns(columns =>
{
Type t = Model.GetType().GetGenericArguments()[0];
foreach (var prop in t.GetProperties())
{
if (IsCoreType(prop.PropertyType))
{
columns.Bound(prop.PropertyType, prop.Name);
}
}
})
.DataBinding(binding => binding.Ajax()
.Select("SelectMethod", "SomeController")
)
.Sortable()
.Pageable()
.Filterable()
.Groupable()
)
#functions{
public bool IsCoreType(Type type)
{
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
{
return IsCoreType(type.GetGenericArguments()[0]);
}
return !(type != typeof(object) && Type.GetTypeCode(type) == TypeCode.Object);
}
}
Here is the workaround:
#(Html.Telerik().Grid(Model.Users)
.Name("Grid")
.Columns(columns => {
columns.GenerateCustomColumns(columnSettings);
}).DataBinding(dataBinding => dataBinding.Ajax().Select("_getusers", "home"))
.Scrollable(scrolling => scrolling.Enabled(true).Height("auto"))
.Pageable(paging => paging.Enabled(true)
.PageSize(10, new int[] { 5, 10, 20, 50, 100, 500 })
.Position(GridPagerPosition.Both)
.Total(Model.Users.Count)
.Style(GridPagerStyles.PageSizeDropDown | GridPagerStyles.NextPreviousAndNumeric)
.PageTo(1))
.Filterable(filtering => filtering.Enabled(true))
.Reorderable(reordering => reordering.Columns(true))
.NoRecordsTemplate(" ")
.EnableCustomBinding(true)
)
// Extension method to genarate columns dynamically
public static class TelerikMvcGridColumnHelper
{
public static void GenerateCustomColumns<T>(this GridColumnFactory<T> columns,List<GridCustomColumnSettings> settings) where T:class
{
if (settings != null)
{
settings.ForEach(column =>
{
var boundedColumn = columns.Bound(column.Member);
if (column.ClientFooterTemplate != null)
boundedColumn.ClientFooterTemplate(column.ClientFooterTemplate);
if (!string.IsNullOrEmpty(column.Width))
boundedColumn.Width(column.Width);
});
}
}
}
// Column settings class
public class GridCustomColumnSettings : GridColumnSettings
{
public string ClientFooterTemplate { get; set; }
}
I did this simple way. NOTE : the following solution works in ajax edit mode too (not just a read-only grid) :
when the ViewModels are :
public class PriceSheetEditGridViewModel
{
public IEnumerable<PriceSheetRowViewModel> Rows { get; set; }
public IEnumerable<PriceSheetColumnViewModel> Columns { get; set; }
}
public class PriceSheetColumnViewModel
{
public int Id { get; set; }
public string Title { get; set; }
}
public class PriceSheetRowViewModel
{
public int RowNo { get; set; }
public string Description { get; set; }
public double?[] Prices { get; set; }
}
the view can be like this (part of view.cshtml file...) :
....
#model PriceSheetEditGridViewModel
...
columns.Bound(o => o.Description ).Width(150);
int i = 0;
foreach (var col in Model.Columns)
{
columns
.Bound(model => model.Prices).EditorTemplateName("PriceSheetCellPrice").EditorViewData(new { ColumnId = i })
.ClientTemplate("<span><#=Prices ? jsHelper.addCommas(Prices[" + i.ToString() + "]):null#></span>")
.Title(col.Title).Width(80);
i++;
}
....
and the PriceSheetCellPrice.cshtml editor template file (in shared\editortemplates folder) :
#model decimal?
#(Html.Telerik().NumericTextBox()
.Name(ViewData.TemplateInfo.GetFullHtmlFieldName(string.Empty)+"["+ViewBag.ColumnId+"]")
.InputHtmlAttributes(new { style = "width:100%" })
})
.EmptyMessage("")
.DecimalDigits(0)
.DecimalSeparator(",")
.MinValue(0)
.Value((double?) Model)
)
I am trying to get four cascading dropdowns using knockout.js:
Search Criteria
Sub Criteria
Value
State
I was able to get the first cascade going (but not the others due to databinding issues) by using code from the following link:
http://blogs.msdn.com/b/thebeebs/archive/2011/12/01/price-calculator.aspx
The data for these dropdowns is being returned to my razor viewpage as an IEnumrable of SearchCriterion from an MVC view using ViewBag.CriteriaData variable. The code for my classes is as follows:
public class SearchCriterion
{
public string Text { get; set; }
public string Value { get; set; }
public List<SubCriterion> SubCriteria { get; set; }
}
public class SubCriterion
{
public string SearchCriterionValue { get; set; }
public string Text { get; set; }
public string Value { get; set; }
public List<ColumnValue> ColumnValues { get; set; }
}
public class ColumnValue
{
public string SearchCriterionValue { get; set; }
public string SubCriterionValue { get; set; }
public string Text { get; set; }
public string Value { get; set; }
public IEnumerable<StateValue> StateValues { get; set; }
}
public class StateValue
{
public string SearchCriterionValue { get; set; }
public string SubCriterionValue { get; set; }
public string ColumnValue { get; set; }
public IEnumerable<int> InputStateIds { get; set; }
public IEnumerable<int> OutputStateIds { get; set; }
public int SelectedInputStateId { get; set; }
public int SelectedOutputStateId { get; set; }
public string Text { get; set; }
public string Value { get; set; }
}
The issues I am facing are in the following portions of the .cshtml code:
What do I specify in this template for the other two dropdowns. e.g. the third dropdown needs to be bound to ColumnValue.Value (ColumnValue is part of SubCriterion)
<script id='criteriaRowTemplate' type='text/html'>
<tr>
<td><select data-bind='options: criteriaData, optionsText: "Text", optionsCaption: "Search Criterion", value: SearchCriterion' /></td>
<td><select data-bind='visible: SearchCriterion, options: SearchCriterion() ? SearchCriterion().SubCriteria : null, optionsText: "Text", optionsCaption: "Sub Criterion", value: SubCriterion' /></td>
<td><select data-bind='visible: SubCriterion, options: SubCriterion() ? SubCriterion().ColumnValues : null, optionsText: "Text", optionsCaption: "Column Value", value: ColumnValue'/></td>
<td><select data-bind='visible: ColumnValue, options: ColumnValue() ? ColumnValue().StateValues : null, optionsText: "Text", optionsCaption: "State", value: StateValue'/></td>
<td><button data-bind='click: function() { viewModel.removeLine($data) }'>Remove</button></td>
</tr>
</script>
Is this correct?
var CriteriaLine = function() {
this.SearchCriterion = ko.observable();
this.SubCriterion = ko.observable();
this.ColumnValue = ko.observable();
this.StateValue = ko.observable();
// Whenever the Search Criteria changes, reset the Sub Criteria selection
this.SearchCriterion.subscribe(function() { this.SubCriterion(undefined); }.bind(this));
this.SubCriterion.subscribe(function() { this.ColumnValue(undefined); }.bind(this));
this.ColumnValue.subscribe(function() { this.StateValue(undefined); }.bind(this));
};
How do I map the complete C# object with the Javascript object? It works if we just have the first two dropdowns:
// Create a Javascript object object with the same property names as the C# object
var dataToSearch = $.map(this.lines(), function (line) { return line.StateValue() ? line.StateValue() : undefined; });
var SearchObject = new function () {
this.StateValues = dataToSearch;
};
// Convert the object to JSON
var searchCriteria = JSON.stringify(SearchObject);
Does anything need to change here for the binding?
// Apply the data from the server to the variable
var criteriaData = #Html.Raw(#Json.Encode(ViewBag.CriteriaData));
var viewModel = new Criteria();
ko.applyBindings(viewModel, document.getElementById("criteriaDiv"));
EDIT:
I am now able to populate the cascading dropdowns (updated code above). Now I have 4 columns, each column having one of the dropdowns. I also have 1...n number of rows being added dynamically by using Knockoutjs. So, the user can now select values from these dropdowns and add more rows of dropdowns if he wants. The only thing remaining is to return the values that the user selects for the dropdowns to the controller(point 3 above). I am not sure how I can do that. Any help would be appreciated.
EDIT 2:
Added working code for Item # 3 and modified the ColumnValue and StateValue classes.
I'm not sure I fully understand your question, but I'm going to take a whack at it anyway :). I think you're looking for a way to "validate" if it is in fact time to allow the next drop down to be active?
If so, you could approach it from a standpoint of Computed Observables. Basically, you would bind each of your dropdowns to a computed value which is derived from the previous dependencies.
Let me write fiddle and I'll show you :)
OK, give this a shot...sorry for the delay...http://jsfiddle.net/farina/ZNBcM/3/
I update the answer, Hope, it will help new Comers.
Methods for Binding Hierarchical Dropdowns using Knockout JS in MVC
Here you can find the good example .
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, ...