Upgrading to V4 has caused #Html.MvcSiteMap().SiteMapPath() to not return anything - mvcsitemapprovider

Before I revert to the earlier package, does anybody know why this can be? I followed this guide pretty much to the letter.
https://github.com/maartenba/MvcSiteMapProvider/wiki/Upgrading-from-v3-to-v4
Cheers,
J
mvc.sitemap
<?xml version="1.0" encoding="utf-8" ?>
<mvcSiteMapNode title="Home" controller="Home" action="Index">
<mvcSiteMapNode title="Opportunity Stream" controller="TaskStream" action="Index"/>
<mvcSiteMapNode title="Opportunity Stream" controller="TaskStream" action="IndexNew"/>
<mvcSiteMapNode title="Appointment" controller="Appointment" action="Index"/>
<mvcSiteMapNode title="Vehicle Search" controller="VehicleSearch" action="Index"/>
<mvcSiteMapNode title="Stock" controller="Stock" action="Index"/>
<mvcSiteMapNode title="Admin" controller="Admin" action="Index">
<mvcSiteMapNode title="Team Management" controller="Admin" action="TeamManagement">
<mvcSiteMapNode title="Manage Team Member" controller="Admin" action="TeamManagementDetails"/>
</mvcSiteMapNode>
<mvcSiteMapNode title="Site Management" controller="Site" action="Index">
<mvcSiteMapNode title="Site" controller="Site" action="SiteOptions" preservedRouteParameters="id">
<mvcSiteMapNode title="Default Calendar" controller="Site" action="DefaultCalendar"/>
<mvcSiteMapNode title="Exception Calendar" controller="Site" action="ExceptionCalendar"/>
<mvcSiteMapNode title="Manage Site" controller="Site" action="Details"/>
<mvcSiteMapNode title="Manage Site" controller="Site" action="Edit"/>
</mvcSiteMapNode>
<mvcSiteMapNode title="Create Site" controller="Site" action="Create"/>
</mvcSiteMapNode>
<mvcSiteMapNode title="Approve Leave Requests" controller="LeaveRequest" action="Index"/>
</mvcSiteMapNode>
<mvcSiteMapNode title="Auction" controller="Auction" action="Index"/>
<mvcSiteMapNode title="Employee" controller="Employee" action="Index">
<mvcSiteMapNode title="Calendar Exceptions" controller="Site" action="TeamExceptions"/>
<mvcSiteMapNode title="Employee Detail" controller="Employee" action="Detail" clickable="false"/>
<mvcSiteMapNode title="Employee Detail" controller="Employee" action="Edit" clickable="false"/>
</mvcSiteMapNode>
<mvcSiteMapNode title="User Profile" controller="UserProfile" action="Index">
<mvcSiteMapNode title="My Holidays" controller="UserProfile" action="MyHolidays"/>
<mvcSiteMapNode title="Create Leave Request" controller="LeaveRequest" action="Create"/>
</mvcSiteMapNode>
</mvcSiteMapNode>
RouteConfig
public static void RegisterRoutes(RouteCollection routes)
{
XmlSiteMapController.RegisterRoutes(RouteTable.Routes);
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.IgnoreRoute("{*alljs}", new
{
alljs = #".*\.js(/.*)?"
});
routes.IgnoreRoute("{*allpng}", new
{
allpng = #".*\.png(/.*)?"
});
routes.IgnoreRoute("{*allcss}", new
{
allcss = #".*\.css(/.*)?"
});
routes.IgnoreRoute("{*allgif}", new
{
allgif = #".*\.gif(/.*)?"
});
routes.IgnoreRoute("{*alljpg}", new
{
alljpg = #".*\.jpg(/.*)?"
});
routes.MapRoute("Default", "{controller}/{action}/{id}",
new
{
country = "uk",
lang = "En",
controller = "Home",
action = "Index",
id = UrlParameter.Optional
});
routes.MapRoute("localizedDefault", "{country}/{lang}/{controller}/{action}/{id}",
new
{
country = "uk",
lang = "En",
controller = "Home",
action = "Index",
id = UrlParameter.Optional
});
}

