How do I create multiple different layouts using only one SiteMap? - asp.net

Suppose I have 3 areas on my page that have links
Header
Menu
Footer
Each have different links, but some links overlap:
I am using MVCSiteMapProvider to accomplish this. I have a SiteMap:
<?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-3.0"
xsi:schemaLocation="http://mvcsitemap.codeplex.com/schemas/MvcSiteMap-File-3.0 MvcSiteMapSchema.xsd"
enableLocalization="false">
<mvcSiteMapNode title="Home" controller="Home" action="Index">
<mvcSiteMapNode title="Link 1" controller="" action="" visibility="Header, Footer" />
<mvcSiteMapNode title="Link 2" controller="" action="" visibility="Menu" />
<mvcSiteMapNode title="Link 3" controller="" action="" visibility="Header, Menu" />
<mvcSiteMapNode title="Link 4" controller="" action="" visibility="Menu, Footer, Header" />
</mvcSiteMapNode>
</mvcSiteMap>
I thought that maybe Visibility was the way to do this, but it doesn't work the way I want it.
Public Class MenuVisibilityProvider
Implements ISiteMapNodeVisibilityProvider
Public Function IsVisible(ByVal node As SiteMapNode, ByVal context As HttpContext, ByVal sourceMetadata As IDictionary(Of String, Object)) As Boolean Implements ISiteMapNodeVisibilityProvider.IsVisible
Dim visibility As String = node("visibility")
If visibility IsNot Nothing Then Return True
Select Case visibility
Case "Menu"
Case "Header"
Case "Footer"
Return True
End Select
Return False
End Function
End Class
I end up with all of the links in every area.
Edit for clarification:
This is a similar question, but also with no answer:
https://stackoverflow.com/questions/12845929/how-to-show-partial-site-map-including-current-node-with-mvcsitemapprovider
Also similar, but I don't want to have to make multiple SiteMaps: Using Multiple MvcSiteMaps

OP here. I accomplished this using only one site map.
To do this:
I added visibility tags to each sitemap element, for example:
<mvcSiteMapNode title="Login" controller="Members" action="Login" visibility="SideMenu Footer" />
In this example "SideMenu Footer" are my tags. I will use String.Contains() later to determine visibility.
I added multiple different siteMap providers in the Web.config with different siteMapNodeVisibilityProvider:
<siteMap defaultProvider="MvcSiteMapProvider" enabled="true">
<providers>
<clear />
<add name="MvcSiteMapProvider" type="MvcSiteMapProvider.DefaultSiteMapProvider, MvcSiteMapProvider" siteMapFile="~/Mvc.Sitemap" securityTrimmingEnabled="true" cacheDuration="5" enableLocalization="true" scanAssembliesForSiteMapNodes="true" includeAssembliesForScan="" excludeAssembliesForScan="" attributesToIgnore="visibility" nodeKeyGenerator="MvcSiteMapProvider.DefaultNodeKeyGenerator, MvcSiteMapProvider" controllerTypeResolver="MvcSiteMapProvider.DefaultControllerTypeResolver, MvcSiteMapProvider" actionMethodParameterResolver="MvcSiteMapProvider.DefaultActionMethodParameterResolver, MvcSiteMapProvider" aclModule="MvcSiteMapProvider.DefaultAclModule, MvcSiteMapProvider" siteMapNodeUrlResolver="MvcSiteMapProvider.DefaultSiteMapNodeUrlResolver, MvcSiteMapProvider" siteMapNodeVisibilityProvider="MvcSiteMapProvider.DefaultSiteMapNodeVisibilityProvider, MvcSiteMapProvider" siteMapProviderEventHandler="MvcSiteMapProvider.DefaultSiteMapProviderEventHandler, MvcSiteMapProvider" />
<add name="NavSiteMapProvider" ... siteMapNodeVisibilityProvider="RootNamespace.Namespace.NavVisibilityProvider, RootNamespace" ... />
<add name="FooterSiteMapProvider" ... siteMapNodeVisibilityProvider="RootNamespace.Namespace.FooterVisibilityProvider, RootNamespace" ... />
</providers>
</siteMap>
I created a new code file (class) called CustomVisibilityProvider. Inside I created a class for each provider (Footer, Nav, Menu etc)
' Note: VB.NET :P
Public Class MenuVisibilityProvider
Implements ISiteMapNodeVisibilityProvider
Public Function IsVisible(ByVal node As SiteMapNode, ByVal context As HttpContext, ByVal sourceMetadata As IDictionary(Of String, Object)) As Boolean Implements ISiteMapNodeVisibilityProvider.IsVisible
Dim visibility As String = node("visibility")
If visibility Is Nothing Then Return False
If visibility.Contains("Menu") Then Return True
Return False
End Function
End Class
Public Class NavVisibilityProvider
Implements ISiteMapNodeVisibilityProvider
...
If visibility.Contains("Nav") Then Return True
...
End Class
When you're in a view file:
#Html.MvcSiteMap("FooterSiteMapProvider").Menu
Note: You can give the Menu() a custom view also, so that nav, footer, menu etc render the links differently (some might be in <ul></ul> format while others might be <a> tags).

