For some reason, I cannot seem to get menu binding with sitemap to work... Can you please help me?
Here's my Web.sitemap:
| <?xml version="1.0" encoding="utf-8" ?> |
| <siteMap> |
| <siteMapNode title="Home" controller="Home" action=""> |
| </siteMapNode> |
| </siteMap> |
And, in my Site.master, i have:
| <% |
| Html.Telerik().Menu() |
| .Name("Home") |
| .BindTo("Web") |
| .Render(); |
| %> |
in my HomeController I have:
| [PopulateSiteMap(SiteMapName = "Web", ViewDataKey = "Web")] |
| public ActionResult SiteMapBinding() |
| { |
| if (!SiteMapManager.SiteMaps.ContainsKey("Web")) |
| { |
| SiteMapManager.SiteMaps.Register<XmlSiteMap>("Web", sitmap => sitmap.LoadFrom("~/Web.sitemap")); |
| } |
| return View(); |
| } |
And it stills gives me this error:
You must have SiteMap defined with key "Web" in ViewData dictionary.
What am I doing wrong?
Thank you
18 Answers, 1 is accepted
I was not able to reproduce the depicted issue. In my attempt to replicate the problem, I have created a test project and bind menu to sitemap.
I have attach it to this message.
Please review it and let me know if I am missing something.
Sincerely yours,
Georgi Krustev
the Telerik team
Instantly find answers to your questions on the new Telerik Support Portal.
Watch a video on how to optimize your support resource searches and check out more tips on the blogs.
Could you please review the test project attached to my previous message?
Let me know what the differences are.
All the best,
Georgi Krustev
the Telerik team
Instantly find answers to your questions on the new Telerik Support Portal.
Watch a video on how to optimize your support resource searches and check out more tips on the blogs.
To aid debugging this issue I removed the [PopulateSiteMap] attribute from the controller action and just added the SiteBase to ViewData within the action:
| public ActionResult Index() |
| { |
| SiteMapManager.SiteMaps.Register<XmlSiteMap>("Web", sitemap => sitemap.LoadFrom("~/user.sitemap")); |
| var siteMap = SiteMapManager.SiteMaps["Web"]; |
| ViewData["Web"] = siteMap; |
| return View(); |
| } |
In my Index.ascx file I set a breakpoint in the inline server code that renders the menu and via the VS 2008 debugger I can see that ViewData has a key called Web with a value set to the relevant SiteBase
| <%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %> |
| <% |
| Html.Telerik().Menu() |
| .Name("Home") |
| .BindTo("Web") |
| .Render(); |
| %> |
An exception is thrown
| NotSupportedException: You must have SiteMap defined with key "Web" in ViewData dictionary.] |
| Telerik.Web.Mvc.UI.NavigationItemContainerExtensions.BindTo(INavigationItemContainer`1 component, String sitemapViewDataKey, ViewContext viewContext, Action`2 siteMapAction) +403 |
| Telerik.Web.Mvc.UI.MenuBuilder.BindTo(String viewDataKey) +45 |
| ASP.views_mainmenu_index_ascx.__Render__control1(HtmlTextWriter __w, Control parameterContainer) in m:\users\david\Visual Studio 2008\Projects\TimeManagementSystem\FuelCardOptimiser\Views\MainMenu\Index.ascx:4 |
| System.Web.UI.Control.RenderChildrenInternal(HtmlTextWriter writer, ICollection children) +256 |
| System.Web.UI.Control.RenderChildren(HtmlTextWriter writer) +19 |
| System.Web.UI.Control.Render(HtmlTextWriter writer) +10 |
| System.Web.UI.Control.RenderControlInternal(HtmlTextWriter writer, ControlAdapter adapter) +27 |
| System.Web.UI.Control.RenderControl(HtmlTextWriter writer, ControlAdapter adapter) +99 |
| System.Web.UI.Control.RenderControl(HtmlTextWriter writer) +25 |
| System.Web.UI.Control.RenderChildrenInternal(HtmlTextWriter writer, ICollection children) +134 |
| System.Web.UI.Control.RenderChildren(HtmlTextWriter writer) +19 |
| System.Web.UI.Page.Render(HtmlTextWriter writer) +29 |
| System.Web.Mvc.ViewPage.Render(HtmlTextWriter writer) +59 |
| System.Web.UI.Control.RenderControlInternal(HtmlTextWriter writer, ControlAdapter adapter) +27 |
| System.Web.UI.Control.RenderControl(HtmlTextWriter writer, ControlAdapter adapter) +99 |
| System.Web.UI.Control.RenderControl(HtmlTextWriter writer) +25 |
| System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint) +1266 |
Regards
David
In a first look the code snippets that you provide look correct. Unfortunately it is hard to understand where could be the problem without a running test project which reproduces the depicted issue.
Could you please review my test project attached to one of my previous messages? Let me know if there are some differences in the implementation or in the structure of the sitemap.
Regards,
Georgi Krustev
the Telerik team
Do you want to have your say when we set our development plans? Do you want to know when a feature you care about is added or when a bug fixed? Explore the Telerik Public Issue Tracking system and vote to affect the priority of the items.
Thank you for your feedback.
I will forward this thread to our developers for further discussion. If we are able to find better solution, we definitely will implement it.
Kind regards,
Georgi Krustev
the Telerik team
Do you want to have your say when we set our development plans? Do you want to know when a feature you care about is added or when a bug fixed? Explore the Telerik Public Issue Tracking system and vote to affect the priority of the items.
Tried to make a base controller class and then inherit the class on every other controller.
This works well for setting ViewData[]. This didn't work for the Telerik MVC menu.
NullReference Exception was unhandled by user code. (Object reference not set to an instance of an object.)
The Telerik menu for Webforms was so great and easy to use! It's too bad that you couldn't use the same xml files as a datasource in MVC!
I suppose that the exception is thrown by the "GetAuthorizeAttributes" method. This is part of the validation process, where the BindTo method checks whether the provided Controller and Action names are correct.
Please verify that your sitemap is correctly constructed.
Nevertheless we implement additional feature to our navigation components - rather than apply PopulateSitemapAttribute to every controller, you just can register the sitemap in the Global.asax with SiteMapManager (as we do in our examples) and then give the name of the sitemap to the BindTo method.
This feature will be included in next release of Telerik Components for ASP.NET MVC.
Currently the work around of the need to add attribute to every controller is to make base controller and all others to inherit it. I have attached test project which shows what I mean.
Sincerely yours,
Georgi Krustev
the Telerik team
Do you want to have your say when we set our development plans? Do you want to know when a feature you care about is added or when a bug fixed? Explore the Telerik Public Issue Tracking system and vote to affect the priority of the items.
Will definitely wait for the next release of Telerik Components for MVC. That is a huge feature.
I'm hoping that I can define mulitple sitemaps in Global.asax. Then based on a session variable chose the correct name of the sitemap in the BindTo method.
Will you be releasing the next Telerik Components for MVC with Visual Studio 2010?
Thanks,
Mike
Use SiteMapManager to register multiple sitemaps with unique names. With the new release of Telerik Components for ASP.NET MVC you can use this unique names to bind navigation controls without applying PopulateSiteMap attribute.
Currently I am not able to define concrete date for our release. Nevertheless the aforementioned improvement will be included in the next minor release.
Kind regards,
Georgi Krustev
the Telerik team
Do you want to have your say when we set our development plans? Do you want to know when a feature you care about is added or when a bug fixed? Explore the Telerik Public Issue Tracking system and vote to affect the priority of the items.
Any updates on the internal build release date?
Thanks,
Mike
We are going to release service pack of the Telerik Components for ASP.NET MVC in the begging of the next week (19.04.2010). As you are customer you will be able to download it from your account.
Greetings,
Georgi Krustev
the Telerik team
Do you want to have your say when we set our development plans? Do you want to know when a feature you care about is added or when a bug fixed? Explore the Telerik Public Issue Tracking system and vote to affect the priority of the items.
First, decorate the entire controller class with the PopulateSiteMap Attribute.
Then, inside your controller class, Call a public instance of itself, and place your Registration code in there...
Example:
| namespace MVCSite.Controllers |
| { |
| [PopulateSiteMap(SiteMapName = "sample", ViewDataKey = "sample")] |
| public class HomeController : Controller |
| { |
| public HomeController() |
| { |
| if (!SiteMapManager.SiteMaps.ContainsKey("sample")) |
| { |
| SiteMapManager.SiteMaps.Register<XmlSiteMap>("sample", sitemap => sitemap.LoadFrom("~/sample.sitemap")); |
| } |
| } //Your Controller's Actions Go Here } } |
Another method, instead of doing this for each Controller, would be to create a partial Controller class and derive all of your other controllers from that.
Personally, for performance, I prefer having to add this to each Controller that calls it, as I'm not using this functionality on every view, just multiple views within the same Area.
This is what I was attempting to do also. Just curious why inheriting the base controller class would be less efficient? Wouldn't the code be compiled.
What I would really like to do is generate the menu from a model and then cache the model after the user logs in or when a change is needed.
This way the menu system could be controlled completely from the database and have the performance similar to storing a sitemap. Would this even be possible?
Mike
Granted, this is incredibly efficient if you need the Sitemap on every page, but for the application I'm using it on curently I do not, and it varies in different areas of the site.
So, rather than consistently keeping data I don't need in memory, I've opted to call it only on those controllers that I feel actually need it. Now I could do this by using the base class method, as well, but for me, having the attribute decorated on each Controller I use it in makes it a bit more visible for me in the code, and easier for me to track down those items, rather than looking to see whether it inherits from Controller or from "CustomController".
While you certainly could put together a custom SiteMap Provider that is database generated and geared towards asp.NET MVC (I'd check the Asp.NET Provider Samples and modify the SQLSiteMapProvider to taste for MVC and the Telerik controls), caching it based on the user profile is whoilly unnecessary, as now you have yet another complex type that is stored in session memory for each and every logged in user.
I'd highly recommend utilizing the RolesProvider, whichever flavor of it you prefer (I use a custom one built off of LINQ for my current project), and decorating your actions to restrict access as necessary. The Telerik SiteMapManager already uses some initelligent caching, near as I could tell, AND will defer to any restrictions you place. The SiteMap Binding of each of the Telerik MVC Controls fully supports Roles and User Authorization based upon the Authorize Attribute (or any derivative thereof) of that Action.
For example, if we build upon the previous code sample:
Controller:
| namespace MVCSite.Controllers |
| { |
| [PopulateSiteMap(SiteMapName = "sample", ViewDataKey = "sample")] |
| public class HomeController : Controller |
| { |
| public HomeController() |
| { |
| if (!SiteMapManager.SiteMaps.ContainsKey("sample")) |
| { |
| SiteMapManager.SiteMaps.Register<XmlSiteMap>("sample", sitemap => sitemap.LoadFrom("~/sample.sitemap")); |
| } |
| } |
| public ActionResult Index() |
| { |
| //perform actions |
| return View(); |
| } |
| public ActionResult Create() |
| { |
| //perform actions |
| return View(); |
| } |
| public ActionResult Update() |
| { |
| //perform actions |
| return View(); |
| } |
| public ActionResult Delete() |
| { |
| //perform actions |
| return View(); |
| } |
| } |
| } |
We now have 4 Actions on this controller, so the "sample" SiteMap file may look like this:
| <?xml version="1.0" encoding="utf-8" ?> |
| <siteMap xmlns="http://schemas.microsoft.com/AspNet/SiteMap-File-1.0" > |
| <siteMapNode title="Home" controller="Home" action="Index" > |
| <siteMapNode title="Home"> |
| <siteMapNode title="Homepage Title" controller="Home" action="Index" /> |
| <siteMapNode title="Create Title" controller="Home" action="Create" /> |
| <siteMapNode title="Delete Title" controller="Home" action="Delete" /> |
| <siteMapNode title="Update Title" controller="Home" action="Update" /> |
| </siteMapNode> |
| </siteMapNode> |
| </siteMap> |
So we have our simple SiteMap which relates to those entries... We'd see a menu with all of those items ("Homepage Title", "Create Title","Delete Title","Update Title") appeared as sub items of the "Home" Menu item.
Now, if we decorate the Delete ActionResult as follows:
| [Authorize(Roles="Administrator")] |
| public ActionResult Delete() |
| { |
| //perform actions |
| return View(); |
| } |
And then we login as a User who is NOT in the "Administrator" Role, the menu with show only the other 3 links, but not "Delete Title".
So, no need to cache additional data, just decorate your Actions with the Authorize Attribute, and you're golden! :)
Object reference not set to an instance of an object
orYou must have SiteMap defined with key "*" in ViewData dictionary.
)Even I copy pasted the telerik mvc demo details into my application in order to have same kind of info that the app uses but with no success... I dont know what to do....
Additionally, why in the example the app uses telerik.web.mvc.products and telerik.web.mvc.products.examples?. These two details were only defined in populateproductsitemapattribute and in navigation, but nothing more...?.
any idea will be appreciated.