The most likely cause is that you haven't accounted for the country or lang route parameters in your configuration. And since these are ambient values that have nothing to do with page identification, you could use preservedRouteParamters to force them to always match.
<mvcSiteMapNode title="Home" controller="Home" action="Index" preservedRouteParameters="country,lang">
Currently there is no way to specify preservedRouteParameters globally, so you will need to supply this attribute on every node.
However, be aware that your localized pages won't appear in the XML sitemap endpoint at /sitemap.xml when using preservedRouteParameters.
To fix that, you should fix your route configuration so they will instead appear at {country}/{lang}/sitemap.xml. Add the 2 routes to your configuration to create localized sitemap.xml endpoints, like this:
public static void RegisterRoutes(RouteCollection routes)
{
XmlSiteMapController.RegisterRoutes(RouteTable.Routes);
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.IgnoreRoute("{*alljs}", new
{
alljs = #".*\.js(/.*)?"
});
routes.IgnoreRoute("{*allpng}", new
{
allpng = #".*\.png(/.*)?"
});
routes.IgnoreRoute("{*allcss}", new
{
allcss = #".*\.css(/.*)?"
});
routes.IgnoreRoute("{*allgif}", new
{
allgif = #".*\.gif(/.*)?"
});
routes.IgnoreRoute("{*alljpg}", new
{
alljpg = #".*\.jpg(/.*)?"
});
// Localized XML Sitemap routes for MvcSiteMapProvider
routes.MapRoute("localizedSitemap", "{country}/{lang}/sitemap.xml",
new
{
country = "uk",
lang = "En",
controller = "XmlSiteMap",
action = "Index",
page = 0
});
routes.MapRoute("localizedSitemapPage", "{country}/{lang}/sitemap-{page}.xml",
new
{
country = "uk",
lang = "En",
controller = "XmlSiteMap",
action = "Index",
page = 0
});
routes.MapRoute("Default", "{controller}/{action}/{id}",
new
{
country = "uk",
lang = "En",
controller = "Home",
action = "Index",
id = UrlParameter.Optional
});
routes.MapRoute("localizedDefault", "{country}/{lang}/{controller}/{action}/{id}",
new
{
country = "uk",
lang = "En",
controller = "Home",
action = "Index",
id = UrlParameter.Optional
});
}
You will need to create an index file so the search engines will know about your localized URLs, and submit the index file to search engines instead of the one at /sitemap.xml.
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<sitemap>
<!-- specifies the default culture sitemap En-uk -->
<loc>http://www.example.com/sitemap.xml</loc>
</sitemap>
<sitemap>
<loc>http://www.example.com/de/De/sitemap.xml</loc>
</sitemap>
<sitemap>
<loc>http://www.example.com/mx/Es/sitemap.xml</loc>
</sitemap>
</sitemapindex>
You should also specify this sitemap index in your robots.txt file so bots of minor search engines can pick it up automatically. For example, if you put the above XML into a file named sitemapindex.xml at the root of your site (where it should be), you can add this line to your robots.txt file (anywhere in the file):
Sitemap: http://www.example.com/sitemapindex.xml
Reference: http://www.sitemaps.org/protocol.html#informing
You should also do a 301 redirect for requests of /uk/En/SomeController/SomeAction and /uk/En/SomeController/SomeAction/SomeId to the default versions of these URLs. This can be accomplished either by subclassing RouteBase to make a redirect route or by using an IIS rewrite rule in your web.config file.
Additional Information That Might Be Helpful
There are several things that could cause the SiteMapPath not to appear.
The node intended to match the current page is missing some route or querystring parameters.
Security Trimming is enabled and the user doesn't have permission to view the current node.
The visibility settings are prohibiting the nodes from being rendered.
The HTML helper templates in your /Views/Shared/DisplayTemplates/ folder are prohibiting them from being shown.
The configuration isn't set up to start automatically.
Since you are upgrading, the most likely causes are #1 or #5.
You can rule out #5 by navigating to /sitemap.xml to see if your XML sitemap is rendering.
It might be helpful to temporarily add #Html.MvcSiteMap().SiteMap() to your layout page to see if any nodes are not resolving to the correct URL.
If you do that, #1 is the most likely cause. You must configure every route value to either a specific value...
<mvcSiteMapNode title="Foo" action="Index" controller="Customer" id="3"/>
This works best in conjunction with dynamic node providers.
Or you can force a match with any value by using preservedRouteParameters.
<mvcSiteMapNode title="Foo" action="Index" controller="Customer" preservedRouteParameters="id" />
Have a look at How to Make MvcSiteMapProvider Remember a User's Position for an in depth discussion of this behavior.
Also, keep in mind that if you are using custom attributes that you don't intend to use with routing, you need to add them to the MvcSiteMapProvider_AttributesToIgnore configuration setting.
<mvcSiteMapNode title="Foo" action="Index" controller="Customer" myCustomValue="Something" />
In web.config:
<appSettings>
<add key="MvcSiteMapProvider_AttributesToIgnore" value="myCustomValue"/>
</appSettings>