Related

MVCSiteMapProvider ignore parameters

I am using MVCSiteMapProvider.MVC5 and I am trying to set up my site map.
My routes are:
notifications/
notifications/Alarm/
notifications/Warning/
notifications/Information/
The Alarm, Warning and Information are passed to the "notificationFilter" action parameter.
This is my basic site map which currently shows by breadcrumb for route notifications/ as Home>Notifications.
<mvcSiteMapNode title="Home" controller="Dashboard" action="System">
<mvcSiteMapNode title="Vacuum Management" controller="Dashboard" action="SubSystem" />
<mvcSiteMapNode title="Notifications" controller="Notifications" action="SystemNotifications" />
</mvcSiteMapNode>
What I would like to do is add a catch all so that if the "notificationFilter" parameter is passed (e.g. notifications/Alarm/) it still displays the breadcrumb as Home>Notifications and ignores the parameter.
I have tried this:
<mvcSiteMapNode title="Home" controller="Dashboard" action="System">
<mvcSiteMapNode title="Vacuum Management" controller="Dashboard" action="SubSystem" />
<mvcSiteMapNode title="Notifications" controller="Notifications" action="SystemNotifications" notificationFilter="Alarm" />
Which works for notifications/Alarm but then nothing else. I also tried adding multiple lines:
<mvcSiteMapNode title="Home" controller="Dashboard" action="System">
<mvcSiteMapNode title="Vacuum Management" controller="Dashboard" action="SubSystem" />
<mvcSiteMapNode title="Notifications" controller="Notifications" action="SystemNotifications" notificationFilter="Alarm" />
<mvcSiteMapNode title="Notifications" controller="Notifications" action="SystemNotifications" notificationFilter="Warning" />
<mvcSiteMapNode title="Notifications" controller="Notifications" action="SystemNotifications" notificationFilter="Information" />
But because the title is the key the the bread crumb would display different text.
All help appreciated.
Thanks in advance!
Route Config:
routes.MapRoute(
name: "NotificationsSystem",
url: "notifications",
defaults: new { controller = "Notifications", action = "SystemNotifications", notificationFilter = "" }
);
routes.MapRoute(
name: "NotificationsSystemFiltered",
url: "notifications/{notificationFilter}",
defaults: new { controller = "Notifications", action = "SystemNotifications", notificationFilter = "" }
);
As per the documentation, getting a single node to match any value for a particular parameter can be done by using preservedRouteParameters.
<mvcSiteMapNode title="Notifications" controller="Notifications" action="SystemNotifications" preservedRouteParameters="notificationFilter" />
This works well when your node has no children, but if you put this attribute on nodes that do have children you need to take into consideration what will happen when the user navigates back up the hierarchy.

XmlSiteMapProvider only parse the first node

I have an annoying problem and i can't find any start of a solution, so i hope you can help me.
I have a sitemap with roles defined for each node :
<?xml version="1.0" encoding="utf-8"?>
<siteMap enableLocalization="true">
<siteMapNode title="" url="" roles="">
<siteMapNode title="default" url="~/Default.aspx" roles="user" />
<siteMapNode title="supervision" url="~/EcranSupervision.aspx" roles="Admin" />
<siteMapNode title="exploitation" url="~/ChaineTraitementList.aspx" roles="Admin" />
</siteMapNode>
</siteMap>
And in my web.config, i enable security and i use a custom provider :
<siteMap defaultProvider="MainMenuSitemap">
<providers>
<add name="MainMenuSitemap" type="UbiXmlSiteMapProvider" siteMapFile="Web.sitemap" securityTrimmingEnabled="true" />
</providers>
</siteMap>
In my provider, i only override IsAccessibleToUser to do my logic :
public class UbiXmlSiteMapProvider : XmlSiteMapProvider
{
public override bool IsAccessibleToUser(HttpContext context, SiteMapNode node)
{
// custom logic here
}
}
My problem is that the node used in IsAccessibleToUser is always the one with the url "Default.aspx".
So if I have the role to see it, all the nodes are shown and if i don't have the role, none of the nodes are shown.
I don't understand what is wrong here.
Do you have a hint for me ?

