Passing a list to partialview, BeginCollectionItem() - asp.net-mvc-5

I want to pass a list to PartialView that has BeginCollectionItem(). Here is the code,
InquiryOrderViewModel
public class InquiryOrderViewModel
{
public InquiryOrder InquiryOrder { get; set; }
public List<InquiryOrderDetail> InquiryOrderDetails { get; set; }
public List<InquiryComponentDetail> InquiryComponentDetails { get; set; }
}
InquiryComponentDetail model
public class InquiryComponentDetail
{
[Key]
public int InquiryComponentDetailId { get; set; }
public int DesignCodeId { get; set; }
public int QualityReferenceId { get; set; }
public int Height { get; set; }
public int Length { get; set; }
public int GscmComp { get; set; }
public int Wastage { get; set; }
public int TotalYarn { get; set; }
public virtual DesignCodeQltyRef DesignCodeQltyRef { get; set; }
}
InquiryOrderIndex View and the Script to render multiple items at once
#model eKnittingData.InquiryOrderViewModel
#using (Html.BeginForm("Save", "InquiryOrder"))
{
..........
<div id="cmpDts">
#foreach (var item in Model.InquiryComponentDetails)
{
}
</div>
..........
}
<script>
var prev;
$(document).on('focus', '.class03', function () {
prev = $(this).val();
}).on('change', '.class03', function () {
if (prev != "") {
$.ajax({
url: '#Url.Action("ComponentDts", "InquiryOrder")', // dont hard code your url's
type: "GET",
data: { DesignCdId: $(this).val() }, // pass the selected value
success: function (data) {
$('.cmpCls').last().replaceWith(data);
}
});
}
else {
$.ajax({
url: '#Url.Action("ComponentDts", "InquiryOrder")', // dont hard code your url's
type: "GET",
data: { DesignCdId: $(this).val() }, // pass the selected value
success: function (data) {
$(".class03 option[value='']").remove();
$('#cmpDts').append(data);
}
});
}
});
</script>
The _DetailEditorRow PartialView which gives ddls with class03 and in main view where it got appended.(This is just to show you what is class03)
#model eKnittingData.InquiryOrderDetail
#using eKnitting.Helpers
#using (Html.BeginCollectionItem("InquiryOrderDetails"))
{
<div class="editorRow">
#Html.DropDownListFor(a => a.ComponentId, (SelectList)ViewBag.CompList, "Select", new { Class = "class02" })
#Html.DropDownListFor(a => a.DesignCodeId, (SelectList)ViewBag.DCodeList, "Select", new { Class = "class03" })
#Html.TextBoxFor(a => a.NoOfParts, new { Class = "class01" })
delete
</div>
}
and in main view it got appended to
<div id="editorRows">
#foreach (var item in Model.InquiryOrderDetails)
{
Html.RenderPartial("_DetailEditorRow", item);
}
</div>
_ComponentDetails PartialView to render items(a list has been passed at once)
#model List<eKnittingData.InquiryComponentDetail>
#using eKnitting.Helpers
<div class="cmpCls">
#foreach(var icd in Model)
{
using (Html.BeginCollectionItem("InquiryComponentDetails"))
{
<div class="innerCmpCls">
#Html.DisplayFor(a => icd.DesignCodeId)
#Html.DisplayFor(a => icd.QualityReferenceId)
#Html.TextBoxFor(a => icd.Height, new { Class="clsHeight clsSameHL"})
#Html.TextBoxFor(a => icd.Length, new { Class = "clsLength clsSameHL" })
#Html.TextBoxFor(a => icd.GscmComp, new { Class = "clsGscmComp clsSameHL" })
#Html.TextBoxFor(A => icd.Wastage, new { Class = "clsWastage" })
#Html.ActionLink("Fds", "View", new { id = icd.QualityReferenceId }, new { #class = "myLink", data_id = icd.QualityReferenceId })
#Html.TextBoxFor(a => icd.TotalYarn, new { Class = "clsTotalYarn" })
<br>
<div class="popFds"></div>
</div>
}
}
</div>
ActionResult that Passes a list at once and returns the PartialView
public ActionResult ComponentDts(int DesignCdId)
{
var objContext = new KnittingdbContext();
var QltyRefList = objContext.DesignCodeQltyRefs.Where(a=>a.DesignCodeId==DesignCdId).ToList();
var iocdList = new List<InquiryComponentDetail>();
foreach(DesignCodeQltyRef dcqr in QltyRefList)
{
iocdList.Add(new InquiryComponentDetail {
DesignCodeId=dcqr.DesignCodeId,
QualityReferenceId=dcqr.QltyRefId
});
}
return PartialView("_ComponentDetails", iocdList);
}
ActionResult for GET
var objContext = new KnittingdbContext();
var newIovm = new InquiryOrderViewModel();
var newIo = new InquiryOrder();
var iocdL = new List<InquiryComponentDetail>();
newIovm.InquiryOrder = newIo;
newIovm.InquiryComponentDetails = iocdL;
return View(newIovm);
ActionResult for POST
public ActionResult Save(InquiryOrderViewModel inquiryOrderViewModel)
{
.........
}
When user selects an item from a dropdownlist(class03), the items related to that item are rendered to the view using the PartialView(_ComponentDetails') and get appended. Then user selects another item from another ddl(class03), the related items are rendered and appended after earlier appended ones. User can go on like this.
Rendering and appending items works fine. But for the PostBack even though i get the number of items in the list correctly(I checked it by putting a break point on POST ActionResult ) all items content show null values. Pls guide me in the correct way for achieving this. All help appreciated. Thanks!

Your _ComponentDetails view is generating form controls that have name attributes that look like (where ### is a Guid)
name="InquiryComponentDetail[###].icd.Height"
which does not match your model because typeof InquiryComponentDetail does not contain a property named icd. In order to bind to your model, your name attribute would need
name="InquiryComponentDetail[###].Height"
To generate the correct html, you will need 2 partials
_ComponentDetailsList.cshtml (this will be called by the ComponentDts() method using return PartialView("_ComponentDetailsList", iocdList);)
#model List<eKnittingData.InquiryComponentDetail>
<div class="cmpCls">
#foreach(var item in Model)
{
Html.RenderPartial("_ComponentDetails", item);
}
</div>
_ComponentDetails.cshtml
#model eKnittingData.InquiryComponentDetail
using (Html.BeginCollectionItem("InquiryComponentDetails"))
{
<div class="innerCmpCls">
#Html.DisplayFor(a => a.DesignCodeId)
#Html.DisplayFor(a => a.QualityReferenceId)
#Html.TextBoxFor(a => a.Height, new { #class="clsHeight clsSameHL"}) // use #class, not Class
#Html.TextBoxFor(a => a.Length, new { Class = "clsLength clsSameHL" })
....
</div>
}

Related

ASP.Net MVC: How to show checkboxes selection in webgrid column after postback data

I have developed a tabular UI with webgrid. i am showing student information through webgrid. i am showing multiple checkboxes for hobbies in each row of webgrid. when i select hobbies and click submit button then i saw hobbies selection is not going to action.
i guess there is my mistake in view model class design. please have a look at my code and tell me which area i need to change in code.
i want all hobbies should go to action when i click submit button and selected hobbies also should post to action for each student. a student may have multiple hobbies selected.
here is my viewcode
#model MVCCRUDPageList.Models.StudentListViewModel
#{
ViewBag.Title = "Index";
}
<h2>Student View Model</h2>
#using (Html.BeginForm("Index", "WebGridMoreControls", FormMethod.Post))
{
var grid = new WebGrid(Model.Students, canSort: false, canPage: false);
var rowNum = 0;
var SelectedHobbies = 0;
<div id="gridContent" style=" padding:20px; ">
#grid.GetHtml(
tableStyle: "table",
alternatingRowStyle: "alternate",
selectedRowStyle: "selected",
headerStyle: "header",
columns: grid.Columns
(
grid.Column(null, header: "Row No", format: item => rowNum = rowNum + 1),
grid.Column("ID", format: (item) => #Html.TextBoxFor(m => m.Students[rowNum - 1].ID, new { #class = "edit-mode" })),
grid.Column("Name", format: (item) => #Html.TextBoxFor(m => m.Students[rowNum - 1].Name, new { #class = "edit-mode" })),
grid.Column("Country", format: (item) =>
#Html.DropDownListFor(x => x.Students[rowNum - 1].CountryID,
new SelectList(Model.Country, "ID", "Name", item.CountryID),
"-- Select Countries--", new { id = "cboCountry", #class = "edit-mode" })),
grid.Column(header: "Hobbies",
format: #<text>
Hobbies
#foreach (var hobby in Model.Hobbies)
{
<div class="checkbox">
<label>
#Html.HiddenFor(e => e.Hobbies)
<input type="checkbox"
name="Hobbies"
value="#hobby.ID" /> #hobby.Name
</label>
</div>
}
</text>)
))
<input type="submit" value="Submit" />
</div>
}
Action code
public class WebGridMoreControlsController : Controller
{
// GET: WebGridMoreControls
public ActionResult Index()
{
StudentListViewModel osvm = new StudentListViewModel();
return View(osvm);
}
[HttpPost]
public ActionResult Index(StudentListViewModel oStudentListViewModel)
{
return View(oStudentListViewModel);
}
}
View model code
public class StudentListViewModel
{
public IList<Student> Students { get; set; }
public List<Country> Country { get; set; }
public IList<Hobby> SelectedHobbies { get; set; }
public IList<Hobby> Hobbies { get; set; }
public StudentListViewModel()
{
Students = new List<Student>
{
new Student{ID=1,Name="Keith",CountryID=0,Hobby=0},
new Student{ID=2,Name="Paul",CountryID=2,Hobby=0},
new Student{ID=3,Name="Sam",CountryID=3,Hobby=0}
};
Country = new List<Country>
{
new Country{ID=1,Name="India"},
new Country{ID=2,Name="UK"},
new Country{ID=3,Name="USA"}
};
Hobbies = new List<Hobby>
{
new Hobby{ID=1,Name="Football"},
new Hobby{ID=2,Name="Hocky"},
new Hobby{ID=3,Name="Cricket"}
};
}
}
Model code
public class Student
{
public int ID { get; set; }
[Required(ErrorMessage = "First Name Required")]
public string Name { get; set; }
public int CountryID { get; set; }
public int Hobby { get; set; }
}
public class Country
{
public int ID { get; set; }
public string Name { get; set; }
}
public class Hobby
{
public int ID { get; set; }
public string Name { get; set; }
}
please help me to rectify view, viewmodel and model class code. thanks
Add a viewmodel for Hobby:
public class HobbyViewModel
{
public int ID { get; set; }
public string Name { get; set; }
public bool Checked { get; set; }
}
Modify your StudentListViewModel as following:
public class StudentListViewModel
{
public IList<Student> Students { get; set; }
public List<Country> Country { get; set; }
public IList<HobbyViewModel> Hobbies { get; set; }
public StudentListViewModel()
{
Students = new List<Student>
{
new Student{ID=1,Name="Keith",CountryID=0,Hobby=0},
new Student{ID=2,Name="Paul",CountryID=2,Hobby=0},
new Student{ID=3,Name="Sam",CountryID=3,Hobby=0}
};
Country = new List<Country>
{
new Country{ID=1,Name="India"},
new Country{ID=2,Name="UK"},
new Country{ID=3,Name="USA"}
};
Hobbies = new List<HobbyViewModel>
{
new HobbyViewModel{ID=1,Name="Football"},
new HobbyViewModel{ID=2,Name="Hocky"},
new HobbyViewModel{ID=3,Name="Cricket"}
};
}
Replace the foreach in the view as following:
#for (var i = 0; i < Model.Hobbies.Count; i++)
{
<div class="checkbox">
#Html.HiddenFor(m => m.Hobbies[i].ID)
#Html.CheckBoxFor(m => m.Hobbies[i].Checked)
#Html.LabelFor(m => m.Hobbies[i].Checked, Model.Hobbies[i].Name)
</div>
}

dynamically create textbox and save data MVC

I am new to MVC. I want to dynamically create textboxes upon user click.
<script>
function addRow(tableID) {
var table = document.getElementById(tableID);
var rowCount = table.rows.length;
var row = table.insertRow(rowCount);
var colCount = table.rows[0].cells.length;
for(var i=0; i<colCount; i++) {
var newcell = row.insertCell(i);
newcell.innerHTML = table.rows[0].cells[i].innerHTML;
switch(newcell.childNodes[0].type) {
case "text":
newcell.childNodes[0].value = "";
break;
case "checkbox":
newcell.childNodes[0].checked = false;
break;
case "select":
newcell.childNodes[0].selectedIndex = 0;
clear_attrib();
break;
}
}
else {
alert("Cannot add another row.");
}
}
function deleteRow(tableID) {
try {
var table = document.getElementById(tableID);
var rowCount = table.rows.length;
for(var i=0; i<rowCount; i++) {
var row = table.rows[i];
var chkbox = row.cells[0].childNodes[0];
if(null != chkbox && true == chkbox.checked) {
if(rowCount <= 1) {
alert("Cannot delete all the rows.");
break;
}
table.deleteRow(i);
rowCount--;
i--;
}
}
}catch(e) {
alert(e);
}
}
and the Razor
<table class="table" id="AddSchedule">
<tr>
<td><input type="checkbox" name="chk[]" class="checkbox_style" /></td>
<td>#Html.EditorFor(model => model.Schedule, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Schedule, "", new { #class = "text-danger" })
</td>
<td>#Html.EditorFor(model => model.Room, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Room, "", new { #class = "text-danger" })
</td>
<td>#Html.EditorFor(model => model.Subject, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Subject, "", new { #class = "text-danger" })
</td>
</tr>
the adding and removing of textboxes is OK. But I don't know how to save the data in the database. This is my controller
public ActionResult Create(EmployeeViewModel employeeViewModel)
{
if (ModelState.IsValid)
{
var emp = db.Employee.Create();
emp.ID = employeeViewModel.ID;
emp.LastName = employeeViewModel.LastName;
emp.FirstName = employeeViewModel.FirstName;
db.Employee.Add(emp);
var sched = db.FacultySchedule.Create();
sched.Schedule = employeeViewModel.Schedule;
sched.Room = employeeViewModel.Room;
sched.Subject = employeeViewModel.Subject;
db.FacultySchedule.Add(sched);
db.SaveChanges();
}
I tried using foreach but it still save only the first value, so i removed it... I think I'm missing something in the mark-up... like adding [] to make it an array just like in PHP. And I don't know how to fix it in the controller.
How can I fix the mark-up and the loop for in the controller for saving? Thanks.
Edit
this is the viewmodel
public class EmployeeViewModel
{
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int ID { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
public string Schedule { get; set; }
public string Room { get; set; }
public string Subject { get; set; }
public virtual IEnumerable<FacultySchedule> FacultySchedule { get; set; }
}
and these are the domain model
public partial class Employee
{
public Employee()
{
FacultySchedule = new HashSet<FacultySchedule>();
}
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int ID { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
public virtual ICollection<FacultySchedule> FacultySchedule { get; set; }
}
public partial class FacultySchedule
{
public FacultySchedule()
{
Substitution = new HashSet<Substitution>();
}
public int ID { get; set; }
public int EmployeeID { get; set; }
public string Schedule { get; set; }
public string Room { get; set; }
public string Subject { get; set; }
public virtual Employee Employee { get; set; }
}

MVC 5 Ajax.BeginForm POST does not bind to model in conroller

Sorry for the amount of code here but it's the best way to explain what;s happening.
I gave this code in my MVC 5 partial view:
#model Hide.MVC.MFC.Models.RequestDataModel
using (Ajax.BeginForm("RequestSetCanceledState", "Home", null,
new AjaxOptions
{
HttpMethod = "POST",
InsertionMode = InsertionMode.Replace,
UpdateTargetId = "request-info",
OnBegin = "AjaxAbsoluteLoaderOn",
OnComplete = "AjaxAbsoluteLoaderOff"
}, new { id = "formCancel" }))
{
<input id="sbmtCancel" type="submit" value="Cancel" />
#Html.DropDownListFor(m => m.CancelReason, new SelectList(Model.CancelReasons, "Id", "Name", Model.CancelReason), String.Empty)
#Html.TextBoxFor(m => m.Reason)
#Html.HiddenFor(m => m.RequestId)
}
My controller action is as follows:
[HttpPost]
public PartialViewResult RequestSetCanceledState(RequestDataModel model)
{
...
return PartialView("....", model);
}
And my model is as follows:
public class RequestDataModel
{
public RequestDataModel() { }
public RequestDataModel(int requestId)
{
this.RequestId = requestId;
using (var service = new InnerServiceClient())
{
var request = service.GetRequest(requestId);
this.State = request.ServiceState;
if (request.PremiseInformationId.HasValue)
{
this.PremisInformation = service.GetExistingData(requestId) ?? new PremiseInformationBL();
}
}
}
public int RequestId { get; set; }
public int State { get; set; }
public PremiseInformationBL PremisInformation { get; set; }
public int? CancelReason { get; set; }
public string Reason { get; set; }
public List<ListItem> CancelReasons
{
get
{
using (var service = new DictionaryServiceClient())
{
return service.GetShortList(TypeDictionary.MFCCancelReason).ToList();
}
}
}
}
The code does post to the RequestSetCanceledState method, and browser send message
CancelReason=1&Reason=123&RequestId=48&X-Requested-With=XMLHttpRequest
but the model is always empty. Also, if I use a GET request, the model is not empty! I tried to remove PremisInformation and CancelReasons from the model, but it did not help. Can someone please tell me why this might be?

Orchard part doesn't save

Walking through this tutorial everything is fine but the data doesn't save.
Tables has been created and edit page shows the fields but no record has been saved. After clicking the save button I get this message:
Your Car ContentType has been created.
Not found
The page you are looking for does not exist.
Any idea?
Models:
public class CarPart : ContentPart<CarPartRecord> {
public string Name {
get { return Record.Name; }
set { Record.Name = value; }
}
public string Description {
get { return Record.Description; }
set { Record.Description = value; }
}
public bool IsPublished {
get { return Record.IsPublished; }
set { Record.IsPublished = value; }
}
}
public class CarPartRecord : ContentPartRecord {
public virtual string Name { get; set; }
public virtual string Description { get; set; }
public virtual bool IsPublished { get; set; }
}
Handlers:
public class CarPartHandler : ContentHandler {
public CarPartHandler(IRepository<CarPartRecord> repository) {
Filters.Add(StorageFilter.For(repository));
}
}
Drivers:
public class CarPartDriver : ContentPartDriver<CarPart> {
protected override string Prefix { get { return "Car"; } }
protected override DriverResult Editor(CarPart part, dynamic shapeHelper) {
return ContentShape("Parts_Car_Edit", () => shapeHelper.EditorTemplate(TemplateName: "Parts/Car", Model: part, Prefix: Prefix));
}
protected override DriverResult Editor(CarPart part, IUpdateModel updater, dynamic shapeHelper) {
updater.TryUpdateModel(part, Prefix, null, null);
return Editor(part, shapeHelper);
}
}
Migrations:
public int Create(){
SchemaBuilder.DropTable("CarPartRecord");
SchemaBuilder.CreateTable("CarPartRecord", table => table
.ContentPartRecord()
.Column<string>("Name",column => column.WithLength(50).NotNull().Unique())
.Column<string>("Description",column => column.WithLength(500))
.Column<bool>("IsPublished",column => column.WithDefault(true).NotNull()));
return 1;
}
public int UpdateFrom1() {
ContentDefinitionManager.AlterPartDefinition("CarPart",part => part.Attachable());
return 2;
}
Errors:
Orchard.Exceptions.DefaultExceptionPolicy - Default - An unexpected exception was caught
http...../OrchardLocal/Admin/Contents/Create/Car
NHibernate.Exceptions.GenericADOException: could not execute batch command.[SQL: SQL not available] ---> System.Data.SqlClient.SqlException: Cannot insert the value NULL into column 'Name', table 'OrchardDB.dbo.Mega_Car_CarPartRecord'; column does not allow nulls. INSERT fails.
NHibernate.AssertionFailure - Default - An AssertionFailure occurred - this may indicate a bug in NHibernate or in your custom types.
http..../OrchardLocal/Admin/Contents/Create/Car
NHibernate.AssertionFailure: null id in Orchard.ContentManagement.Records.ContentTypeRecord entry (don't flush the Session after an exception occurs)
Orchard.Web\Modules\M.Car\Views\EditorTemplates\Parts
#using System.Web.Mvc.Html
#model M.Car.Models.CarPart
<fieldset>
<legend>Car Fields</legend>
<div class="editor-label">#Html.LabelFor(x => x.Name)</div>
<div class="editor-field">
#Html.EditorFor(x => x.Name)
#Html.ValidationMessageFor(x => x.Name)
</div>
<div class="editor-label">#Html.LabelFor(x => x.Description)</div>
<div class="editor-field">
#Html.EditorFor(x => x.Description)
#Html.ValidationMessageFor(x => x.Description)
</div>
<div class="editor-label">#Html.LabelFor(x => x.IsPublished)</div>
<div class="editor-field">
#Html.EditorFor(x => x.IsPublished)
#Html.ValidationMessageFor(x => x.IsPublished)
</div>
</fieldset>

Orchard Custom Settings Not Persisting

I'm pulling my hair out on this one; it should be so simple yet I can't figure out the issue.
I'm trying to simply save some custom settings in my module. I used the Orchard.Email module as an example on how to plug into the 'Settings' menu; my code is as follows:
Migrations.cs
public class CustomSettingsMigrations : DataMigrationImpl {
public int Create() {
SchemaBuilder.CreateTable("CustomSettingsPartRecord", table => table
.ContentPartRecord()
.Column<string>("GatewayUrl")
.Column<string>("MerchantId")
.Column<string>("MerchantPassword")
.Column<bool>("SandboxMode")
.Column<string>("SandboxGatewayUrl")
.Column<string>("SandboxMerchantId")
.Column<string>("SandboxMerchantPassword")
);
return 1;
}
}
Models/CustomSettingsPartRecord.cs
public class CustomSettingsPartRecord : ContentPartRecord {
public virtual string GatewayUrl { get; set; }
public virtual string MerchantId { get; set; }
public virtual string MerchantPassword { get; set; }
public virtual bool SandboxMode { get; set; }
public virtual string SandboxGatewayUrl { get; set; }
public virtual string SandboxMerchantId { get; set; }
public virtual string SandboxMerchantPassword { get; set; }
public CustomSettingsPartRecord() {
SandboxMode = true;
}
}
Models/CustomSettingsPart.cs
public class CustomSettingsPart : ContentPart<CustomSettingsPartRecord> {
private readonly ComputedField<string> _password = new ComputedField<string>();
public ComputedField<string> PasswordField {
get { return _password; }
}
public string GatewayUrl {
get { return Record.GatewayUrl; }
set { Record.GatewayUrl = value; }
}
public string MerchantId {
get { return Record.MerchantId; }
set { Record.MerchantId = value; }
}
public string MerchantPassword {
get { return Record.MerchantPassword; }
set { Record.MerchantPassword = value; }
}
public bool SandboxMode {
get { return Record.SandboxMode; }
set { Record.SandboxMode = value; }
}
public string SandboxGatewayUrl {
get { return Record.SandboxGatewayUrl; }
set { Record.SandboxGatewayUrl = value; }
}
public string SandboxMerchantId {
get { return Record.SandboxMerchantId; }
set { Record.SandboxMerchantId = value; }
}
public string SandboxMerchantPassword {
get { return Record.SandboxMerchantPassword; }
set { Record.SandboxMerchantPassword = value; }
}
public bool IsValid() {
return ((!String.IsNullOrWhiteSpace(Record.GatewayUrl)
&& !String.IsNullOrWhiteSpace(Record.MerchantId)) ||
(Record.SandboxMode && !String.IsNullOrWhiteSpace(Record.SandboxGatewayUrl) &&
!String.IsNullOrWhiteSpace(Record.SandboxMerchantId)));
}
}
Handlers/CustomSettingsPartHandler.cs
[UsedImplicitly]
public class CustomSettingsPartHandler : ContentHandler {
private readonly IEncryptionService _encryptionService;
public CustomSettingsPartHandler(IRepository<CustomSettingsPartRecord> repository, IEncryptionService encryptionService) {
T = NullLocalizer.Instance;
Logger = NullLogger.Instance;
_encryptionService = encryptionService;
Filters.Add(new ActivatingFilter<CustomSettingsPart>("Site"));
Filters.Add(StorageFilter.For(repository));
OnLoaded<CustomSettingsPart>(LazyLoadHandlers);
}
public Localizer T { get; set; }
public new ILogger Logger { get; set; }
void LazyLoadHandlers(LoadContentContext context, CustomSettingsPart part) {
part.PasswordField.Getter(() => {
try {
return String.IsNullOrWhiteSpace(part.Record.MerchantPassword) ? String.Empty : Encoding.UTF8.GetString(_encryptionService.Decode(Convert.FromBase64String(part.Record.MerchantPassword)));
}
catch (Exception) {
Logger.Error("The merchant password could not be decrypted. It might be corrupt, try to reset it.");
return null;
}
});
part.PasswordField.Setter(value => part.Record.MerchantPassword = String.IsNullOrWhiteSpace(value) ? String.Empty : Convert.ToBase64String(_encryptionService.Encode(Encoding.UTF8.GetBytes(value))));
}
protected override void GetItemMetadata(GetContentItemMetadataContext context) {
if (context.ContentItem.ContentType != "Site")
return;
base.GetItemMetadata(context);
context.Metadata.EditorGroupInfo.Add(new GroupInfo(T("Custom")));
}
}
Drivers/CustomSettingsPartDriver.cs
public class CustomSettingsPartDriver : ContentPartDriver<CustomSettingsPart> {
private const string TemplateName = "Parts/CustomSettings";
public CustomSettingsPartDriver() {
T = NullLocalizer.Instance;
}
public Localizer T { get; set; }
protected override string Prefix { get { return "CustomSettings"; } }
protected override DriverResult Editor(CustomSettingsPart part, dynamic shapeHelper) {
return ContentShape("Parts_CustomSettings_Edit",
() => shapeHelper.EditorTemplate(TemplateName: TemplateName, Model: part, Prefix: Prefix))
.OnGroup("custom");
}
protected override DriverResult Editor(CustomSettingsPart part, IUpdateModel updater, dynamic shapeHelper) {
return ContentShape("Parts_CustomSettings_Edit", () => {
var previousPassword = part.MerchantPassword;
updater.TryUpdateModel(part, Prefix, null, null);
// restore password if the input is empty, meaning it has not been changed
if (String.IsNullOrEmpty(part.MerchantPassword)) {
part.MerchantPassword = previousPassword;
}
return shapeHelper.EditorTemplate(TemplateName: TemplateName, Model: part, Prefix: Prefix)
.OnGroup("custom");
});
}
}
Views/EditorTemplates/Parts/CustomSettings.cshtml
#model CustomModule.Models.CustomSettingsPart
#{
Script.Require("jQuery");
}
<fieldset>
<legend>#T("Custom Settings")</legend>
<div>
<label for="#Html.FieldIdFor(m => m.GatewayUrl)">#T("Gateway Url")</label>
#Html.EditorFor(m => m.GatewayUrl)
#Html.ValidationMessage("GatewayUrl", "*")
</div>
<div>
<label for="#Html.FieldIdFor(m => m.MerchantId)">#T("Merchant ID")</label>
#Html.EditorFor(m => m.MerchantId)
#Html.ValidationMessage("MerchantId", "*")
</div>
<div>
<label for="#Html.FieldIdFor(m => m.MerchantPassword)">#T("Merchant Password")</label>
#Html.PasswordFor(m => m.MerchantPassword)
#Html.ValidationMessage("MerchantPassword", "*")
</div>
<div>
#Html.EditorFor(m => m.SandboxMode)
<label for="#Html.FieldIdFor(m => m.SandboxMode)" class="forcheckbox">#T("Enable Sandbox Mode (for testing)")</label>
#Html.ValidationMessage("SandboxMode", "*")
</div>
<div id="sandboxSettings">
<div>
<label for="#Html.FieldIdFor(m => m.SandboxGatewayUrl)">#T("Sandbox Gateway Url")</label>
#Html.EditorFor(m => m.SandboxGatewayUrl)
#Html.ValidationMessage("SandboxGatewayUrl", "*")
</div>
<div>
<label for="#Html.FieldIdFor(m => m.SandboxMerchantId)">#T("Sandbox Merchant ID")</label>
#Html.EditorFor(m => m.SandboxMerchantId)
#Html.ValidationMessage("SandboxMerchantId", "*")
</div>
<div>
<label for="#Html.FieldIdFor(m => m.SandboxMerchantPassword)">#T("Sandbox Merchant Password")</label>
#Html.EditorFor(m => m.SandboxMerchantPassword)
#Html.ValidationMessage("SandboxMerchantPassword", "*")
</div>
</div>
</fieldset>
#using (Script.Foot()) {
<script>
$('##Html.FieldIdFor(m => m.SandboxMode)').on('click', function() {
$('#sandboxSettings').toggle($(this).prop('checked'));
});
</script>
}
I have the Placement.info and I can access the View through the "Custom" menu item underneath "Settings" in the main menu. The View loads fine, and when I enter some details and click 'Save', the form is sent find and will hit the CustomSettingsPartDriver.cs DriverResult Editor(CustomSettingsPart part, IUpdateModel updater.. method.
I believe this is where the issue could be, as it doesn't hit any breakpoints inside the return ContentShape("Parts_CustomSettings_Edit, () => { lambda expression.
Could anyone shed any light on how I can resolve this? I'm sure it's a simple issue, but I've been trying to figure this one out for a while unsuccessfully. Thanks!
Okay, I managed to figure this one out. I feel silly asking questions then answering them myself, but if it saves one person time in the future, then it's worth it.
The issue was, as I expected, in the Driver. I appended the .OnGroup() extension to the wrong Shape.
Below is the fixed Driver code:
protected override DriverResult Editor(CustomSettingsPart part, IUpdateModel updater, dynamic shapeHelper) {
return ContentShape("Parts_CustomSettings_Edit", () => {
var previousPassword = part.MerchantPassword;
updater.TryUpdateModel(part, Prefix, null, null);
// restore password if the input is empty, meaning it has not been changed
if (String.IsNullOrEmpty(part.MerchantPassword)) {
part.MerchantPassword = previousPassword;
}
return shapeHelper.EditorTemplate(TemplateName: TemplateName, Model: part, Prefix: Prefix);
// Offending extension -> .OnGroup("custom");
}).OnGroup("custom"); // In the correct location
}
*facepalm*

Resources