Related

Add additional fields in registration using Jhipster 4.10.2

I need to add phone number to the registration page and need to save it in the db as well. I followed following link.
http://www.jhipster.tech/tips/022_tip_registering_user_with_additional_information.html
But since here Jhispter version is changed code is bit different than the code in above link. So I am bit confusing to go with it. According to the link instructions I did upto "Updating ManagedUserVM". Then after I need the help since code is differed.
It really didn't change that much, and the logic remains the same.
The registerAccount function should look like this now :
public void registerAccount(#Valid #RequestBody ManagedUserVM managedUserVM) {
if (!checkPasswordLength(managedUserVM.getPassword())) {
throw new InvalidPasswordException();
}
userRepository.findOneByLogin(managedUserVM.getLogin().toLowerCase()).ifPresent(u -> {throw new LoginAlreadyUsedException();});
userRepository.findOneByEmailIgnoreCase(managedUserVM.getEmail()).ifPresent(u -> {throw new EmailAlreadyUsedException();});
User user = userService.registerUser(managedUserVM, managedUserVM.getPassword(), managedUserVM.getPhone());
mailService.sendActivationEmail(user);
}
And the registerUser function in the UserService (which is a rename of the former createUser) :
public User registerUser(UserDTO userDTO, String password, String phone) {
// JHipster code omitted for brevity
...
// Create and save the UserExtra entity
UserExtra newUserExtra = new UserExtra();
newUserExtra.setUser(newUser);
newUserExtra.setPhone(phone);
userExtraRepository.save(newUserExtra);
log.debug("Created Information for UserExtra: {}", newUserExtra);
return newUser;
}
Just note that you may have to manually change your database changelog (if using a SQL database) to correctly link the ids of User and UserExtra, so it looks like this :
<createTable tableName="user_extra">
<column name="phone" type="varchar(255)">
<constraints nullable="true" />
</column>
<column name="user_id" type="bigint">
<constraints primaryKey="true" nullable="false" />
</column>
<!-- jhipster-needle-liquibase-add-column - JHipster will add columns here, do not remove-->
</createTable>

Show dynamic breadcrumb based on preservedRouteParameters for MvcSiteMapProvider