How to implement Google reCaptcha in an MVC3 application?

Can anyone explain how to have reCaptcha functionality like stackoverflow in my MVC3 application.
And how can you customize that?
I use the Google ReCaptcha and it works very well and is very simple to implement.
Note that if you are using Https be sure you have the current version of the dll (1.0.5.0 at this time)
You need to create an account on the Google Recaptcha site and get a set of public and private keys. Add the keys to your web project main web.config file:
<appSettings>
<add key="webpages:Version" value="1.0.0.0"/>
<add key="ClientValidationEnabled" value="true"/>
<add key="UnobtrusiveJavaScriptEnabled" value="true"/>
<add key="ReCaptchaPrivateKey" value="put your private key value here" />
<add key="ReCaptchaPublicKey" value="put your public key value here" />
</appSettings>
Now use NuGet and install the reCAPTCHA plugin for .NET
Then, go to your web.config file inside of your VIEWS folder. Add this line:
<namespaces>
<add namespace="System.Web.Mvc" />
<add namespace="System.Web.Mvc.Ajax" />
<add namespace="System.Web.Mvc.Html" />
<add namespace="System.Web.Routing" />
<add namespace="Recaptcha"/>
</namespaces>
Then, in your view that you want to show the captcha, add the using statement at the top of your file
#using Recaptcha;
then add this to your view:
<div class="editor-label">
Are you a human?
</div>
<div class="editor-field">
#Html.Raw(Html.GenerateCaptcha("captcha", "clean"))
#Html.ValidationMessage("captcha")
</div>
In your controller action you will need to modify the signature to accept the captcha results:
[HttpPost]
[RecaptchaControlMvc.CaptchaValidator]
public ActionResult ForgotPassword(CheckUsernameViewModel model, bool captchaValid, string captchaErrorMessage) {
if (!Membership.EnablePasswordReset)
throw new Exception("Password reset is not allowed\r\n");
if(ModelState.IsValid) {
if(captchaValid) {
return RedirectToAction("AnswerSecurityQuestion", new { username = model.Username });
}
ModelState.AddModelError("", captchaErrorMessage);
}
return View(model);
}
Following those steps have allowed me to implement captcha on several pages and it works smoothly. Note that the parameter names on the controller action MUST BE NAMED CORRECTLY:
bool captchaValid, string captchaErrorMessage
If you changed these parameter names you WILL get an error at runtime when your form posts back to the controller action.
I would recommend using a Honeypot Captcha. The experience for your users is MUCH better. There is one fore ASP.NET MVC here http://nuget.org/packages/SimpleHoneypot.MVC
PM> Install-Package SimpleHoneypot.MVC4
There is a WiKi on how to get it up here: https://github.com/webadvanced/Honeypot-MVC/wiki
Just start out with the Getting Started section.
You can read more about the general idea of a Honeypot Captcha here: http://haacked.com/archive/2007/09/11/honeypot-captcha.aspx

roles based menu does not work, what am I doing wrong?

