Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Upgrading from MVC5 4.4.6 -> 4.4.8 throws KeyNotFoundException #268

Closed
bkryl opened this issue Jan 27, 2014 · 11 comments
Closed

Upgrading from MVC5 4.4.6 -> 4.4.8 throws KeyNotFoundException #268

bkryl opened this issue Jan 27, 2014 · 11 comments

Comments

@bkryl
Copy link

bkryl commented Jan 27, 2014

Will try to track down more details. For now, here is the stacktrace when trying to request sitemap.xml file. Reverting back to 4.4.6 fixes the issue for now.

[KeyNotFoundException: The given key was not present in the dictionary.]
System.Collections.Generic.Dictionary2.get_Item(TKey key) +14379623 MvcSiteMapProvider.FilteredSiteMapNodeVisibilityProvider.IsVisible(ISiteMapNode node, IDictionary2 sourceMetadata) +400
MvcSiteMapProvider.RequestCacheableSiteMapNode.IsVisible(IDictionary2 sourceMetadata) +144 MvcSiteMapProvider.Web.Mvc.XmlSiteMapResult.ShouldNodeRender(ISiteMapNode node, ControllerContext context) +66 MvcSiteMapProvider.Web.Mvc.<FlattenHierarchy>d__f.MoveNext() +152 MvcSiteMapProvider.Web.Mvc.<FlattenHierarchy>d__f.MoveNext() +568 MvcSiteMapProvider.Web.Mvc.<FlattenHierarchy>d__f.MoveNext() +568 MvcSiteMapProvider.Web.Mvc.<FlattenHierarchy>d__f.MoveNext() +568 MvcSiteMapProvider.Web.Mvc.<FlattenHierarchy>d__f.MoveNext() +568 MvcSiteMapProvider.Web.Mvc.<FlattenHierarchy>d__f.MoveNext() +568 MvcSiteMapProvider.Web.Mvc.XmlSiteMapResult.ExecuteResult(ControllerContext context) +584 System.Web.Mvc.ControllerActionInvoker.InvokeActionResultFilterRecursive(IList1 filters, Int32 filterIndex, ResultExecutingContext preContext, ControllerContext controllerContext, ActionResult actionResult) +109
System.Web.Mvc.ControllerActionInvoker.InvokeActionResultFilterRecursive(IList1 filters, Int32 filterIndex, ResultExecutingContext preContext, ControllerContext controllerContext, ActionResult actionResult) +890 System.Web.Mvc.ControllerActionInvoker.InvokeActionResultFilterRecursive(IList1 filters, Int32 filterIndex, ResultExecutingContext preContext, ControllerContext controllerContext, ActionResult actionResult) +890
System.Web.Mvc.ControllerActionInvoker.InvokeActionResultFilterRecursive(IList1 filters, Int32 filterIndex, ResultExecutingContext preContext, ControllerContext controllerContext, ActionResult actionResult) +890 System.Web.Mvc.ControllerActionInvoker.InvokeActionResultFilterRecursive(IList1 filters, Int32 filterIndex, ResultExecutingContext preContext, ControllerContext controllerContext, ActionResult actionResult) +890
System.Web.Mvc.ControllerActionInvoker.InvokeActionResultFilterRecursive(IList1 filters, Int32 filterIndex, ResultExecutingContext preContext, ControllerContext controllerContext, ActionResult actionResult) +890 System.Web.Mvc.ControllerActionInvoker.InvokeActionResultWithFilters(ControllerContext controllerContext, IList1 filters, ActionResult actionResult) +97
System.Web.Mvc.Async.<>c__DisplayClass21.b__1e(IAsyncResult asyncResult) +241
System.Web.Mvc.Controller.b__1d(IAsyncResult asyncResult, ExecuteCoreState innerState) +29
System.Web.Mvc.Async.WrappedAsyncVoid1.CallEndDelegate(IAsyncResult asyncResult) +111 System.Web.Mvc.Controller.EndExecuteCore(IAsyncResult asyncResult) +53 System.Web.Mvc.Async.WrappedAsyncVoid1.CallEndDelegate(IAsyncResult asyncResult) +19
System.Web.Mvc.MvcHandler.b__5(IAsyncResult asyncResult, ProcessRequestState innerState) +51
System.Web.Mvc.Async.WrappedAsyncVoid`1.CallEndDelegate(IAsyncResult asyncResult) +111
System.Web.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +606
System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +288

@AdmiringWorm
Copy link

I just started getting this stack-trace as well when requesting 'sitemap.xml', and
I believe I can narrow it down a little.
From what I can see it has something to do with the visibility attribute.
If the visibility attribute is removed from all nodes (both dynamic and static) the 'sitemap.xml' is working just fine.

@NightOwl888
Copy link
Collaborator

Thanks. Actually, it is an issue with improperly guarding against optional keys in the FilteredSiteMapNodeVisibilityProvider.

This issue is already fixed in my local codebase, I am just working on getting some other issues fixed before releasing it.

@NightOwl888
Copy link
Collaborator

In the meantime, you can work around this problem by putting the fixed FilteredSiteMapNodeVisibilityProvider into your own project and adjusting the namespace accordingly. Then all you have to do is change the namespace back when the patch is released.

I could also use some feedback on the fix (and the new "named" HTML helper feature that caused it to break) if there is still something awry here.

using System;
using System.Collections.Generic;
using System.Web;

namespace MvcSiteMapProvider
{
    /// <summary>
    /// Filtered SiteMapNode Visibility Provider.
    /// 
    /// Rules are parsed left-to-right, first match wins. Asterisk can be used to match any control. Exclamation mark can be used to negate a match.
    /// </summary>
    public class FilteredSiteMapNodeVisibilityProvider
        : SiteMapNodeVisibilityProviderBase
    {
        #region ISiteMapNodeVisibilityProvider Members

        /// <summary>
        /// Determines whether the node is visible.
        /// </summary>
        /// <param name="node">The node.</param>
        /// <param name="sourceMetadata">The source metadata.</param>
        /// <returns>
        ///     <c>true</c> if the specified node is visible; otherwise, <c>false</c>.
        /// </returns>
        public override bool IsVisible(ISiteMapNode node, IDictionary<string, object> sourceMetadata)
        {
            // Is a visibility attribute specified?
            string visibility = string.Empty;
            if (node.Attributes.ContainsKey("visibility"))
            {
                visibility = node.Attributes["visibility"].GetType().Equals(typeof(string)) ? node.Attributes["visibility"].ToString() : string.Empty;
            }
            if (string.IsNullOrEmpty(visibility))
            {
                return true;
            }
            visibility = visibility.Trim();

            string name = string.Empty;
            string htmlHelper = string.Empty;
            if (sourceMetadata.ContainsKey("name"))
            {
                name = Convert.ToString(sourceMetadata["name"]);
            }
            if (sourceMetadata.ContainsKey("HtmlHelper"))
            {
                name = Convert.ToString(sourceMetadata["HtmlHelper"]);
            }

            // Check for the source HtmlHelper or given name. If neither are configured,
            // then always visible.
            if (string.IsNullOrEmpty(name) && string.IsNullOrEmpty(htmlHelper))
            {
                return true;
            }

            // Chop off the namespace
            htmlHelper = htmlHelper.Substring(htmlHelper.LastIndexOf(".") + 1);

            // Get the keywords
            var visibilityKeywords = visibility.Split(new[] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries);

            // All set. Now parse the visibility variable.
            foreach (string visibilityKeyword in visibilityKeywords)
            {
                if (visibilityKeyword == htmlHelper || visibilityKeyword == name || visibilityKeyword == "*")
                {
                    return true;
                }
                else if (visibilityKeyword == "!" + htmlHelper || visibilityKeyword == "!" + name || visibilityKeyword == "!*")
                {
                    return false;
                }
            }

            // Still nothing? Then it's OK!
            return true;
        }

        #endregion
    }
}

@AdmiringWorm
Copy link

Hey NightOwl, I did a quick pre-eliminary test on the new FilteredSiteMapNodeVisibilityProvider you posted.
What I noticed was that it wasn't working as I was expecting, sure it fixed the problem.
But I was using the visibility attribute to hide nodes from the 'Menu' on the site, and this was not happening.
What I had to do then was either:

  1. Include the namespace for the MenuHelper in the visibility attribute. or
  2. Chop off the namespace for the 'name' string just as you do with the 'htmlHelper' string in the new FilteredSiteMapNodeVisibilityProvider.

@NightOwl888
Copy link
Collaborator

Try this instead. The name variable was being overwritten by the HtmlHelper value.

using System;
using System.Collections.Generic;
using System.Web;

namespace MvcSiteMapProvider
{
    /// <summary>
    /// Filtered SiteMapNode Visibility Provider.
    /// 
    /// Rules are parsed left-to-right, first match wins. Asterisk can be used to match any control. Exclamation mark can be used to negate a match.
    /// </summary>
    public class FilteredSiteMapNodeVisibilityProvider
        : SiteMapNodeVisibilityProviderBase
    {
        #region ISiteMapNodeVisibilityProvider Members

        /// <summary>
        /// Determines whether the node is visible.
        /// </summary>
        /// <param name="node">The node.</param>
        /// <param name="sourceMetadata">The source metadata.</param>
        /// <returns>
        ///     <c>true</c> if the specified node is visible; otherwise, <c>false</c>.
        /// </returns>
        public override bool IsVisible(ISiteMapNode node, IDictionary<string, object> sourceMetadata)
        {
            // Is a visibility attribute specified?
            string visibility = string.Empty;
            if (node.Attributes.ContainsKey("visibility"))
            {
                visibility = node.Attributes["visibility"].GetType().Equals(typeof(string)) ? node.Attributes["visibility"].ToString() : string.Empty;
            }
            if (string.IsNullOrEmpty(visibility))
            {
                return true;
            }
            visibility = visibility.Trim();

            string name = string.Empty;
            string htmlHelper = string.Empty;
            if (sourceMetadata.ContainsKey("name"))
            {
                name = Convert.ToString(sourceMetadata["name"]);
            }
            if (sourceMetadata.ContainsKey("HtmlHelper"))
            {
                htmlHelper = Convert.ToString(sourceMetadata["HtmlHelper"]);
            }

            // Check for the source HtmlHelper or given name. If neither are configured,
            // then always visible.
            if (string.IsNullOrEmpty(name) && string.IsNullOrEmpty(htmlHelper))
            {
                return true;
            }

            // Chop off the namespace
            htmlHelper = htmlHelper.Substring(htmlHelper.LastIndexOf(".") + 1);

            // Get the keywords
            var visibilityKeywords = visibility.Split(new[] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries);

            // All set. Now parse the visibility variable.
            foreach (string visibilityKeyword in visibilityKeywords)
            {
                if (visibilityKeyword == htmlHelper || visibilityKeyword == name || visibilityKeyword == "*")
                {
                    return true;
                }
                else if (visibilityKeyword == "!" + htmlHelper || visibilityKeyword == "!" + name || visibilityKeyword == "!*")
                {
                    return false;
                }
            }

            // Still nothing? Then it's OK!
            return true;
        }

        #endregion
    }
}

@AdmiringWorm
Copy link

ah, I see. I didn't notice that.
It does seem to work as it should, at least for my current project.

@vietnamtv
Copy link

I got the same problem with the version 4.6.17 with MVC 5.2.2. Could you please check it again? Thanks!

@NightOwl888
Copy link
Collaborator

Could you post the stack trace and your node configuration?

@vietnamtv
Copy link

a part of stack trace:
System.Collections.Generic.KeyNotFoundException: The given key was not present in the dictionary.
at System.Collections.Generic.Dictionary2.get_Item(TKey key) at MvcSiteMapProvider.Collections.CacheableDictionary2.get_Item(TKey key)
at MvcSiteMapProvider.Collections.Specialized.AttributeDictionary.get_Item(String key)
at Test.MVC5.Helpers.CustomSiteMapVisibilityProvider.IsVisible(ISiteMapNode node, IDictionary2 sourceMetadata) in c:\Test.MVC5\Helpers\CustomSiteMapVisibilityProvider.cs:line 15 at MvcSiteMapProvider.SiteMapNodeVisibilityProviderStrategy.IsVisible(String providerName, ISiteMapNode node, IDictionary2 sourceMetadata)
at MvcSiteMapProvider.SiteMapNode.IsVisible(IDictionary2 sourceMetadata) at MvcSiteMapProvider.RequestCacheableSiteMapNode.IsVisible(IDictionary2 sourceMetadata)
at MvcSiteMapProvider.Web.Html.SiteMapPathHelper.BuildModel(MvcSiteMapHtmlHelper helper, SourceMetadataDictionary sourceMetadata, ISiteMapNode startingNode)
at MvcSiteMapProvider.Web.Html.SiteMapPathHelper.SiteMapPath(MvcSiteMapHtmlHelper helper, String templateName, ISiteMapNode startingNode, SourceMetadataDictionary sourceMetadata)
at MvcSiteMapProvider.Web.Html.SiteMapPathHelper.SiteMapPath(MvcSiteMapHtmlHelper helper, String templateName, String startingNodeKey, SourceMetadataDictionary sourceMetadata)
at MvcSiteMapProvider.Web.Html.SiteMapPathHelper.SiteMapPath(MvcSiteMapHtmlHelper helper, String templateName, String startingNodeKey)
at MvcSiteMapProvider.Web.Html.SiteMapPathHelper.SiteMapPath(MvcSiteMapHtmlHelper helper)
at ASP._Page_Views_Shared__Layout_cshtml.Execute()

Node Config:
<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="$resources:Main,Label_Menu_Default" resourceKey="navigation" clickable ="false" visibility="Condition2">
<mvcSiteMapNode title="$resources:Main,Label_Menu_DevTest" resourceKey="devtest" clickable ="false" visibility="Condition2"></mvcSiteMapNode>
<mvcSiteMapNode title="$resources:Main,Label_Menu_UserMgmt" resourceKey="usermgmt" clickable ="false" visibility="Condition2">
<mvcSiteMapNode title="$resources:Main,Label_Menu_UserMgmt_Superuser_New" resourceKey="usermgmt.superusernew" controller="Admin" action="New" visibility="Condition1"/>
<mvcSiteMapNode title="$resources:Main,Label_Menu_UserMgmt_Superuser_Edit" resourceKey="usermgmt.superuseredit" controller="Admin" action="Edit" visibility="Condition1"/>
</mvcSiteMapNode>
</mvcSiteMapNode>
</mvcSiteMap>

my CustomSiteMapVisibilityProvider:
public class CustomSiteMapVisibilityProvider : SiteMapNodeVisibilityProviderBase
{
public override bool IsVisible(ISiteMapNode node, IDictionary<string, object> sourceMetadata)
{
// Is a visibility attribute specified?
var visibility = (String) node.Attributes["visibility"];
if (string.IsNullOrEmpty(visibility))
{
return true;
}
visibility = visibility.Trim();
...}

@NightOwl888
Copy link
Collaborator

Since attributes is an IDictionary<string, object>, you must always check whether an attribute exists using the ContainsKey method before attempting to retrieve it. Here is how the visibility attribute is retrieved in the original source:

// Is a visibility attribute specified?
            string visibility = string.Empty;
            if (node.Attributes.ContainsKey("visibility"))
            {
                visibility = node.Attributes["visibility"].GetType().Equals(typeof(string)) ? node.Attributes["visibility"].ToString() : string.Empty;
            }
            if (string.IsNullOrEmpty(visibility))
            {
                return true;
            }
            visibility = visibility.Trim();

@vietnamtv
Copy link

thanks. it works now!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants