DropDownListFor in an MVC ASP application - asp.net-mvc-5

I have a view model which you see below
public class AssetCategoryViewModel : IEnumerable<AssetCategory>
{
DataModelContext db = new DataModelContext();
public AssetCategory AssetCategory { get; set; }
public Guid Id { get; set; }
public IList<AssetCategory> AssetCategoryList { get; set; }
public IEnumerable<SelectListItem> AssetCategoryNames { get; set; }
public AssetCategoryViewModel()
{
AssetCategoryList = (from x in db.AssetCategory
select x).ToList();
AssetCategoryNames = (from x in db.AssetCategory
select new SelectListItem
{
Text = x.Name,
Value = x.Code
});
}
public IEnumerator<AssetCategory> GetEnumerator()
{
return AssetCategoryList.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
throw new NotImplementedException();
}
}
and I want to send SelectlistItem to to view for using dropdownlist.
action method :
public ActionResult AddNewCategory()
{
AssetCategoryViewModel acvm = new AssetCategoryViewModel();
return View(acvm);
}
and div class (included dropdownlistfor helper method)
<div class="form-group">
#Html.LabelFor(model => model.AssetCategory.MasterCategory, htmlAttributes: new { #class = "control-label" })
#Html.DropDownListFor(model => model.AssetCategoryNames, Model.AssetCategoryNames, "Seçim yapınız", htmlAttributes: new { #class = "form-control" })
</div>
and I got error
Server Error in '/' Application.
Object reference not set to an instance of an object.
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: System.NullReferenceException: Object reference not set to an instance of an object.
I am stuck on this.

I reccomend you to do this way to make dropdownlistfor
Instead of using view model you create a model class and add a function which retrieves the data from the database
your model class should be like,`
public list<selectListItem> function()
{
AssetCategoryList = (from x in db.AssetCategory
select x).ToList();
AssetCategoryNames = (from x in db.AssetCategory
select new SelectListItem
{
Text = x.Name,
Value = x.Code
});`
}
return list;
and it should return the value as list.
then the controller part should be
public ActionResult AddNewCategory()
{
AssetCategoryModel acm = new AssetCategoryModel();
viewmodel vm=new viewmodel();
vm.assetcatagorylist=acm.function();
return View("viewname",vm);
}
then you should make to render the data to the view by passing the viewmodel data to ur view,
your view be like
#using projectname.ViewModel
#model ViewModel
<html>
//your design part
#Html.DropDownListFor(m => m.assetcatagorylist, Model.assetcatagorylist, new { #id ="hi" })
</html>
IT WORKS PERFECTLY
CHEERS,

thank you very much.
I forgot to add new model to actionresult view.
I have changed the controller part as below and I worked
public ActionResult AddNewCategory()
{
AssetCategoryViewModel acvm = new AssetCategoryViewModel();
return View(acvm);
}
thanks.

Related

Use a part from a different module in Orchard

