I'm trying to make a menu with asp .net mvc 4.
Calling menu
#{Html.RenderAction("LeftMenu", "Navigation",
new { currentPoint = ViewData["CurrentCategory"] });}
Navigation controller:
public ViewResult LeftMenu(string currentPoint) {
List<NavLink> navLinks = new List<NavLink>();
navLinks.Add(new CategoryLink(null)
{
IsSelected = (currentPoint == null)
});
List<string> categories = new List<string>{
"Bicycles",
"Details"
};
foreach (var category in categories)
navLinks.Add(new CategoryLink(category){
IsSelected = (category == currentPoint)
});
return View(navLinks);
}
Partial View
#model IEnumerable<MvcWebShopApp.Controllers.NavigationController.NavLink>
<ul class="nav navbar-nav">
#foreach (var link in Model)
{
<li class = "#(link.IsSelected ? "active": "")">
<a href="#Url.RouteUrl(link.RouteValues)")>#link.Text</a>
</li>
}
</ul>
But when I run my project I got Exception:
Insufficient stack to continue executing the program safely. This can
happen from having too many functions on the call stack or function on
the stack using too much stack space.
Please help.
You're actually rendering a full view, not a partial view.
That includes the layout, which renders that view again, creating a stack overflow.
You need to return PartialView().
Related
Blazor's NavLink component detects whether the link refers to the current page, and sets the active class.
It is customary to also set the aria-current="page" attribute, when it is part of a menu.
Can the component do that somehow? Or could I wrap it in a custom component that does this?
I can't find an extension point that easily allows for this: docs, source.
Extending the existing NavLink
public class NavLinkExtended : NavLink
{
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
builder.OpenElement(0, "a");
builder.AddMultipleAttributes(1, AdditionalAttributes);
builder.AddAttribute(2, "class", CssClass);
if(CssClass.Contains("active"))
{
builder.AddAttribute(3, "aria-current", "page");
builder.AddContent(4, ChildContent);
builder.CloseElement();
}
else
{
builder.AddContent(3, ChildContent);
builder.CloseElement();
}
}
}
Here is my solution.
AriaNavLink.razor:
<NavLink class=#CssClass #attributes=Attributes>
#ChildContent
</NavLink>
#inherits NavLink
#code {
private const string _defaultActiveClass = "active";
private Dictionary<string, object>? _attributes;
private IReadOnlyDictionary<string, object> Attributes
{
get
{
if (_attributes != null) return _attributes;
_attributes = AdditionalAttributes != null
? new Dictionary<string, object>(AdditionalAttributes)
: new Dictionary<string, object>();
// the trick to inferring "active" indirectly:
var isActive = CssClass?.Split(' ', StringSplitOptions.TrimEntries).Contains(ActiveClass ?? _defaultActiveClass) ?? false;
if (isActive)
_attributes.Add("aria-current", "page");
return _attributes;
}
}
}
Some page using bootstrap:
<li class="nav-item">
<AriaNavLink href="/foo" class="nav-link" foo="bar">
<i class="some-icon"></i>
Foo
</AriaNavLink>
</li>
Pardon me for not being exact, but... I would approach this with something like...
aria-current="#(route == nav route) ? "page" : "" ")
and then figure out how to detect the route of the current page and the configured nav route for the thing you're modifying.
Then, if you have a LOT of these nav things and you don't want to duplicate the logic all over the place.... write a method that does the above and add that to your code like...
The accepted answer is a good solution for now. When v8 is released (probably November 2023), the functionality will be built-in. The merged PR is here.
I am using the MVC Widget Template to read a list of Dynamic Content, but need to be able to query the list. Because it has pagination, it only goes through the first 20 or so. How do I turn this off?
I Tried using this but didnt work:
<telerik:RadListView ID="KBList" ItemPlaceholderID="ItemsContainer" runat="server"
EnableEmbeddedSkins="False" EnableEmbeddedBaseStylesheet="False"
AllowPaging="False">
My Code Here:
#model Telerik.Sitefinity.Frontend.DynamicContent.Mvc.Models.DynamicContentListViewModel
#using Telerik.Sitefinity.Frontend.DynamicContent.WidgetTemplates.Fields.Helpers;
#using Telerik.Sitefinity;
#using Telerik.Sitefinity.Data.ContentLinks;
#using Telerik.Sitefinity.Frontend.Mvc.Helpers;
#using Telerik.Sitefinity.Frontend.Mvc.Models;
#using Telerik.Sitefinity.Web.DataResolving;
#using Telerik.Sitefinity.Model.ContentLinks;
#using Telerik.Sitefinity.Modules.Pages;
#Html.Script(ScriptRef.JQuery, "top", false)
<div class="#Model.CssClass">
<ul>
#foreach (var item in Model.Items)
{
var navigateUrl = HyperLinkHelpers.GetDetailPageUrl(item, ViewBag.DetailsPageId, ViewBag.OpenInSamePage, Model.UrlKeyPrefix);
<li #Html.InlineEditingAttributes(Model.ProviderName, Model.ContentType.FullName, (Guid)item.Fields.Id)>
<h3>
<a #Html.InlineEditingFieldAttributes("Title", "ShortText") href="#navigateUrl">
#item.Fields.Title
</a>
</h3>
</li>
}
</ul>
#if (Model.ShowPager)
{
#Html.Action("Index", "ContentPager", new { currentPage = Model.CurrentPage,
totalPagesCount = Model.TotalPagesCount.Value,
redirectUrlTemplate = ViewBag.RedirectPageUrlTemplate })
}
</div>
You need to edit the widget in page edit mode and there find the List Settings tab and once there you select the No Limit and Paging option.
This way, all items will be sent to the view.
Try writing your own MVC widget. In the Index method of your controller try fetching all content using resolveType of the dynamic module and (if needed convertinto required model) and pass it to your view file.
public ActionResult Index()
{
DynamicModuleManager dynamicModuleManager = DynamicModuleManager.GetManager(String.Empty,"SomeName");
Type dynArticleType = TypeResolutionService.ResolveType(resolveType);
var FilteredCollection = dynamicModuleManager.GetDataItems(dynArticleType)
.Where(x => x.Status == ContentLifecycleStatus.Live && x.Status != ContentLifecycleStatus.Deleted
&& x.Status != ContentLifecycleStatus.Temp && x.Status != ContentLifecycleStatus.Master)
.OrderByDescending(x => x.GetValue<System.DateTime>("PublicationDate"));
var fullList = FilteredCollection.ToList();
//Convert into customModel.
return View("Default", yourCustomModel);
}
I'm using cloudscribe navigation to build the sidebar and associated breadcrumb menu
I couldn't find it in the help documentation, so posting here
we have some route parameters in our routing templates - I'm unsure how to preserve those in breadcrumbs, navigation parameter preservedRouterParameters I understand that only works with Query string params
our route is something like this:
{wholesaleCustomerCode}/{area:exists}/{baseItemId:guid}/{controller=Home}/{action=Index}/{id:guid?}
and would translate to something like :
http://127.0.0.1:5100/TOM/Service/09185d87-5e3f-4217-a0b9-02f766efc714/Home/Detail
now when I'm on the /Detail view - it's nested quite deep - but the previous parent in breadcrumb loses baseItemId value - any suggestions how I can preserve route parameters in the breadcrumb hierarchy ?
I have attempted the following, just to test if it would pick up preserve route parameters, but didn't work
<Children>
<NavNode
key="d902daaa-99ec-488a-85de-c03641fb547d"
area="Service"
controller="Home"
action="Detail"
text="Service"
iconCssClass=""
componentVisibility="breadcrumbs"
viewRoles="Administrator"
preservedRouteParameters="baseItemId"
>
<Children>
<NavNode
key="3047c57d-0f8c-4875-aff9-9c1f91909e41"
area="Service"
controller="RecurringCredit"
action="Edit"
text="Recurring Credit"
iconCssClass=""
componentVisibility="breadcrumbs"
viewRoles="Administrator"
preservedRouteParameters="baseItemId"
>
</NavNode></Children></NavNode>
It is true that preservedRouteParameters only works for query string parameters.
One possible solution is to use code from within the controller action for your detail view to update the url for the parent breadcrumbs as needed. Relevant documentation here:https://www.cloudscribe.com/docs/adjusting-menu-items-per-request
var crumbAdjuster = new NavigationNodeAdjuster(Request.HttpContext);
crumbAdjuster.KeyToAdjust = "d902daaa-99ec-488a-85de-c03641fb547d";
crumbAdjuster.AdjustedUrl = // set the url here as you want it to be;
crumbAdjuster.AddToContext();
that will update the url for the given menu node for the life of the current request
Here's how I solved the issue
Consider you have the following default BootstrapBreadcrumbs view from cloudscribe.Web.Navigation/Views:
#using cloudscribe.Web.Navigation
#model NavigationViewModel
#if (Model.CurrentNode != null && (Model.ParentChain.Count > 1 || (Model.TailCrumbs != null && Model.TailCrumbs.Count > 0)))
{
<ul class="breadcrumb">
#foreach (var node in Model.ParentChain)
{
if (!Model.ShouldAllowView(node)) { continue; }
if (node.EqualsNode(Model.CurrentNode))
{
if (Model.TailCrumbs != null)
{
<li>#Model.AdjustText(node)<span class="divider"></span></li>
}
else
{
<li class="active">#Model.AdjustText(node)</li>
}
}
else
{
<li>#Model.AdjustText(node)<span class="divider"></span></li>
}
}
#if (Model.TailCrumbs != null)
{
foreach (var n in Model.TailCrumbs)
{
<li class="active">#n.Text</li>
}
}
</ul>
}
instead of using Model.AdjustUrl bypass it in favour of using asp-route-* helper
#using cloudscribe.Web.Navigation
#model NavigationViewModel
#{
var id = ViewContext.RouteData.Values["id"];
var baseItemId = ViewContext.RouteData.Values["baseItemId"];
var orderId = ViewContext.HttpContext.Request.Query["orderId"];
}
#if (Model.CurrentNode != null && (Model.ParentChain.Count > 1 || (Model.TailCrumbs != null && Model.TailCrumbs.Count > 0)))
{
<ul class="breadcrumb">
#foreach (var node in Model.ParentChain)
{
if (!Model.ShouldAllowView(node)) { continue; }
if (node.EqualsNode(Model.CurrentNode))
{
if (Model.TailCrumbs != null)
{
<li><a asp-area="#node.Value.Area" asp-controller="#node.Value.Controller" asp-action="#node.Value.Action" asp-route-baseItemId="#baseItemId" asp-route-orderId="#orderId" asp-route-id="#id">
#Model.AdjustText(node)
</a>
<span class="divider"></span>
</li>
}
else
{
<li class="active">#Model.AdjustText(node)</li>
}
}
else
{
<li><a asp-area="#node.Value.Area" asp-controller="#node.Value.Controller" asp-action="#node.Value.Action" asp-route-baseItemId="#baseItemId" asp-route-orderId="#orderId" asp-route-id="#id">#Model.AdjustText(node)</a><span class="divider"></span></li>
}
}
#if (Model.TailCrumbs != null)
{
foreach (var n in Model.TailCrumbs)
{
<li class="active">#n.Text</li>
}
}
</ul>
}
this implementation has some drawbacks and may not work for everyone - for example, all the previous breadcrumbs hierarchy now have baseItemId in the route -
e.g.
dashboard / all notes / note / detail
here , the all notes will also have the baseItemId added to it, although it doesn't need it -
but it works for us as it's an administrative portal (no SEO requirements) and the way our route templates are mapped
Might be a asked and answered...sorry but, I've been searching for a while.
RAZOR VIEW
#foreach (var item in Model.LanguageList)
{
<li>
<a href="#Url.Action("ChangeLanguage", "UserHeader")" data_languageID="#item.LanguageID" data_someval="hello" data_somevalb="world">
<div class="flag #item.LanguageFlag">
</div>
<div class="flag-title"> #item.LanguageName</div>
</a>
</li>
}
How do I access the data attributes in my controller?
CONTROLLER
public ActionResult ChangeLanguage()
{
var x = ControllerContext; //// ??? get the collection of [data-xx] from where?
return RedirectToAction("Buttons", "Designer");
}
data-* attributes are client side values and are not sent in the request.
In order to send those values, add them as route values
#foreach (var item in Model.LanguageList)
{
<li>
<a href="#Url.Action("ChangeLanguage", "UserHeader", new { languageID=item.LanguageID, someval="hello", somevalb="world" })">
<div class="flag #item.LanguageFlag"></div>
<div class="flag-title"> #item.LanguageName</div>
</a>
</li>
}
and include parameters in your GET method for the values
public ActionResult ChangeLanguage(int LanguageID, string someval, string somevalb)
Alternatively you could handle this using javascript/jquery (the ChangeLanguage() method also needs to be modified as shown above)
$('a').click(function() {
// get the url
var url = $(this).attr('href');
// get the data attributes
var languageID = $(this).data('languageID');
var someVal= $(this).data('someval');
var someValB= $(this).data('somevalb');
location.href = url + '?languageID=' + languageID + '&someVal =' + someVal + '&someValB=' + someValB;
return false; // cancel the default redirect
});
I have been using ASP.NET MVC 4 for a while but I have not yet come across a situation where i need to insert a value into the database from a scaffolded Create view which is based on a value passed from another view. I have tried to infer from the Edit view to try and modify my code to work but I have run into a snag. I got an error similar to this post. Here is my code from the view passing the value
#Html.ActionLink("Allocate", "Create", "Allocation", new { id=item.requestID}, null)
this is from the list of requests already in the database from the Index view
here is my code on the controller that is trying to force the Create method to use the ID passed from the link above
public ActionResult Create(int id = 0)
{
Request request = db.Requests.Find(id);
ViewBag.requestID = new SelectList(db.Requests, "requestID", "requestID", request.requestID);
return View(request);
}
then here is the posting code to the db
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(Allocation allocation)
{
if (ModelState.IsValid)
{
db.Allocations.Add(allocation);
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.requestID = new SelectList(db.Requests, "requestID", "requestID", allocation.requestID);
return View(allocation);
}
Basically what I am trying to do is allocate funds to a request made where by the allocation is entered into the db based on the request id. I am trying to prevent the user from choosing the request id from a drop down list. When I run this i get an error
The model item passed into the dictionary is of type 'System.Data.Entity.DynamicProxies.Request_A52006F7570E0448EE323CB36858E4D13EED0BAD958340B32FF166708545DA8C', but this dictionary requires a model item of type 'BudgetAllocation.Models.Allocation'.
If theres anyone out there who can help me out with this please do as soon as you can. I appreciate all the effort offred!!!!!
//EDIT
Here is my Create view
#model BudgetAllocation.Models.Allocation
#{
ViewBag.Title = "Create";
}
<h2>Create</h2>
#using (Html.BeginForm()) {
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<fieldset>
<legend>Allocation</legend>
#Html.HiddenFor(model => model.requestID)
<div class="editor-label">
#Html.LabelFor(model => model.allocAmount, "Amount")
</div>
<div class="editor-field">
#Html.EditorFor(model => model.allocAmount)
#Html.ValidationMessageFor(model => model.allocAmount)
</div>
<p>
<input type="submit" value="Allocate" />
</p>
</fieldset>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
The problem is your view id strongly typed with BudgetAllocation.Models.Allocation while in get action of Create you are passing object of type BudgetAllocation.Models.Request thats why you are getting the exception.
You have to pass object of type BudgetAllocation.Models.Allocation in Create get action as well because you view is strongly typed to it.
public ActionResult Create(int id = 0)
{
Request request = db.Requests.Find(id);
return View(request) // <-------------- here is the mistake
}
it should return allocation object, something like this, it is just an example may be you need to do some other thing instead of selecting:
public ActionResult Create(int id = 0)
{
Allocation allocation = db.Allocations.Find(x=>x.requestID == id);
ViewBag.requestID = new SelectList(db.Requests, "requestID", "requestID", request.requestID);
return View(allocation);
}
UPDATE:
you simply need to do like this not return allocaiton object return simply View:
public ActionResult Create(int id = 0)
{
ViewBag.requestID = new SelectList(db.Requests, "requestID", "requestID", request.requestID);
return View();
}