I'm using MvcSiteMapProvider for ASP.NET MVC5 project. I want to show a dynamic breadcrumb based on preservedRouteUrlParamters. I have multiple universities and each university has courses. I don't want to list all the university in the mvc.sitemap.
Instead of:
url: /stanford
breadcrumb: home / university
url: /stanford/course1
breadcrumb: home / university / course details
It should look like:
url: /stanford
breadcrumb: home / stanford
url: /stanford/course1
breadcrumb: home / stanford / course details ...where stanford is link to /stanford
url: /mit
breadcrumb: home / mit
url: /mit/course1
breadcrumb: home / mit / course details ...where mit is link to /mit
So this is the pattern:
url: /{university}
breadcrumb: home / {university}
url: /{university}/{course}
breadcrumb: home / {university} / course details
Here is the mvc.sitemap config I have:
<mvcSiteMapNode title="university" controller="Curriculum" action="UniversityDetails" preservedRouteParameters="university">
<mvcSiteMapNode title="course details" action="CourseDetails" preservedRouteParameters="university,course"/>
</mvcSiteMapNode>
This is solution I have so far but I'm not sure if it is a good way.
I use title="{university}" and check for the pattern {university}.
<mvcSiteMapNode title="{university}" controller="Curriculum" action="UniversityDetails" preservedRouteParameters="university">
<mvcSiteMapNode title="course details" action="CourseDetails" preservedRouteParameters="university,course"/>
</mvcSiteMapNode>
I use the SiteMapNodeModel.Url to dynamically generate the breadcrumb.
public static string TitleBreadcrumb(this SiteMapNodeModel m)
{
if (m.Title.StartsWith("{") && m.Title.EndsWith("}"))
{
return m.Url.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries).Last();
}
return m.Title;
}
I then use the above extension method in the SiteMapNodeModel.cshtml display template.
// use #Model.TitleBreadcrumb() instead of #Model.Title
#Model.TitleBreadcrumb()
Similar for #Model.Description.
Is there a better way?
The only thing particularly wrong with your approach is that you are not encoding the value from the URL before displaying it in your HTML. This means that some malicious user could potentially inject HTML and/or JavaScript into your page by manipulating the URL.
However, the most common way to provide a dynamic title is to use the SiteMapTitleAttribute, which uses a value from your Model or a value in your ViewData to populate the title dynamically.
[SiteMapTitle("Name")]
public ViewResult UniversityDetails(string university) {
var model = _repository.Find(university);
// Name is a string property of
// the university model object.
return View(model);
}
[SiteMapTitle("Name", Target = AttributeTarget.ParentNode)]
public ViewResult CourseDetails(string university) {
var model = _repository.Find(university);
// Name is a string property of
// the university model object.
return View(model);
}

Create child nodes dynamically for a node in MVC.Sitemap

I am working on an MVC 4 application where I am showing a menu on masterpage using Mvc.sitemap. I have a node named say "Tasks" which will appear everytime among other nodes on menu. I need to create child nodes for this node based on values fetched from database. Depending on number of values the child nodes will be created and on clicking each child code a certain function will be performed.
Since I dont know how to generate child nodes according to values from database, I have hardcoded nodes as of now in Mvc.sitemap. Below is the code of how I have been doing it now:
<?xml version="1.0" encoding="utf-8" ?>
<mvcSiteMap xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://mvcsitemap.codeplex.com/schemas/MvcSiteMap-File-4.0"
xsi:schemaLocation="http://mvcsitemap.codeplex.com/schemas/MvcSiteMap-File-4.0 MvcSiteMapSchema.xsd">
<mvcSiteMapNode title="Home" controller="Home" action="Index">
<mvcSiteMapNode title="Tasks" controller="Home" action="Index">
<mvcSiteMapNode title="Task 1" controller="Home" action="Index" url="http://localhost:...."/>
<mvcSiteMapNode title="Task 2" controller="Home" action="Index" url="http://localhost:...."/>
</mvcSiteMapNode>
<mvcSiteMapNode title="Admin" controller="Home" action="Admin"/>
<mvcSiteMapNode title="About" controller="Home" action="About"/>
<mvcSiteMapNode title="Help" controller="Home" action="Help"/>
</mvcSiteMapNode>
</mvcSiteMap>
As you can see in the above code I have hardcoded the child nodes and also specified the url property.
Please help on how to achieve this dynamically. Thanks in Advance!!
This is what dynamic node providers are for.
<mvcSiteMapNode title="Tasks" controller="Home" action="Index" key="TasksIndex">
<!-- This is the task template node - this node won't be added to the SiteMap,
but will be used to define the defaults of the Dynamic Nodes -->
<mvcSiteMapNode action="Index" dynamicNodeProvider="MyNamespace.TaskDynamicNodeProvider, MyAssembly" />
</mvcSiteMapNode>
namespace MyNamespace
{
public class TaskDynamicNodeProvider
: DynamicNodeProviderBase
{
public override IEnumerable<DynamicNode> GetDynamicNodeCollection(ISiteMapNode node)
{
using (var db = new MyEntities())
{
// Create a node for each album
foreach (var task in db.Tasks)
{
var dynamicNode = new DynamicNode();
dynamicNode.Title = task.Name;
dynamicNode.ParentKey = "TasksIndex";
dynamicNode.RouteValues.Add("id", task.Id);
// NOTE: Controller is automatically inherited in the XML from the
// nearest parent node where it is set, and action is set in the
// template node in this example. However, you can override the
// values here if you need to.
// dynamicNode.Controller = "Home";
// dynamicNode.Action = "Index";
yield return dynamicNode;
}
}
}
}
}