What I'm trying to do is create a site in Orchard that doesn't have a way for a user to register. An administrator will create the users.
What I have is module that defines the parts, records, views, etc. That is basically working.
Now what I'm trying to do is add a UserPart (from Orchard.Users) to one of the parts in my module.
I'm not sure how to do that. I need the fields displayed for the UserPart with the fields for the parent part in the same view. This also needs to be done in a way that when a save happens, all of the UserPart fields get sent to the Orchard.Users module.
Any suggestions, pointers or links on how to do that?
Thanks!
UPDATE...
The Activating Filter is an interesting idea. I initially chose the migration route. For now, I'll try and get that method working.
For simplicity, let's say I have a "Company" type (there's more to the actual type) that has a "CompanyName" and a UserPart.
Here's what the different pieces look like...
Migrations.cs (simplified)
public int Create()
{
SchemaBuilder.CreateTable("CompanyPartRecord", table => table.ContentPartRecord()
.Column("CompanyName", DbType.AnsiString, c => c.WithLength(50))
.Column("UserId", DbType.Int32));
SchemaBuilder.CreateForeignKey("FK_CompanyPartRecord_UserPartRecord", "CompanyPartRecord", new[] {"UserId" }, "Orchard.Users", "UserPartRecord", new[] { "Id" })
ContentDefinitionManager.AlterTypeDefinition("Company", type => type.WithPart("CommonPart").WithPart("UserPart"));
}
CompanyPartRecord
public class CompanyPartRecord : ContentPartRecord
{
public virtual string CompanyName { get; set; }
public virtual int? UserId { get; set; }
}
CompanyPart
public class CompanyPart : ContentPart<CompanyPartRecord>
{
internal LazyField<UserPart> UserPartField = new LazyField<UserPart>();
public string CompanyName
{
get { return Record.CompanyName; }
set { Record.CompanyName = value; }
}
public UserPart User
{
get { return UserPartField.Value;}
set { UserPartField.Value = value; }
}
}
Handler
public class CompanyPartHandler : ContentHandler
{
private readonly IContentManager _manager;
public CompanyPartHandler(IRepository<CompanyPartRecord> repository, IContentManager manager)
{
_manager = manager;
Filters.Add(StorageFilter.For(repository));
OnActivated<CompanyPart>(OnActivatedHandler);
}
private void OnActivatedHandler(ActivatedContentContext context, CompanyPart part)
{
if(part.User == null)
{
part.User = _manager.Create<UserPart>("User");
}
else
{
part.User = _manager.Get<UserPart>(part.User.Id);
}
}
}
Driver
public class CompanyPartDriver : ContentPartDriver<CompanyPart>
{
protected override DriverResult Editor(CompanyPart part, dynamic shapeHelper)
{
return ContentShape("Parts_Company_Edit", () => shapeHelper.EditorTemplate(TemplateName: "Parts/Company",
Model: part, Prefix: Prefix));
}
protected override DriverResult Editor(CompanyPart part, IUpdateModel updater, dynamic shapeHelper)
{
updater.TryUpdateModel(part, Prefix, null, null);
return Editor(part, shapeHelper);
}
}
Controller
public class AdminCompanyController : Controller, IUpdateModel
{
private readonly IOrchardServices _services;
private readonly INotifier _notifier;
private readonly IContentManager _contentManager;
private readonly ITransactionManager _transactionManager;
private readonly Localizer T = NullLocalizer.Instance;
public AdminCompanyController(IOrchardServices services)
{
_services = services;
_notifier = services.Notifier;
_contentManager = services.ContentManager;
_transactionManager = services.TransactionManager;
}
public ActionResult Create()
{
var company = _contentManager.New<CompanyPart>("Company");
var model = _contentManager.BuildEditor(company);
return View(model);
}
[HttpPost, ActionName("Create")]
public ActionResult CreatePOST()
{
var contentItem = _contentManager.New<CompanyPart>("Company");
var model = _contentManager.UpdateEditor(contentItem, this);
if (!ModelState.IsValid)
{
_transactionManager.Cancel();
return View(model);
}
_contentManager.Create(contentItem.ContentItem);
_notifier.Information(T("Company has been saved"));
return RedirectToAction("Index");
}
public ActionResult Edit(int Id)
{
var contentItem = _services.ContentManager.Get(Id);
dynamic model = _services.ContentManager.BuildEditor(contentItem);
return View(model);
}
[HttpPost, ActionName("Edit")]
public ActionResult EditPOST(int Id)
{
var contentItem = _contentManager.Get<CompanyPart>(Id);
var model = _contentManager.UpdateEditor(contentItem, this);
_notifier.Information(T("Company has been saved"));
return RedirectToAction("Index");
}
public ActionResult Delete(int Id)
{
var contentItem = _contentManager.Get<CompanyPart>(Id);
_contentManager.Destroy(contentItem.ContentItem);
return RedirectToAction("Index");
}
bool IUpdateModel.TryUpdateModel<TModel>(TModel model, string prefix, string[] includeProperties, string[] excludeProperties)
{
return TryUpdateModel(model, prefix, includeProperties, excludeProperties);
}
public void AddModelError(string key, LocalizedString errorMessage)
{
ModelState.AddModelError(key, errorMessage.ToString());
}
}
View (create)
#{ Layout.Title = T("Add Company").ToString(); }
#using (Html.BeginFormAntiForgeryPost())
{
#Display(Model)
}
Editor Template
#model SDS.Models.CompanyPart
<fieldset>
#Html.LabelFor(m => m.CompanyName)
#Html.TextBoxFor(m => m.CompanyName)
</fieldset>
#*
What goes here to display UserPart?
*#
So here's where I'm at. I can see the ContentItem (CompanyType). I can put in the name and save it. The name is getting saved to the db. Right now the UserPart is getting saved to the db, but all of the fields are blank.
The part I'm stuck on is what to put in the editor template to display the UserPart fields so that the values get to the UserPart driver and ultimately the db.
Any ideas on how to do that?
Thanks!
So you don't attach parts to parts, you attach parts to content items, and you can do that in multiple ways.
You can do it through the admin screen, but that isn't a code driven solution and would have problems if you have multiple environments or need to redeploy a fresh version of code.
You can attach the part when you create a new content item in the migration. This might be a good solution, if you already ran your migration you could possibly do it with an update migration. This allows the part to be managed through the admin screen, but has downsides because it can be removed and if you have code that relies on the part then you will start having errors.
The last way and best way is to attach the part dynamically using an Activating Filter.
ActivatingFilter class - Attaches a part to a content type from code. As opposed to attaching parts via migrations, parts attached using this filter will neither be displayed in the Dashboard, nor users will be able to remove them from types. It's a legitimate way of attaching parts that should always exist on a given content type.
So to do this:
1. Add a reference to Orchard.Users to your custom project.
2. Create a handler for you part. Such as MyPartHandler
3. Then add the activating handler like so
Filters.Add(ActivatingFilter.For<UserPart>("MyContentType"));
So now anywhere in your code you can access the UserPart if you already have your part, or the content item using
var userPart = myPart.As<UserPart>();

