MVC5 Binding issue - asp.net-mvc-5

I'm trying to set up some basic navigation on a web site I'm rewriting and I've run into a brick wall and don't see why this is not working. I'm doing something similar in a half dozen other places but it just ain't working.
What I want to do is if my article has a next and or previous ID I want to show a navigation bar with appropriate forward/reverse navigation arrows or whatever to allow user to navigate pages.
The ViewModel
public class NavViewModel
{
public int NextID { get; set; }
public int PreviousID { get; set; }
public string NextString { get; set; }
public string PreviousString { get; set; }
public bool SelectedMode { get; set; }
public NavViewModel() { }
}
The View
#Html.HiddenFor(model => model.NavigationViewModel.PreviousID)
#Html.HiddenFor(model => model.NavigationViewModel.NextID)
<div class="post-nav">
#if (#Model.NavigationViewModel.PreviousString != null)
{
using (Html.BeginForm("SinglePost", "Article", FormMethod.Post, new { #nvm = Model.NavigationViewModel }))
{
<input type="submit" class="btn btn-default" value="#Model.NavigationViewModel.PreviousString" />
}
}
#if (#Model.NavigationViewModel.NextString != null)
{
using (Html.BeginForm("SinglePost", "Article", FormMethod.Post, new { nvm = #Model.NavigationViewModel }))
{
<input type="submit" class="btn btn-default" value="#Model.NavigationViewModel.NextString" />
}
}
</div>
and the Controller
[HttpPost]
public ActionResult SinglePost(NavViewModel nvm)
{
return RedirectToAction("SinglePost", "Article", new { postID = nvm.PreviousID });
}
I've tried passing back the bool, the IDs, the ViewModel and they all come null or containing null values.
I had this code in a PartialView and because it wasn't working I moved it up a level into the calling view and it has the same result.

You have stated you want to navigate to the next and previous items so using forms and inputs and submitting to a POST method is not appropriate. Instead use a link to navigate to a GET method, passing the ID of the previous or next item.
#if (#Model.NavigationViewModel.PreviousString != null)
{
#Html.ActionLink(Model.NavigationViewModel.PreviousString, "SinglePost", "Article", new { postID = Model.NavigationViewModel.PreviousID }, null)
}
#if (#Model.NavigationViewModel.NextString != null)
{
#Html.ActionLink(Model.NavigationViewModel.NextString , "SinglePost", "Article", new { postID = Model.NavigationViewModel.NextID }, null)
}
The reason your code does not work will be obvious when you inspect the html generated for the <form> tag. Your generating an attribute nvm="YourAssembly.NavigationViewModel" (not a route value). If you used the correct overload to generate route values, which would be
using (Html.BeginForm("SinglePost", "Article", new { #nvm = Model.NavigationViewModel }))
it will still fail because it will generate something similar to (depending on you routes) action="/Article/SinglePost?nvm=YourAssembly.NavigationViewModel" so when you post back, the DefaultModelBinder will try to assign the string "YourAssembly.NavigationViewModel" to parameter nvm, but nvm is a complex object, not a string, so binding will fail.
You could make the POST method work by using
using (Html.BeginForm("SinglePost", "Article", Model.NavigationViewModel))
however this is just degrading performance by posting back unnecessary data and if your model contained properties that were complex objects or collections, it would fail anyway, so don't do it.
Finally, if you want to make the link look like a button, then style it using css.

Try to move hidden inputs into the form
<div class="post-nav">
#if (#Model.NavigationViewModel.PreviousString != null)
{
using (Html.BeginForm("SinglePost", "Article", FormMethod.Post, new { #nvm = Model.NavigationViewModel }))
{
#Html.HiddenFor(model => model.NavigationViewModel.PreviousID)
<input type="submit" class="btn btn-default" value="#Model.NavigationViewModel.PreviousString" />
}
}
#if (#Model.NavigationViewModel.NextString != null)
{
using (Html.BeginForm("SinglePost", "Article", FormMethod.Post, new { nvm = #Model.NavigationViewModel }))
{
#Html.HiddenFor(model => model.NavigationViewModel.NextID)
<input type="submit" class="btn btn-default" value="#Model.NavigationViewModel.NextString" />
}
}
</div>

Related

Blazor synchronised confirm syncfusion

Is it possible to have a synchronised version of the Blazor Syncfusion confirm and prompt dialog?
Meaning that a modal dialog is shown from a method, awaits the yes/no confirmation, returns the users input and continues the method that initially showed the dialog?
We have validated the reported query. Yes, you can perform you actions based on the SfDialog button clicked, using the lambda expression. Check the below code block for reference.
#using Syncfusion.Blazor.Popups
#using Syncfusion.Blazor.Buttons
<SfButton #onclick="#OpenDialog">Open Dialog</SfButton>
<SfDialog Width="250px" ShowCloseIcon="true" IsModal="true" #bind-Visible="#IsVisible">
<DialogTemplates>
<Header> Dialog </Header>
<Content> This is a Dialog with button and primary button </Content>
</DialogTemplates>
<DialogButtons>
<DialogButton Content="Confirm" IsPrimary="true" OnClick='(e => CloseDialog("Yes"))' />
<DialogButton Content="Cancel" OnClick='(e => CloseDialog("No"))' />
</DialogButtons>
</SfDialog>
#code {
private bool IsVisible { get; set; } = true;
private void OpenDialog()
{
this.IsVisible = true;
}
private void CloseDialog(string userResponse)
{
if (userResponse == "Yes")
{
// Perform your action
this.IsVisible = false;
} else
{
this.IsVisible = true;
}
}
}
Please let us know if the solution helps,
Thanks NDRA JITH,
You suggestion contains two methods, it would be nice if it's possible in one method, just like the confirm method in JavaScript. So open the Confirm dialog wait for the user to answer and based on the answer continue in the same method.
In Blazored it is possible (but I want it in Syncfusion) with the following code:
Parent screen:
#inject IModalService Modal
<BlazoredModal />
#code {
var modalImport = Modal.Show<ModalImport>("Title comes here");
var result = await modalImport.Result;
if (!result.Cancelled)
{
var doSomethingWithThisResult = result.Data;
}
}
Modal screen:
#inject IModalService ModalService;
<button #onclick="HandleStartImport" class="btn btn-secondary">Start</button>
<button #onclick="No" class="btn btn-secondary">Cancel</button>
#code {
[CascadingParameter] BlazoredModalInstance BlazoredModal { get; set; }
async Task HandleStartImport()
{
BlazoredModal.Close(ModalResult.Ok("I clicked OK!"));
}
void No() => BlazoredModal.Cancel();
}

MVC 5 Ajax.BeginForm Submit button calls current page instead of URL of Controller and Action specified

I'm trying to use Ajax.BeginForm, so I can pass the value of the checkbox to my Controller Action.
I was using #Ajax.ActionLink, but I can't get the value of the newly introduced checkbox, so I want to use Ajax.BeginForm going forward.
Since #Ajax.ActionLink is working in the View, I assume that Ajax is working properly, so I can rule that out.
Here are a couple of options I have tried. When I hover over the buttons, they both display the URL of my web page instead of the URL of the Controller and Action. When I click on the buttons, the page basically refreshes instead of calling the GetCode Action in the PublicOffers Controller.
Any help is much appreciated! Thanks!
Option 1
<div>
#using (Ajax.BeginForm("GetCode", "PublicOffers", null, new AjaxOptions()
{
UpdateTargetId = "detailsDiv",
InsertionMode = InsertionMode.Replace,
HttpMethod = "GET",
OnBegin = "onStart",
OnComplete = "onComplete",
OnSuccess = "myCallback"
}))
{
//bool checkedOption = false;
//#Html.CheckBoxFor(x => checkedOption, new { Name = "OptIn" })
#Html.CheckBoxFor(model => model.OptIn);
#Html.HiddenFor(model => model.Id);
<input type="submit" value="Get Code" class="button btn-primary" />
}
</div>
Option 2
<div>
#using (Ajax.BeginForm(new AjaxOptions()
{ HttpMethod = "GET",
Url = "PublicOffers/GetCode",
InsertionMode = InsertionMode.Replace,
UpdateTargetId = "detailsDiv",
OnBegin = "onStart",
OnComplete = "onComplete",
OnSuccess = "myCallback"
}))
{
//bool checkedOption = false;
//#Html.CheckBoxFor(x => checkedOption, new { Name = "OptIn" })
#Html.CheckBoxFor(model => model.OptIn)
//#Html.HiddenFor(model => model.Id, new { Name = "OfferId" })
#Html.HiddenFor(model => model.Id);
#Html.AntiForgeryToken()
<input type="submit" value="Submit" />
}
</div>
This is not exactly an answer but it might help.
First be sure you have this, I use NuGet to install it,
Microsoft.jQuery.Unobtrusive.Ajax
Then be sure it gets emitted to the page, either though bundle scripts or a script link in the cshtml
First a simple model...
namespace AjaxTest.Models
{
public class AjaxTestModel
{
public bool OptIn { get; set; }
}
}
Next some cshtml...
#model AjaxTest.Models.AjaxTestModel
#{ ViewBag.Title = "AjaxTest1";}
<h2>AjaxTest1</h2>
#using (Ajax.BeginForm("AjaxTest1", "Home", null,
new AjaxOptions
{
HttpMethod = "POST",
OnSuccess = "OnSuccess(xhr)",
OnFailure = "OnFailure(xhr, status)"
},
new { id = "myform" }))
{
#Html.CheckBoxFor(model => model.OptIn);
<span id="lbl_message"></span>
<br />
<button id="btn_submit" type="submit" class="">Submit</button>
}
#section scripts{
<script>
$(document).ready(function () {
console.log("ready to rock and roll....");
});
function OnBegin() {
console.log("OnBegin");
}
function OnSuccess(xhr) {
console.log("OnComplete");
$("#lbl_message").text("Ok:" + xhr.responseJSON.param2);
}
function OnFailure(xhr, status) {
console.log("OnFailure");
$("#lbl_message").text("Error:" + xhr.responseJSON.param2);
}
</script>
}
The magic is in the controller, notice that I am returning a Json object, not a view.
public class HomeController : Controller
{
public ActionResult AjaxTest1()
{
AjaxTestModel model = new AjaxTestModel();
return View();
}
[HttpPost]
public ActionResult AjaxTest1(AjaxTestModel model)
{
if (model.OptIn)
{
dynamic errorMessage = new { param1 = "param1", param2 = "You opted in." };
HttpContext.Response.StatusCode = (int)HttpStatusCode.OK;
return Json(errorMessage, JsonRequestBehavior.AllowGet);
}
else
{
dynamic errorMessage = new { param1 = "param1", param2 = "You opted out." };
HttpContext.Response.StatusCode = (int)HttpStatusCode.NotFound;
return Json(errorMessage, JsonRequestBehavior.AllowGet);
}
}
}
Note, The HttpContext.Response.StatusCode will control which Ajax callback is activate, return HttpStatusCode.Ok and the OnSuccess gets called, return the HttpStatusCode.NotFound, or most any other error code, and the OnFailure will get called.
I got it to work by adding a beginning Ajax.BeginForm method. It's almost like the first instance invokes the second Ajax.BeginForm method. Since there is no button or anything else in the first method, nothing displays on the page.
NOTE: I changed my Get to a Post so the [ValidateAntiForgeryToken] attribute would work on the Controller Action. I read that it would not work with a Get.
<div>
#using (Ajax.BeginForm("GetCode", "PublicOffers", new { offerId = Model.Id }, new AjaxOptions()
{
//Nothing here, but for some reason without this code the Ajax.BeginForm below won't work
}))
{
//Nothing here, but for some reason without this code the Ajax.BeginForm below won't work
}
</div>
<div>
#using (Ajax.BeginForm("GetCode", "PublicOffers", new { offerId = Model.Id },
new AjaxOptions { OnBegin = "onStart", UpdateTargetId = "detailsDiv",
InsertionMode = InsertionMode.Replace, HttpMethod = "Post",
OnComplete = "onComplete",
OnSuccess = "myCallback"},
new { #style = "display:inline-block" }))
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<div class="form-group">
#Html.CheckBoxFor(model => model.OptIn, new { Name = "optIn"})
</div>
</div>
<input type="submit" class="button btn-primary" value="Get Code" id="get-code button" />
}
</div>
Here is my Controller Action.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> GetCode(Guid offerId, bool optIn = false)
{
//Code here
return PartialView("_OfferCode");
}

Solving XSS issues

I am trying to build an MVC application and have been told it is not a good way to retrieve data and is susceptible to cross-site scripting. I have never done security and have been trying to learn as well but I cannot wrap my head around it.
I am guessing there are several flaws here. Is there any particular encoding I can use?
I haven't pasted the entire code here but trying to figure out where I can stop the XSS attacks.
Model and View Model
namespace ThePeopleSearchApplication.Models
{
public class UserViewModel
{
public string UID{ get; set; }
public string FName{ get; set; }
public string LName{ get; set; }
public string Email { get; set; }
public string Status{ get; set; }
}
public class UserModel
{
public string UID{ get; set; }
public string FName{ get; set; }
public string LName{ get; set; }
public string Email { get; set; }
public string Status{ get; set; }
}
}
Controller
namespace ThePeopleSearchApplication.Controllers
{
public class MyController : Controller
{
// GET: My
public ActionResult Index()
{
return View();
}
[ValidateInput(false)] //has been added to understand XSS better
public ActionResult SearchUserAjax(string userId)
{
UserModel myUser = fetchUserFromLdap(userId);
return Content("{\"message\": \"search for userId: " +
userId + " result\", \"result\": " + convertToJson(myUser) + " }");
}
private string convertToJson(UserModel myUser)
{
return "{ \"userId\": \"" + myUser.UserId + "\", \"FirstName\": \"" +
myUser.FirstName + "\", \"LastName\": \"" + myUser.LastName + "\", \"Email\": \"" +
myUser.Email + "\", \"Status\": \"" + myUser.Status + "\"}";
}
[ValidateInput(false)] //has been added to understand XSS better
public ActionResult SearchUser(string userId)
{
UserModel myUser = fetchUserFromLdap(userId);
var viewModel = new UserViewModel
{
UID = userId,
FName = myUser.FirstName,
LName = myUser.LastName,
Email = myUser.Email,
Status = myUser.Status,
};
return this.View(viewModel);
}
private UserModel fetchUserFromLdap(string userId)
{
var retVal = new UserModel();
if (String.IsNullOrEmpty(userId))
{
retVal.UID = "N/A";
retVal.FName = "N/A";
retVal.LName = "N/A";
retVal.Email = "N/A";
retVal.Status = "N/A";
}
else
{
retVal.UID = userId;
retVal.FName = "FirstName";
retVal.LName = "LastName";
retVal.Email = "email#example.com";
retVal.Status = "<div style=background-color:#F00800>My Status</div>";
}
return retVal;
}
}
}
View
#model ThePeopleSearchApplication.Models.UserViewModel
#{
ViewBag.Title = "Search result for user: " + Model.UserId;
var ulId = "ul-id" + Model.UserId;
var formId = "form" + Model.UserId;
}
<html>
<head>
#Scripts.Render("~/bundles/jquery")
#Scripts.Render("~/bundles/bootstrap")
</head>
<body>
<h1>Search result for user: #Model.UserId</h1>
<ul id="#Html.Raw(ulId)">
<li>#Model.FirstName</li>
<li>#Model.LastName</li>
<li>#Model.Email</li>
<li>#Html.Raw(Model.Status)</li>
</ul>
<form id=#formId name=#formId action=/My/SearchUser enctype="multipart/form-data">
<input type="text" name="userId" />
<input type="submit" />
</form>
<script type="text/javascript">
var theForm = document.#formId;
$(theForm).submit(function() {
alert('Valid form');
return true;
});
// just to demonstrate potential usage $(theForm).submit();
</script>
<div>
Ajax search:
<form id="ajax-search" name="ajax-search">
<input type="text" name="userId" />
<input type="submit" />
</form>
<script>
$("#ajax-search").submit(function() {
var url = "/My/SearchUserAjax"; // the script where you handle the form input.
$.ajax({
type: "POST",
url: url,
data: $("#ajax-search").serialize(), // serializes the form's elements.
success: function(data)
{
var obj = JSON.parse(data);
$('#ajax-search').append('<hr/>');
$('#ajax-search').append(obj.message); // show response from the php script.
$('#ajax-search').append('<hr/>');
$('#ajax-search').append(obj.result.userId);
$('#ajax-search').append('<hr/>');
$('#ajax-search').append(obj.result.FirstName);
$('#ajax-search').append('<hr/>');
$('#ajax-search').append(obj.result.LastName);
$('#ajax-search').append('<hr/>');
$('#ajax-search').append(obj.result.Status);
$('#ajax-search').append('<hr/>');
}
});
return false; // avoid to execute the actual submit of the form.
});
</script>
</div>
</body>
</html>
The principle problem is if the User controls some data that you render to the page in an unsafe way. Whether that be from their name (my name is <script>function() { nasty stuff is happening here... }</script>) or any other content.
I take the following approach, look at your output (or better think ahead about it) and see if it's a problem at each stage:
Let Razor do it, by default Razor handles encoding of all HTML
characters on the page, this doesn't apply if you use an
IHtmlString so avoid this Type (or methods that return it like
Html.Raw()) so #("<script>nastyThings()</script>") is a string so that will be encoded, and the script will not run
If it's broken, that means your string has some HTML/JS in it that
you actually want to render. So try and move that directly onto the
Razor template (HTML/JS) or obtain it via a link (JS)
Instead of the whole string being user controlled "<element onclick="javascript:alert('trouble')"></element>" with template #Html.Raw(Model.UserBadString)
Make the template <element onclick="mySafeJsFunction()">#Model.UserSafeString</element>", this takes control of the JS function away from the User, and leaves them with a Razor encoded parameter that they can't do XSS with
You want the User to have control over the HTML, then you will have to sanitise the string on output to the page, by using something like (https://github.com/mganss/HtmlSanitizer)
So the template could be #Html.Raw(sanitizer.Sanitize(Model.UserBadString)) but you'd probably want to do something nicer than that, good coding practice etc. The main point is that the string is sanitised
As an aside, make sure you keep a very close aye on usages of properties such as .innerHTML in JS (or the dreaded jQuery .html() which calls eval()), as if these take in user controlled content you'll have the exact same problem. But the same steps can be applied, (1) use .innerText instead, or (3) otherwise use a purification library on the string like DOMPurify before giving it to the JS (https://github.com/cure53/DOMPurify). Unfortunately in this case option (2) is not recommended, since whatever is left will have to be made safe by you or I, I'd rather trust DOMPurify to do it :)

Asp.net MVC 5 passing model object to controller via ActionLink

I need some help/advise on how to make this work.
I need to pass the model from the view to the controller through an ActionLink
#Html.ActionLink("Radera", "DeleteTraffic", new { model = Model, trafficId = traffic.Id }, new { #class = "btn btn-link NoBorder NoBackGround" })
the method in the controller looks like this.
public ActionResult DeleteTraffic(CalendarModel model, int trafficId)
{
return View("EditDay", model);
}
I have not put any code in the method yet, I've only been debugging it to get the call to work. model is null when I press the button, trafficId is however correctly set. so what have I done wrong?
Edit 1:
I've changed the code according to the suggestions here.
#using (Html.BeginForm("DeleteTraffic", "Calendar", new {trafficId = traffic.Id})) {<input type="submit" value="Radera" class="btn btn-link NoBorder NoBackGround"/>}
[HttpPost]
[ValidateAntiForgeryToken]
[ActionName("DeleteTraffic")]
public ActionResult DeleteTraffic(int trafficId)
{
return View("EditDay", Model);
}
but DeleteTraffic is never reched, instead it calls the Main Action for this page.
// GET: Calendar
public ActionResult Calendar()
{
CalendarModel model = new CalendarModel {SelectedDate = DateTime.Today};
if (Request.HttpMethod == "POST")
{
if (!string.IsNullOrEmpty(Request.Form.Get("submit.SelectDate")))
{
model.SelectedDate = Convert.ToDateTime(Request.Form["selectedDate"]);
model.TrafficDates = TrafficData.GeTrafficDatesPerMonth(model.SelectedDate);
Model = model;
return View("EditDay", Model);
}
}
Model = model;
return View(Model);
}
should I just tuck the trafficId into a hiddenfield and use this action for everything? MVC seems so inflexible at times...
First, something like a "delete" should never be handled by GET. Deleting is atomic and should be done utilizing either the POST or DELETE (preferably) verbs. Generally, you also should not just delete something without user confirmation, so the simplest and correct way to handle this would be to have the "delete" link take the user to a view that asks them to confirm deleting the item. On this view, then, you would submit the id of the item to be deleted via a form post:
public ActionResult Delete(int id)
{
var foo = db.Foos.Find(id);
if (foo == null)
{
return new HttpNotFoundResult();
}
return View(foo);
}
[HttpPost]
[ValidateAntiForgeryToken]
[ActionName("Delete")]
public ActionResult DeleteConfirm(int id)
{
var foo = db.Foos.Find(id);
if (foo == null)
{
return new HttpNotFoundResult();
}
db.Foos.Remove(foo);
db.SaveChanges();
return RedirectToAction("Index");
}
Then, for your GET action, you would add a Delete.cshtml file:
#model Namespace.To.Foo
<p>Are you sure you want to delete the foo, "#Model.Name"?</p>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
#Html.HiddenFor(m => m.Id)
#Html.ActionLink("Cancel", "Index")
<button type="submit">Delete</button>
}
Alternatively (or rather progressively, as you should still have the previous method as a fallback), you could use a JavaScript confirm and AJAX to do this, if you don't want to change pages:
#Html.ActionLink("Radera", "DeleteTraffic", new { id = item.Id }, new { #class = "btn btn-link NoBorder NoBackGround delete", data_id = item.Id })
Then:
<script>
$('.delete').on('click', function () {
var $deleteLink = $(this);
if (confirm('Are you sure?')) {
$.post('/url/for/delete/', { id = $deleteLink.data('id') }, function () {
$deleteLink.closest('tr').remove();
});
}
});
</script>

Writing a Content Part that contains a list of content parts

I've been trying to write a simple accordian widget, where each section of accordian would be its own content part. I have the leafs content part created fine, but I want to create the accordian part which contains a list of the leafs. I havn't been able to find a good tutorial that went over something like this. I'm working on displaying the leafs now, and am running into issues. I'm trying to mimic the comments module. This is what I have. It seems like i am able to get up to the list of leafs in the driver, but i'm not sure what to do with the view. i see comments calls #Display(Model.List) but i have no idea what this is doing.
Edit View (Just using a textbox for one leaf id, need to figure out how to select leaves):
<fieldset>
<legend>Accordian Fields</legend>
<div class="editor-label">
#Html.LabelFor(model => model.Title)
</div>
<div class="editor-field">
#Html.TextBoxFor(model => model.Title)
#Html.ValidationMessageFor(model => model.Title)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.AccordianLeaf)
</div>
<div class="editor-field">
#*#Html.DropDownListFor(m => m.AccordianLeaf,
new System.Web.Mvc.SelectList(service.GetComments(), "Value", "Text"))*#
#Html.TextBoxFor(model => model.AccordianLeaf)
#Html.ValidationMessageFor(model => model.AccordianLeaf)
</div>
</fieldset>
Model:
namespace SuccessCenter.Models
{
public class AccordianRecord : ContentPartRecord
{
public virtual string Title { get; set; }
public virtual int AccordianLeaf { get; set; }
}
public class AccordianPart : ContentPart<AccordianRecord>
{
[Required]
public string Title
{
get { return Retrieve(r => r.Title); }
set { Store(r => r.Title, value); }
}
[Required]
public int AccordianLeaf
{
get { return Retrieve(r => r.AccordianLeaf); }
set { Store(r => r.AccordianLeaf, value); }
}
}
}
Handler:
namespace SuccessCenter.Handlers
{
public class AccordianHandler : ContentHandler
{
public AccordianHandler(IRepository<AccordianRecord> repository)
{
Filters.Add(StorageFilter.For(repository));
}
}
}
Driver:
namespace SuccessCenter.Drivers
{
[UsedImplicitly]
public class AccordianDriver : ContentPartDriver<AccordianPart>
{
private readonly IAccordian _accordian;
public AccordianDriver(IAccordian accordian)
{
_accordian = accordian;
}
protected override DriverResult Display(AccordianPart part, string displayType, dynamic shapeHelper)
{
//return ContentShape("Parts_Accordian", () => shapeHelper.Parts_Accordian(Title: part.Title, AccordianLeaf: part.AccordianLeaf));
return Combined(
ContentShape("Parts_Accordian",
() =>
{
// create a hierarchy of shapes
var firstLevelShapes = new List<dynamic>();
var allShapes = new Dictionary<int, dynamic>();
var AccordianLeafs = _accordian.AccordianLeafs.ToList();
foreach (var item in AccordianLeafs)
{
var shape = shapeHelper.AccordianLeaf(ContentPart: item, ContentItem: item.ContentItem);
allShapes.Add(item.Id, shape);
}
var list = shapeHelper.List(Items: allShapes);
return shapeHelper.Parts_Accordian(
List: list
);
}));
}
//GET
protected override DriverResult Editor(AccordianPart part, dynamic shapeHelper)
{
return ContentShape("Parts_Accordian_Edit", () => shapeHelper.EditorTemplate(TemplateName: "Parts/Accordian", Model: part, Prefix: Prefix));
}
//POST
protected override DriverResult Editor(AccordianPart part, IUpdateModel updater, dynamic shapeHelper)
{
updater.TryUpdateModel(part, Prefix, null, null);
return Editor(part, shapeHelper);
}
}
}
View:
#using SuccessCenter.Models;
}<div class="expand-view expanded">
<header class="bg-brand-blue txt-white relative">
<h3 class="txt-left">#Model.List.Title</h3>
<span class="toggle v-align absolute">
<span class="expanded">Colllapse <i class="icons icon-carat-up-wh"></i></span><span class="collapsed">Expand <i class="icons icon-carat-down-wh"></i></span>
</span>
</header>
<section class="default-padding">
#Model.List.AccordianLeaf
</section>
##Display(Model.List)#
The List shape (shapeHelper.List()) takes a range of content item shapes, which you seem to build with shapeHelper.AccordionLeaf(). Therefore you can just display it with the Display method:
#Display(Model.List)
This method will display the List property on your model (your model properties are the ones you give as parameter in shapeHelper.Parts_Accordian(/* model properties */)
I am not sure what you are trying to do in the view, it seems like your want to iterate over the items in the List shape? In that case you can do something like this:
#foreach (var item in Model.List.Items) {
// item here is an AccordionLeaf shape
#Display(item)
}

Resources