I can't figure this one out.
I have the following SiteMap
<?xml version="1.0" encoding="utf-8" ?>
<siteMap xmlns="http://schemas.microsoft.com/AspNet/SiteMap-File-1.0" >
<siteMapNode url="~/" title="Root" description="Go root">
<siteMapNode url="~/h" title="Home" description="Go home" />
<siteMapNode url="~/h/uo" title="Ultima Online" description="Ultima Online">
<siteMapNode url="~/h/uo/get" roles="RegisteredUser" title="Get account!" description="Get account!" />
</siteMapNode>
</siteMapNode>
</siteMap>
I've an XmlSiteMapProvider with securityTrimmingEnabled="true", which points to this site map file.
The file I want to trim has an authorization rule in it's folder's web.config
<configuration>
<system.web>
<authorization>
<deny users="?" />
</authorization>
</system.web>
</configuration>
The file can't be accessed via url, if I type http://localhost/h/uo/get I get redirected to login page.
I've set up an <asp:Menu> like this in the Master page file:
<asp:SiteMapDataSource ID="MenuSiteMap" ShowStartingNode="false"
SiteMapProvider="MenuSiteMapProvider" runat="server"
/>
<div>
<asp:Menu ID="NavigationMenu" runat="server" DataSourceID="MenuSiteMap"
CssClass="menu" EnableViewState="false"
IncludeStyleBlock="false" Orientation="Horizontal"
/>
</div>
Yet, when the page is rendered, I see the Get account node that is supposed to be trimmed when I'm not even logged in, no matter what.
What am I doing wrong?
Is there any other way to build a security trimming enabled site map navigation menu?
I'm using ASP.NET 4.0, and URL-rewritting with an HttpModule.
In reading http://forums.asp.net/t/975077.aspx/1 I found out that this is exactly what is happening to me.
If the node doesn't have an URL it behaves fine, but if it does, like all of my nodes do. Security trimming is just ignored.
I resolved my problem by resorting to a more intuitive role based site map implementation, to say:
public class TrimmingXmlSiteMapProvider : XmlSiteMapProvider
{
public override bool IsAccessibleToUser(HttpContext context, SiteMapNode node)
{
if (node.Roles.Cast<string>().Any(r => r == "*"))
return true;
if (node.Roles.Count > 0 && node.Roles.Cast<string>().Count(Roles.IsUserInRole) == 0)
return false;
return node.ParentNode != null && node.ParentNode.IsAccessibleToUser(context);
}
}
Then, the only change I had to make was add an asterisk to the root level's role definition.
How does this work?
First I check if any of the roles definied for this node is an asterisk, if that's the case, then I can see the node.
Second, if the node isn't everyone-level, I check if there are any roles specified, and if the logged in user is part of at least one of them.
Lastly, I check if there is a parent node, and just inherit their rule.
This allows the security trimming to actually be "SECURITY TRIMMING" and not well, however the heck it's supposed to be working by default.

Change the link on a sitemap based on if a user is logged in?

I have a sitemap that has a link for when a user is not logged in, but when they do login, the link should change, for example, nonmember.aspx should change to member.aspx. This sitemap is tied to an asp:menu. Does anyone know how to do this?
A simple solution is to have two nodes in your sitemap.
One node shows up for Anonymous users.
One node shows up for Authenticated users with the security access
I believe you can set this up quite simply.
The end result is the same as changing the link but it's easier to maintain.
To add to this:
<?xml version="1.0" encoding="utf-8" ?>
<siteMap xmlns="http://schemas.microsoft.com/AspNet/SiteMap-File-1.0" >
<siteMapNode title="Home" url="~/" roles="*">
<siteMapNode url="~/Member.aspx" title="Home" roles="SpecialPeople" />
<siteMapNode url="~/Nonmember.aspx" title="Site Map" roles="HideForUsers" />
</siteMapNode>
</siteMap>
So, you set up a rule that denies access to the "HideForMembers" role to authenticated users. It's something like that. ASP.NET will take the first rule it finds a match, so you should be able to accomplish it this way.
Otherwise, you could do a Menu_OnDataBound and look for the node:
Protected Sub menMainDataBound(ByVal sender As Object, ByVal e As System.EventArgs)
Try
Dim myPage As New Page
Dim myPrincipal As IPrincipal
Dim colNodes As New Collection
myPrincipal = myPage.User
If myPrincipal.Identity.IsAuthenticated = True Then
Dim menNode As MenuItem
For Each menNode In menMain.Items
Select Case menNode.Value.ToString
Case "Products"
colNodes.Add(menNode)
Case "Contact Us"
colNodes.Add(menNode)
Case "About Us"
colNodes.Add(menNode)
Case "Links"
colNodes.Add(menNode)
End Select
Next
For Each menNode In colNodes
menMain.Items.Remove(menNode)
Next
End If
Catch ex As Exception
End Try
End Sub
source
The below is the web.config code you're looking for:
<location path="Registration.aspx">
<system.web>
<authorization>
<allow users="?" />
<deny users="*" />
</authorization>
</system.web>
</location>

Resources