Saving data on postback in Orchard

I'm an Orchard newbie and I'm having difficulty trying to get the form data when a new item is created.
What I have is a module that creates a menu item on the admin dashboard. That menu item will load a page where a user can enter a new "Coach".
There are 3 things needed for a coach, first name, last name and email.
Here's the code I have implemented for this...
migrations.cs
public class SDSDataMigration : DataMigrationImpl
{
public int Create()
{
SchemaBuilder..CreateTable("CoachPartRecord", table => table.ContentPartRecord()
.Column("FirstName", DbType.AnsiString, c => c.WithLength(50))
.Column("LastName", DbType.AnsiString, c => c.WithLength(50))
.Column("Email", DbType.AnsiString, c => c.WithLength(200)))
ContentDefinitionManager.AlterPartDefinition("CoachPart", part => part
.WithField("FirstName", f => f.OfType("TextField"))
.WithField("LastName", f => f.OfType("TextField"))
.WithField("Email", f => f.OfType("TextField"))
);
ContentDefinitionManager.AlterTypeDefinition("Coach", type => type.WithPart("CommonPart")
.WithPart("CoachPart"));
return 1;
}
}
parts/records
public class CoachPartRecord : ContentPartRecord
{
public virtual string FirstName { get; set; }
public virtual string LastName { get; set; }
public virtual string Email { get; set; }
}
public class CoachPart : ContentPart<CoachPartRecord>
{
public string FirstName
{
get { return Record.FirstName; }
set { Record.FirstName = value; }
}
public string LastName
{
get { return Record.LastName; }
set { Record.LastName = value; }
}
public string Email
{
get { return Record.Email; }
set { Record.Email = value; }
}
}
view for creating editor
#{ Layout.Title = T("Add Coach").ToString(); }
#using (Html.BeginFormAntiForgeryPost()) {
// Model is a Shape, calling Display() so that it is rendered using the most specific template for its Shape type
#Display(Model)
}
handler
public class CoachPartHandler : ContentHandler
{
public CoachPartHandler(IRepository<CoachPartRecord> repository)
{
Filters.Add(StorageFilter.For(repository));
}
}
driver
protected override DriverResult Editor(CoachPart part, IUpdateModel updater, dynamic shapeHelper)
{
updater.TryUpdateModel(part, Prefix, null, null);
return Editor(part, shapeHelper);
}
controller (for dashboard menu item)
public ActionResult Create()
{
var coach = _services.ContentManager.New("Coach");
var model = _services.ContentManager.BuildEditor(coach);
return View(model);
}
[HttpPost, ActionName("Create")]
public ActionResult CreatePOST()
{
var contentItem = _services.ContentManager.New("Coach");
_services.ContentManager.Publish(contentItem);
return View("Index");
}
Right now I can get the form to appear to create a new coach. When I hit "Publish" all of the fields (i.e. FirstName, LastName, Email) for the CoachPart parameter in the driver are null.
I can look at the http request and I can see the values I put on the form, but they're not making it to the CoachPart.
Any ideas why the CoachPart fields aren't getting filed in?
Thanks!
First of all, you are defining the properties on your own record. Therefore you don't need new textfields attached to your part, so you should remove this:
ContentDefinitionManager.AlterPartDefinition("CoachPart", part => part
.WithField("FirstName", f => f.OfType("TextField"))
.WithField("LastName", f => f.OfType("TextField"))
.WithField("Email", f => f.OfType("TextField")));
Secondly, because you use your custom controller istead of orchard's content controller, you must implement the IUpdateModel and act on it:
[Admin]
public class MyController : Controller, IUpdateModel {
private readonly IContentManager _contentManager;
private readonly ITransactionManager _transactionManager;
public MyController(IContentManager contentManager,
ITransactionManager transactionManager) {
_contentManager = contentManager;
_transactionManager = transactionManager;
}
[HttpPost, ActionName("Create")]
public ActionResult CreatePOST()
{
var contentItem = _contentManager.New<CoachPart>("Coach");
// The implementation of IUpdateModel is necessary for this next line:
var model = _contentManager.UpdateEditor(contentItem, this);
if (!ModelState.IsValid) {
_transactionManager.Cancel();
return View(model);
}
_contentManager.Publish(contentItem);
return View("Index");
}
bool IUpdateModel.TryUpdateModel<TModel>(TModel model, string prefix, string[] includeProperties, string[] excludeProperties) {
return TryUpdateModel(model, prefix, includeProperties, excludeProperties);
}
void IUpdateModel.AddModelError(string key, LocalizedString errorMessage) {
ModelState.AddModelError(key, errorMessage.ToString());
}
}

