I've been having a little problem creating a simple widget. All I want to do is create a widget that has the a MediaPickerField and 2 int and 2 string fields that are stored in the database using a part. Everything works as advertised. ContentRecord is created and the image field is properly stored, except that the four fields are not being stored. There are no error in the logs and cannot seem to see why this is happening. Has anyone come across this before? Thank you.
Migration.cs
public int UpdateFrom1()
{
SchemaBuilder.CreateTable("SchoenNavButtonPartRecord",
table => table.ContentPartRecord()
.Column<string>("Url", col=> col.WithLength(2140))
.Column<string>("Text")
.Column<int>("ButtonWidth")
.Column<int>("ButtonHeight"));
ContentDefinitionManager.AlterTypeDefinition("SchoenNavButton", builder =>
builder.WithPart("CommonPart")
.WithPart("SchoenNavButtonPart")
.WithPart("TitlePart")
.Creatable());
ContentDefinitionManager.AlterPartDefinition("SchoenNavButtonPart", builder =>
builder.WithField("ButtonImage", field=>
field.OfType("MediaPickerField")
.WithDisplayName("Button Image")
.WithSetting("Hint", "Select Image for Button")));
return 2;
}
PartRecord
public class SchoenNavButtonPartRecord : ContentPartRecord
{
public virtual string Url { get; set; }
public virtual string Text { get; set; }
public virtual int ButtonWidth { get; set; }
public virtual int ButtonHeight { get; set; }
}
Part
public class SchoenNavButtonPart : ContentPart<SchoenNavButtonPartRecord>
{
public string Url { get; set; }
public string Text { get; set; }
[DefaultValue(296)]
public int ButtonWidth { get; set; }
[DefaultValue(188)]
public int ButtonHeight { get; set; }
}
Handler
public class SchoenNavButtonHandler : ContentHandler
{
public SchoenNavButtonHandler(IRepository<SchoenNavButtonPartRecord> buttonImageLinkRepository)
{
Filters.Add(StorageFilter.For(buttonImageLinkRepository));
}
}
Driver
public class SchoenNavButtonPartDriver : ContentPartDriver<SchoenNavButtonPart>
{
public SchoenNavButtonPartDriver()
{
}
protected override string Prefix
{
get
{
return "SchoenNavButton";
}
}
protected override DriverResult Display(SchoenNavButtonPart part, string displayType, dynamic shapeHelper)
{
var fields = ((ContentPart) part).Fields.OfType<MediaPickerField>();
MediaPickerField mediaPickerField = null;
if(fields.Any())
{
mediaPickerField = fields.ElementAt(0);
}
return ContentShape("Parts_SchoenNavButton",
() => shapeHelper.Parts_SchoenNavButton(
SchoenNavButtonPart: part,
ImageUrl: mediaPickerField == null ? "#" : mediaPickerField.Url
));
}
protected override DriverResult Editor(SchoenNavButtonPart part, dynamic shapeHelper)
{
return ContentShape("Parts_SchoenNavButton_Edit", () =>
shapeHelper.EditorTemplate(
TemplateName: "Parts/SchoenNavButton",
Model: part,
Prefix: Prefix));
}
protected override DriverResult Editor(SchoenNavButtonPart part, Orchard.ContentManagement.IUpdateModel updater, dynamic shapeHelper)
{
updater.TryUpdateModel(part, Prefix, null, null);
return Editor(part, shapeHelper);
}
}
EditorTemplage
#model FishySoftware.SchoenBuilders.Models.SchoenNavButtonPart
<fieldset>
<legend>Button Details</legend>
<div class="editor-label">
#Html.LabelFor(model => model.Url, T("Url"))
</div>
<div class="editor-field">
#Html.TextBoxFor(model => model.Url)
#Html.ValidationMessageFor(model => model.Url)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Text, T("Text"))
</div>
<div class="editor-field">
#Html.TextBoxFor(model => model.Text)
#Html.ValidationMessageFor(model => model.Text)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.ButtonWidth, T("Button Width"))
</div>
<div class="editor-field">
#Html.TextBoxFor(model => model.ButtonWidth)
#Html.ValidationMessageFor(model => model.ButtonWidth)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.ButtonHeight, T("Button Height"))
</div>
<div class="editor-field">
#Html.TextBoxFor(model => model.ButtonHeight)
#Html.ValidationMessageFor(model => model.ButtonHeight)
</div>
</fieldset>
Orchard is always using content of Record classes to save data to the database. You, on the other hand, are saving data from the browser inside the Part class and you haven't properly connected your Part and Record classes.
To do that, you'll have to change your ShoenNavButtonPart to this:
public class SchoenNavButtonPart : ContentPart<SchoenNavButtonPartRecord>
{
public string Url {
get { return Record.Url; }
set { Record.Url = value; }
}
public string Text {
get { return Record.Text; }
set { Record.Text = value; }
}
[DefaultValue(296)]
public int ButtonWidth {
get { return Record.ButtonWidth; }
set { Record.ButtonWidth = value; }
}
[DefaultValue(188)]
public int ButtonHeight {
get { return Record.ButtonHeight; }
set { Record.ButtonHeight = value; }
}
}
Related
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>
}
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>
}
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>
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*
I have recently created a new application that is using MVC4 and EF5. The tables already existed, so we are using Database first. This is all new to me, it is my first time in MVC or EF.
I have some models in my EMDX file, and in one of them I inherited IValidatableObject and put some code in the Validate function. This was working fine, then I changed my view to use a ViewModel, now I get an error from validate, and I am stumped. It is still calling my validate function, but no longer posting it back to the screen, i just get a yellow screen.
Error:
Validation failed for one or more entities. See 'EntityValidationErrors' property for more details.
Model:
public partial class Names : IValidatableObject {
public int Id { get; set; }
public string Name { get; set; }
public bool Active { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) {
// some logic, this works
}
}
ViewModel:
public class NamesVM {
public Names Name { get; set; }
// some other stuff in this model, but not part of this problem
}
Controller: Edit Function:
[HttpPost]
public ActionResult Edit(NamesVM nvm) {
if (ModelState.IsValid) {
dbCommon.Entry(nvm.Name).State = EntityState.Modified;
dbCommon.SaveChanges();
return RedirectToAction("Index");
}
return View(nvm);
}
View:
#model NamesVM
<div class="editor-label">
#Html.LabelFor(model => model.Name.Id)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Name.Id)
#Html.ValidationMessageFor(model => model.Name.Id)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Name.Name)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Name.Name)
#Html.ValidationMessageFor(model => model.Name.Name)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Name.Active)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Name.Active)
#Html.ValidationMessageFor(model => model.Name.Active)
</div>
<input type="submit" value="Save" />
The codes works fine if everything is correct on the screen, but when validation fails, i don't get a nice error, I get a yellow screen. I am sure I am missing something, I just don't know what.
finally solved it. The IValidatableObject needs to be moved to the ViewModel, as well as the logic for it.
Model
public partial class Names { //: IValidatableObject { //Remove the IValidateableObject from here
public int Id { get; set; }
public string Name { get; set; }
public bool Active { get; set; }
// this whole method can be commented out
//public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) {
// some logic, this works
//}
}
View Model
public class NamesVM : IValidatableObject { // add the IValidatableObject to your view model
public Names Name { get; set; }
// some other stuff in this model, but not part of this problem
// and move your validation logic to here
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) {
// some logic, this works
}
}