How to Config MVCSiteMap to realize the parameters?

I'm new to MVCSiteMap and I have a simple question:
I use the default route config like this:
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional });
Now in my controller, I want to create and edit an entity in the same Action:
public ActionResult AddEdit(int? id)
{}
so if the id is null, it means add, and if it is not null then the action is edit.
Now I want the site map to realize the different from add and edit. I tried this:
<mvcSiteMapNode title="Parent" controller="Class" action="Index">
<mvcSiteMapNode title="Add" controller="Class" action="AddEdit" />
<mvcSiteMapNode title="Edit" controller="Class" action="AddEdit" inheritedRouteParameters="Id"/>
</mvcSiteMapNode>
but seems it does not work well. It always use the second one.
What should I do?
Thanks a lot.
There are 2 options.
Option 1
Create a single node that sets preservedRouteParameters="id" on each of the nodes that correspond to an action method with a parameter. This creates a 1-to-1 relationship between the nodes and action methods, but a 1-to-many relationship between the node and the actual entities.
<mvcSiteMapNode title="Products" controller="Product" action="Index">
<mvcSiteMapNode title="Create New" controller="Product" action="Create" visibility="SiteMapPathHelper,!*" />
<mvcSiteMapNode title="Details" controller="Product" action="Details" visibility="SiteMapPathHelper,!*" preservedRouteParameters="id">
<mvcSiteMapNode title="Edit" controller="Product" action="Edit" visibility="SiteMapPathHelper,!*" key="Product_Edit" preservedRouteParameters="id"/>
<mvcSiteMapNode title="Delete" controller="Product" action="Delete" visibility="SiteMapPathHelper,!*" preservedRouteParameters="id"/>
</mvcSiteMapNode>
</mvcSiteMapNode>
This is the recommended way to do it if you are creating pages that edit data, especially if those pages will never be indexed by search engines.
In most cases, you will also need to setup the FilteredSiteMapNodeVisibilityProvider and SiteMapTitleAttribute to fix the visibility and title of the nodes. You won't be able to use this method for anything other than a breadcrumb trail, so it is important to hide these fake nodes from the other HTML helpers like the Menu and SiteMap.
For a complete demo of how this can be done, visit How to Make MvcSiteMapProvider Remember a User's Position.
Option 2
Use a custom IDynamicNodeProvider to create a node per entity (1-to-1 relationship).
public class StoreDetailsDynamicNodeProvider
: DynamicNodeProviderBase
{
public override IEnumerable<DynamicNode> GetDynamicNodeCollection(ISiteMapNode node)
{
using (var storeDB = new MusicStoreEntities())
{
// Create a node for each album
foreach (var album in storeDB.Albums.Include("Genre"))
{
DynamicNode dynamicNode = new DynamicNode();
dynamicNode.Title = album.Title;
dynamicNode.ParentKey = "Genre_" + album.Genre.Name;
dynamicNode.RouteValues.Add("id", album.AlbumId);
yield return dynamicNode;
}
}
}
}
To use this, you need to ensure you set up your key and parent keys in code so each node understands what parent node it belongs to. You may need to explicitly set the "key" attribute in your XML in order to do this. You also need to ensure you set the "id" routeValue on each record to ensure your node matches your incoming route.
Use this method when your pages must be indexed by search engines and/or you want to see the nodes in the menu.
Do note that you can combine these 2 options in the same application and it will work fine. Both of these methods will also work for any number of custom route values (other than "id") as well.