Passing a list to partialview, BeginCollectionItem()

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>
}

How to get data from two occurences of same partial view using encapsulting class model

I have an autocompleter as a partial view which I would like to use twice in the same page. There are three models involved
public partial class CustomParent
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Int64 CustomParentId { get; set; }
public Geoname ParentGeoname { get; set; }
public Geoname ChildGeoname { get; set; }
public CustomParent()
{
ParentGeoname = new Geoname();
ChildGeoname = new Geoname();
}
}
public partial class Geoname
{
[Required]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Int64 GeonameId { get; set; }
public string Name { get; set; }
}
public partial class GeonameWithFilter
{
public GeonameWithFilter()
{
FilterString = "";
}
public Geoname Geoname { get; set; }
public String FilterString { get; set; }
}
I have a controller set up
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "CustomParentId,ParentGeoname,ChildGeoname")] CustomParent customParent)
{...}
I set up the two Html partials on my create view initially only using the customparent and geoname models and it worked fine setting the values in of the parent and child geonames as expected. I now require additional parameters to be passed to the partial view so I created an encapsulating class (GeonameWithFilter). I made changes to my partial view and to my two html.partials on the view page which now look like this :
#Html.Partial("_GeonameAutocomplete",new GeonameWithFilter(){Geoname = Model.ParentGeoname,FilterString="'featurecodes':'CUPA'"},
new ViewDataDictionary()
{
TemplateInfo = new TemplateInfo() { HtmlFieldPrefix = "ParentGeoname" }
})
#Html.Partial("_GeonameAutocomplete", new GeonameWithFilter() { Geoname = Model.ChildGeoname, FilterString = "'featurecodes':'ADM1,ADM2,ADM3,ADM4'" },
new ViewDataDictionary()
{
TemplateInfo = new TemplateInfo() { HtmlFieldPrefix = "ChildGeoname" }
})
The problem is that the customparent.parentGeoname and customparent.childGeoname are not now getting returned to my controller. I'm guessing this is because the partial view's model is not the same class as my page models parentGeoname and childGeoname but cannot work out or find any examples of how to handle such a circumstance if indeed it is possible.
Well - it took me most of a day, but I now have what I required.
I gave up on the encapsulation and instead added my basic geoname model to the new ViewDataDictionary and nullified the default model for the Html.Partial.
I then added the filterstring parameter as a key in a new ViewDataDictionary which I used as the argument for the one with the model and TemplateInfo.
Many thanks to https://stackoverflow.com/users/7714/craig-stuntz for his answer to a different question Shorthand for creating a ViewDataDictionary with both a model and ViewData items? that pointed me in the direction I have gone.
My autocompleter now just use #ViewData["FilterString"] to access the filter parameters. The GeonameWithFilter encapsulation is no longer needed. My two Html.Partials on my view page now look like this:
#Html.Partial("_GeonameAutocomplete", null,
new ViewDataDictionary(new ViewDataDictionary() { {"FilterString", "featurecodes:'CUPA'" }})
{
Model = Model.ParentGeoname,
TemplateInfo = new TemplateInfo { HtmlFieldPrefix = "ParentGeoname" }
})
#Html.Partial("_GeonameAutocomplete", null,
new ViewDataDictionary(new ViewDataDictionary() { { "FilterString", "featurecodes:'ADM1,ADM2,ADM3,ADM4'" } })
{
Model = Model.ChildGeoname,
TemplateInfo = new TemplateInfo { HtmlFieldPrefix = "ChildGeoname" }
})
If anyone knows a better way to achieve the end result I would still like to hear it.

