MvcSiteMapProvider icon indicating copy to clipboard operation
MvcSiteMapProvider copied to clipboard

Problem refershing dynamic nodes

Open SharadaNagaraj opened this issue 8 years ago • 1 comments

I'm working on a MVC ecommerce website with Autofac handling my DI and EF with unit of work for the database calls. I'm using MvcSitemapProvider 4.4.5 and configured it to use external DI. I have the dynamic nodes loaded from the database and have set cache expiry to 10 mins. When the site loads first time, the nodes are populated fine but when cache tries to refresh the nodes every 10 mins, the repository seems to lose the unit of work instance. I'm unable to correctly configure DI and repository pattern outside the controller. Can you please help me with this?

My Dynamic provider

 public class RecursiveDynamicNodeProvider : DynamicNodeProviderBase
    {
        private IWebCatalogService _webCatalogService
        {
            get
            {
                return DependencyResolver.Current.GetService<IWebCategoriesService>();
            } 
        }
         /// <summary>
        /// Gets the dynamic node collection.
        /// </summary>
        /// <param name="node">The current node.</param>
        /// <returns>
        /// A dynamic node collection represented as a <see cref="IEnumerable&lt;MvcSiteMapProvider.Extensibility.DynamicNode&gt;"/> instance 
        /// </returns>
        public override IEnumerable<DynamicNode> GetDynamicNodeCollection(ISiteMapNode node)
        {
            //Fetch all the Collection/Categories
           var collections = this._webCatalogService.GetProductLineTree(node.Key);
           var nodeCollection =
                 collections.Select(
                     catg =>
                         new DynamicNode
                         {
                             Key = string.Format("{0}_{1}", node.Key, catg.web_category_code),
                             Title = catg.web_category_name,
                             ImageUrl =
                                 string.IsNullOrEmpty(catg.menu_image_url)
                                     ? string.Empty
                                     : string.Format("/content/images/{0}", catg.menu_image_url),
                             RouteValues =
                                 new Dictionary<string, object>
                                 {
                                     { "id", catg.web_category_code },
                                     { "slug", GetPath(catg, collections)}
                                 }
                         }).ToList();
            return nodeCollection;
        }

My Service and repository

public partial class WebCatalogService : EntityService<web_categories, IWebCatlogRepository>, IWebCatalogService
    {
        public IList<web_categories> GetProductLineTree(string rootProductLine)
        {
            return Repository.GetProductLineTree(rootProductLine, null);
        }
    }

public partial class WebCatlogRepository: EntityFrameworkRepository<web_catalog>, IWebCatlogRepository
    {
        public WebCatlogRepository(UnitOfWorkManager unitOfWorkManager)
            : base(unitOfWorkManager)
            {
            }

         public IList<web_categories> GetProductLineTree(string rootCategory, int? levels)
        {
            return ExecuteStoredProcedure<web_categories>("[Web.GetWebCategories]",
                new SqlParameter("@rootCategory", string.IsNullOrEmpty(rootCategory) ? SqlString.Null : rootCategory),
                new SqlParameter("@levels", levels.HasValue ? (Int16)levels : SqlInt16.Null))
                .ToList();
        }
    }

protected DbRawSqlQuery<T> ExecuteStoredProcedure<T>(string name, params SqlParameter[] parameters)
        {
            var sprocSql = new StringBuilder();
            sprocSql.Append(name);
            sprocSql.Append(' ');
            foreach (var sqlParameter in parameters)
            {
                sprocSql.Append(sqlParameter.ParameterName);
                sprocSql.Append(',');
            }

            if (sprocSql.ToString().EndsWith(","))
            {
                sprocSql = sprocSql.Remove(sprocSql.Length - 1, 1);
            }

            return UnitOfWork<EntityFrameworkUnitOfWork>() // --> erroring out here since unitwork.getsession is null
                .GetSession<TEntity>()
                .Database.SqlQuery<T>(sprocSql.ToString(), parameters);
        }

My mvc.sitemap

 <mvcSiteMapNode key="Key_Home" title="$resources:SiteMapLocalizations,HomeTitle"  controller="Home" action="Index" imageUrl="$resources:SiteMapLocalizations,HomeImageUrl">

        <mvcSiteMapNode key="Key_Catalog" controller="Store" action="Catalog" menu="leftMenu" visibilityProvider="Store.Web.Infrastructure.MenuVisibilityProvider, Store.Web">
          <mvcSiteMapNode action="ProductLine1"   key="Key_ProductLine1" dynamicNodeProvider="Store.Web.Infrastructure.RecursiveDynamicNodeProvider,Store.Web" parent="Key_Catalog"/>
          </mvcSiteMapNode>

my Global.asax

 public class MvcApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();

            WebApiConfig.Register(GlobalConfiguration.Configuration);
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            BundleConfig.RegisterBundles(BundleTable.Bundles);

            var bs = new Bootstrapper();
            bs.AddModule(new EntityFrameworkBootstrapperModule());
            bs.AddModule(new DataAccessBootstrapperModule(System.Configuration.ConfigurationManager.ConnectionStrings["Store.ConnectionString"].ConnectionString));
            bs.AddModule(new ServiceBootstrapperModule());
            bs.AddModule(new WebBootstrapperModule());
            bs.AddModule(new MvcSiteMapProviderModule());
            var container = bs.Initialize(BootstrapperEnvironmentType.Web);

            MvcSiteMapProviderConfig.Register(container);

        }
    }

My MvcSiteMapProviderConfig

 public class MvcSiteMapProviderConfig
    {
        public static void Register(IContainer container)
        {
            // Setup global sitemap loader (required)
            MvcSiteMapProvider.SiteMaps.Loader = container.Resolve<ISiteMapLoader>();

            // Check all configured .sitemap files to ensure they follow the XSD for MvcSiteMapProvider (optional)
            var validator = container.Resolve<ISiteMapXmlValidator>();
            validator.ValidateXml(HostingEnvironment.MapPath("~/Mvc.sitemap"));

            // Register the Sitemaps routes for search engines (optional)
            XmlSiteMapController.RegisterRoutes(RouteTable.Routes);
        }
    }

my web.config

 <appSettings>
    <add key="MvcSiteMapProvider_IncludeAssembliesForScan" value="Store.Web" />
    <add key="MvcSiteMapProvider_UseExternalDIContainer" value="true" />
    <add key="MvcSiteMapProvider_ScanAssembliesForSiteMapNodes" value="true" />
  </appSettings>

SharadaNagaraj avatar Mar 08 '16 20:03 SharadaNagaraj

Without seeing the code that creates the session, I don't know how much help I can be. You are clearly not using the same UnitOfWork pattern from this tutorial.

What I can tell you is that the dynamic node providers are instantiated at application startup and remain in memory until the application recycles. But since you are creating the IWebCategoriesService on the fly, this probably isn't a problem for your configuration. But then, you haven't posted the implementation of IWebCategoriesService, either, so I would say you are probably not looking in the right place.

NightOwl888 avatar Mar 12 '16 08:03 NightOwl888