mvc sitemap provider: menu disappears

I am using mvcsitemap to create a menu, submenu and breadcrump for a webapp I'm developing. I am using preserveroutevalues in the xml config in order to move around my (many) parameters when rendering links. When a user starts playing around with query parameters, the menu/submenu/breadcrumb disappear after some page loads. This is not consistent and I cannot reproduce it all the time, but it happens way too ofter.
My routes
routes.MapRoute(
name: "ForwardCurveDefault",
url: "forward-curve/{commoditycode}/{selectedyear}-{selectedmonth}-{selectedday}",
defaults: new {
controller = "ForwardCurve", action = "index",
selectedyear = default(DateTime).Year,
selectedmonth = default(DateTime).Month,
selectedday = default(DateTime).Day,
commoditycode = "none"
}
);
routes.MapRoute(
name: "Consumption",
url: "consumption/{commoditycode}/{managedcompanyid}/{entityid}/{startyear}-{startmonth}-{startday}/{endyear}-{endmonth}-{endday}",
defaults: new {
controller = "Consumption", action = "Index",
// begining of current month.
startyear = default(DateTime).Year,
startmonth = default(DateTime).Month,
startday = default(DateTime).Day,
// end of current month.
endyear = default(DateTime).Year,
endmonth = default(DateTime).Month,
endday = default(DateTime).Day,
commoditycode = "none",
managedcompanyid = 0,
entityid = 0,
}
);
routes.MapRoute(
name: "Budget",
url: "budget/{commoditycode}/{managedcompanyid}/{entityid}/{startyear}-{startmonth}-{startday}/{endyear}-{endmonth}-{endday}",
defaults: new {
controller = "Budget", action = "Index",
// begining of current month.
startyear = default(DateTime).Year,
startmonth = default(DateTime).Month,
startday = default(DateTime).Day,
// end of current month.
endyear = default(DateTime).Year,
endmonth = default(DateTime).Month,
endday = default(DateTime).Day,
commoditycode = "none",
managedcompanyid = 0,
entityid = 0,
}
);
My sitemap
<mvcSiteMapNode title="Data Management" controller="ForwardCurve" action="Index" key="DataManagement" preservedRouteParameters="commoditycode,selectedyear,selectedmonth,selectedday">
<mvcSiteMapNode title="Forward curves" controller="ForwardCurve" action="Index" key="forwardcurve" preservedRouteParameters="commoditycode,selectedyear,selectedmonth,selectedday"/>
<mvcSiteMapNode title="Consumption" controller="Consumption" action="Index" key="consumption" preservedRouteParameters="commoditycode,managedcompanyid,entityid,startyear,startmonth,startday,endyear,endmonth,endday"/>
<mvcSiteMapNode title="Budget" controller="Budget" action="Index" key="budget" preservedRouteParameters="commoditycode,managedcompanyid,entityid,startyear,startmonth,startday,endyear,endmonth,endday"/>
</mvcSiteMapNode>
I get a nice menu. After going back and forth to different query values, I end up in a page with a valid URL and no menu. I can see that my currentNode is null.
Have you checked this out yet with version 4.0? There have been some significant improvements in the node matching logic.

Resources