Orchard throws 'The model item passed into the dictionary is of type Part but this dictionary requires a model of type VM

This is an editor for WebShop Global Settings. I needed to extend the editor with a ViewModel. It worked fine before I started but now crashes with the above error when it's invoked. What am I doing wrong?
Here's the driver:
public class WebShopSettingsPartDriver : ContentPartDriver<WebShopSettingsPart>
{
private readonly ISiteService _siteService;
private readonly IWebshopSettingsService _webshopSettings;
protected override string Prefix { get { return "WebShopSettings"; } }
private const string shapeName = "Parts_WebShopSettings_Edit";
private const string templateName = "Parts/WebShopSettings";
public WebShopSettingsPartDriver(IWebshopSettingsService webshopSettings, ISiteService siteService)
{
_webshopSettings = webshopSettings;
_siteService = siteService;
}
protected override DriverResult Editor(WebShopSettingsPart part, dynamic shapeHelper)
{
var settings = _siteService.GetSiteSettings().As<WebShopSettingsPart>();
var model = new WebShopSettingsVM
{
WebShopSettings = settings,
ShippingProducts = _webshopSettings.ShippingProductRecords()
};
return ContentShape(shapeName,
() => shapeHelper.EditorTemplate(TemplateName: templateName, Model: model, Prefix: Prefix)).OnGroup("WebShop");
}
}
}
Here is the Handler:
public class WebShopSettingsPartHandler : ContentHandler {
public WebShopSettingsPartHandler(IRepository<WebShopSettingsRecord> repository) {
T = NullLocalizer.Instance;
Filters.Add(new ActivatingFilter<WebShopSettingsPart>("Site"));
Filters.Add(StorageFilter.For(repository));
OnGetContentItemMetadata<WebShopSettingsPart>((context, part) => context.Metadata.EditorGroupInfo.Add(new GroupInfo("WebShop")));
}
}
And here is the first line of the View (which is in Views\EditorTemplates\Parts\WebShopSettings.cshtml):
#model Cascade.WebShop.ViewModels.WebShopSettingsVM
The Placement.ini file has the following entry:
<Place Parts_WebShopSettings_Edit="Content:0" />
Here is the ViewModel:
public class WebShopSettingsVM
{
public IEnumerable<ShippingProductRecord> ShippingProducts{ get; set; }
[Required]
public int? ShippingProductRecordId { get; set; }
public WebShopSettingsPart WebShopSettings { get; set; }
// Expose all the properties of the Part directly on the VM
[Required]
public string AdministratorEmailAddress
{
get { return WebShopSettings.AdministratorEmailAddress; }
set { WebShopSettings.AdministratorEmailAddress = value; }
}
[Required]
public string ContinueShoppingUrl
{
get { return WebShopSettings.ContinueShoppingUrl; }
set { WebShopSettings.ContinueShoppingUrl = value; }
}
// and so on...
}
After Bertrand's suggestion below I updated the View to:
#using Cascade.WebShop.ViewModels
#using Cascade.WebShop.Models
#{
var vm = Model.Model as WebShopSettingsVM;
}
<fieldset>
<legend>#T("Webshop")</legend>
<div>
<label for="#Html.FieldIdFor(x=>vm.AdministratorEmailAddress)">#T("Administrator email address")</label>
#Html.TextBoxFor(x=>vm.AdministratorEmailAddress, new { #class = "textMedium" })
#Html.ValidationMessage("AdministratorEmailAddress", "*")
...
Insights and suggestions greatly appreciated -- I simply can't see what's wrong.
A second copy of the driver, under a slightly different name, was present in the 'Helpers' directory. Not surprisingly, I didn't notice this. The 'Helper' driver was supplying a Part and the 'proper' driver a VM. Both were being fired and thus whether I used a VM or a Part one or other of the two drivers would throw an exception.
Deleting the spurious driver fixed the problem. Sorry Bertrand.
The model is still the shape. Remove the directive, and access Model.Model to access your view model: Model is the shape, and Model.Model is the property named Model that is on the shape.
Just cast Model.Model to the view model's type.

Resources