/// <summary> /// Handles the OnSave event of the masterPage control. /// </summary> /// <param name="sender">The source of the event.</param> /// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param> protected void masterPage_OnSave(object sender, EventArgs e) { Page.Validate(BlockValidationGroup); if (Page.IsValid && _pageId.HasValue) { var rockContext = new RockContext(); var pageService = new PageService(rockContext); var routeService = new PageRouteService(rockContext); var contextService = new PageContextService(rockContext); var page = pageService.Get(_pageId.Value); // validate/check for removed routes var editorRoutes = tbPageRoute.Text.SplitDelimitedValues().Distinct(); var databasePageRoutes = page.PageRoutes.ToList(); var deletedRouteIds = new List <int>(); var addedRoutes = new List <string>(); if (editorRoutes.Any()) { int?siteId = null; if (page != null && page.Layout != null) { siteId = page.Layout.SiteId; } // validate for any duplicate routes var duplicateRouteQry = routeService.Queryable() .Where(r => r.PageId != _pageId && editorRoutes.Contains(r.Route)); if (siteId.HasValue) { duplicateRouteQry = duplicateRouteQry .Where(r => r.Page != null && r.Page.Layout != null && r.Page.Layout.SiteId == siteId.Value); } var duplicateRoutes = duplicateRouteQry .Select(r => r.Route) .Distinct() .ToList(); if (duplicateRoutes.Any()) { // Duplicate routes nbPageRouteWarning.Title = "Duplicate Route(s)"; nbPageRouteWarning.Text = string.Format("<p>The page route <strong>{0}</strong>, already exists for another page in the same site. Please choose a different route name.</p>", duplicateRoutes.AsDelimited("</strong> and <strong>")); nbPageRouteWarning.Dismissable = true; nbPageRouteWarning.Visible = true; CurrentTab = "Advanced Settings"; rptProperties.DataSource = _tabs; rptProperties.DataBind(); ShowSelectedPane(); return; } } // validate if removed routes can be deleted foreach (var pageRoute in databasePageRoutes) { if (!editorRoutes.Contains(pageRoute.Route)) { // make sure the route can be deleted string errorMessage; if (!routeService.CanDelete(pageRoute, out errorMessage)) { nbPageRouteWarning.Text = string.Format("The page route <strong>{0}</strong>, cannot be removed. {1}", pageRoute.Route, errorMessage); nbPageRouteWarning.NotificationBoxType = NotificationBoxType.Warning; nbPageRouteWarning.Dismissable = true; nbPageRouteWarning.Visible = true; CurrentTab = "Advanced Settings"; rptProperties.DataSource = _tabs; rptProperties.DataBind(); ShowSelectedPane(); return; } } } // take care of deleted routes foreach (var pageRoute in databasePageRoutes) { if (!editorRoutes.Contains(pageRoute.Route)) { // if they removed the Route, remove it from the database page.PageRoutes.Remove(pageRoute); routeService.Delete(pageRoute); deletedRouteIds.Add(pageRoute.Id); } } // take care of added routes foreach (string route in editorRoutes) { // if they added the Route, add it to the database if (!databasePageRoutes.Any(a => a.Route == route)) { var pageRoute = new PageRoute(); pageRoute.Route = route.TrimStart(new char[] { '/' }); pageRoute.Guid = Guid.NewGuid(); page.PageRoutes.Add(pageRoute); addedRoutes.Add(route); } } int parentPageId = ppParentPage.SelectedValueAsInt() ?? 0; if (page.ParentPageId != parentPageId) { if (page.ParentPageId.HasValue) { PageCache.Flush(page.ParentPageId.Value); } if (parentPageId != 0) { PageCache.Flush(parentPageId); } } page.InternalName = tbPageName.Text; page.PageTitle = tbPageTitle.Text; page.BrowserTitle = tbBrowserTitle.Text; page.BodyCssClass = tbBodyCssClass.Text; if (parentPageId != 0) { page.ParentPageId = parentPageId; } else { page.ParentPageId = null; } page.LayoutId = ddlLayout.SelectedValueAsInt().Value; int?orphanedIconFileId = null; page.IconCssClass = tbIconCssClass.Text; page.PageDisplayTitle = cbPageTitle.Checked; page.PageDisplayBreadCrumb = cbPageBreadCrumb.Checked; page.PageDisplayIcon = cbPageIcon.Checked; page.PageDisplayDescription = cbPageDescription.Checked; page.DisplayInNavWhen = ddlMenuWhen.SelectedValue.ConvertToEnumOrNull <DisplayInNavWhen>() ?? DisplayInNavWhen.WhenAllowed; page.MenuDisplayDescription = cbMenuDescription.Checked; page.MenuDisplayIcon = cbMenuIcon.Checked; page.MenuDisplayChildPages = cbMenuChildPages.Checked; page.BreadCrumbDisplayName = cbBreadCrumbName.Checked; page.BreadCrumbDisplayIcon = cbBreadCrumbIcon.Checked; page.RequiresEncryption = cbRequiresEncryption.Checked; page.EnableViewState = cbEnableViewState.Checked; page.IncludeAdminFooter = cbIncludeAdminFooter.Checked; page.AllowIndexing = cbAllowIndexing.Checked; page.OutputCacheDuration = tbCacheDuration.Text.AsIntegerOrNull() ?? 0; page.Description = tbDescription.Text; page.HeaderContent = ceHeaderContent.Text; // update PageContexts foreach (var pageContext in page.PageContexts.ToList()) { contextService.Delete(pageContext); } page.PageContexts.Clear(); foreach (var control in phContext.Controls) { if (control is RockTextBox) { var tbContext = control as RockTextBox; if (!string.IsNullOrWhiteSpace(tbContext.Text)) { var pageContext = new PageContext(); pageContext.Entity = tbContext.ID.Substring(8).Replace('_', '.'); pageContext.IdParameter = tbContext.Text; page.PageContexts.Add(pageContext); } } } // save page and it's routes if (page.IsValid) { rockContext.SaveChanges(); // remove any routes for this page that are no longer configured foreach (var existingRoute in RouteTable.Routes.OfType <Route>().Where(a => a.PageIds().Contains(page.Id))) { if (!editorRoutes.Any(a => a == existingRoute.Url)) { var pageAndRouteIds = existingRoute.DataTokens["PageRoutes"] as List <Rock.Web.PageAndRouteId>; pageAndRouteIds = pageAndRouteIds.Where(p => p.PageId != page.Id).ToList(); if (pageAndRouteIds.Any()) { existingRoute.DataTokens["PageRoutes"] = pageAndRouteIds; } else { RouteTable.Routes.Remove(existingRoute); } } } // Add any routes that were added foreach (var pageRoute in new PageRouteService(rockContext).GetByPageId(page.Id)) { if (addedRoutes.Contains(pageRoute.Route)) { var pageAndRouteId = new Rock.Web.PageAndRouteId { PageId = pageRoute.PageId, RouteId = pageRoute.Id }; var existingRoute = RouteTable.Routes.OfType <Route>().FirstOrDefault(r => r.Url == pageRoute.Route); if (existingRoute != null) { var pageAndRouteIds = existingRoute.DataTokens["PageRoutes"] as List <Rock.Web.PageAndRouteId>; pageAndRouteIds.Add(pageAndRouteId); existingRoute.DataTokens["PageRoutes"] = pageAndRouteIds; } else { var pageAndRouteIds = new List <Rock.Web.PageAndRouteId>(); pageAndRouteIds.Add(pageAndRouteId); RouteTable.Routes.AddPageRoute(pageRoute.Route, pageAndRouteIds); } } } if (orphanedIconFileId.HasValue) { BinaryFileService binaryFileService = new BinaryFileService(rockContext); var binaryFile = binaryFileService.Get(orphanedIconFileId.Value); if (binaryFile != null) { // marked the old images as IsTemporary so they will get cleaned up later binaryFile.IsTemporary = true; rockContext.SaveChanges(); } } Rock.Web.Cache.PageCache.Flush(page.Id); string script = "if (typeof window.parent.Rock.controls.modal.close === 'function') window.parent.Rock.controls.modal.close('PAGE_UPDATED');"; ScriptManager.RegisterStartupScript(this.Page, this.GetType(), "close-modal", script, true); } } }
/// <summary> /// Handles the Click event of the btnSave control. /// </summary> /// <param name="sender">The source of the event.</param> /// <param name="e">The <see cref="EventArgs" /> instance containing the event data.</param> protected void btnSave_Click(object sender, EventArgs e) { PageRoute pageRoute; var rockContext = new RockContext(); PageRouteService pageRouteService = new PageRouteService(rockContext); int pageRouteId = int.Parse(hfPageRouteId.Value); if (pageRouteId == 0) { pageRoute = new PageRoute(); pageRouteService.Add(pageRoute); } else { pageRoute = pageRouteService.Get(pageRouteId); } pageRoute.Route = tbRoute.Text.Trim(); int selectedPageId = int.Parse(ppPage.SelectedValue); pageRoute.PageId = selectedPageId; if (!pageRoute.IsValid) { // Controls will render the error messages return; } int?siteId = null; var pageCache = PageCache.Read(selectedPageId); if (pageCache != null && pageCache.Layout != null) { siteId = pageCache.Layout.SiteId; } var duplicateRoutes = pageRouteService .Queryable().AsNoTracking() .Where(r => r.Route == pageRoute.Route && r.Id != pageRoute.Id); if (siteId.HasValue) { duplicateRoutes = duplicateRoutes .Where(r => r.Page != null && r.Page.Layout != null && r.Page.Layout.SiteId == siteId.Value); } if (duplicateRoutes.Any()) { // Duplicate nbErrorMessage.Title = "Duplicate Route"; nbErrorMessage.Text = "<p>There is already an existing route with this name for the selected page's site. Route names must be unique per site. Please choose a different route name.</p>"; nbErrorMessage.Visible = true; } else { rockContext.SaveChanges(); // Remove previous route var oldRoute = RouteTable.Routes.OfType <Route>().FirstOrDefault(a => a.RouteIds().Contains(pageRoute.Id)); if (oldRoute != null) { var pageAndRouteIds = oldRoute.DataTokens["PageRoutes"] as List <Rock.Web.PageAndRouteId>; pageAndRouteIds = pageAndRouteIds.Where(p => p.RouteId != pageRoute.Id).ToList(); if (pageAndRouteIds.Any()) { oldRoute.DataTokens["PageRoutes"] = pageAndRouteIds; } else { RouteTable.Routes.Remove(oldRoute); } } // Remove the '{shortlink}' route (will be added back after specific routes) var shortLinkRoute = RouteTable.Routes.OfType <Route>().Where(r => r.Url == "{shortlink}").FirstOrDefault(); if (shortLinkRoute != null) { RouteTable.Routes.Remove(shortLinkRoute); } // Add new route var pageAndRouteId = new Rock.Web.PageAndRouteId { PageId = pageRoute.PageId, RouteId = pageRoute.Id }; var existingRoute = RouteTable.Routes.OfType <Route>().FirstOrDefault(r => r.Url == pageRoute.Route); if (existingRoute != null) { var pageAndRouteIds = existingRoute.DataTokens["PageRoutes"] as List <Rock.Web.PageAndRouteId>; pageAndRouteIds.Add(pageAndRouteId); existingRoute.DataTokens["PageRoutes"] = pageAndRouteIds; } else { var pageAndRouteIds = new List <Rock.Web.PageAndRouteId>(); pageAndRouteIds.Add(pageAndRouteId); RouteTable.Routes.AddPageRoute(pageRoute.Route, pageAndRouteIds); } RouteTable.Routes.Add(new Route("{shortlink}", new Rock.Web.RockRouteHandler())); NavigateToParentPage(); } }
/// <summary> /// Adds the page route. If the route name already exists then the PageAndRouteId obj will be added to the DataTokens "PageRoutes" List. /// </summary> /// <param name="routes">The routes.</param> /// <param name="routeName">Name of the route.</param> /// <param name="pageAndRouteId">The page and route identifier.</param> public static void AddPageRoute(this Collection <RouteBase> routes, string routeName, Rock.Web.PageAndRouteId pageAndRouteId) { Route route; List <Route> filteredRoutes = new List <Route>(); // The list of Route Names being used is case sensitive but IIS's usage of them is not. This can cause problems when the same // route name is used on different Rock sites. In order for the correct route to be selected they must be group together. // So we need to check if an existing route has been created first and then add to the data tokens if it has. foreach (var rb in routes) { // Make sure this is a route if (rb.GetType() == typeof(Route)) { Route r = rb as Route; filteredRoutes.Add(r); } } if (filteredRoutes.Where(r => string.Compare(r.Url, routeName, true) == 0).Any()) { route = filteredRoutes.Where(r => string.Compare(r.Url, routeName, true) == 0).First(); var pageRoutes = (List <Rock.Web.PageAndRouteId>)route.DataTokens["PageRoutes"]; if (pageRoutes == null) { route.DataTokens.Add("PageRoutes", pageAndRouteId); } else { pageRoutes.Add(pageAndRouteId); } } else { var pageRoutes = new List <Rock.Web.PageAndRouteId>(); pageRoutes.Add(pageAndRouteId); route = new Route(routeName, new Rock.Web.RockRouteHandler()); route.DataTokens = new RouteValueDictionary(); route.DataTokens.Add("RouteName", routeName); route.DataTokens.Add("PageRoutes", pageRoutes); routes.Add(route); } }
/// <summary> /// Handles the OnSave event of the masterPage control. /// </summary> /// <param name="sender">The source of the event.</param> /// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param> protected void masterPage_OnSave( object sender, EventArgs e ) { Page.Validate( BlockValidationGroup ); if ( Page.IsValid && _pageId.HasValue ) { var rockContext = new RockContext(); var pageService = new PageService( rockContext ); var routeService = new PageRouteService( rockContext ); var contextService = new PageContextService( rockContext ); var page = pageService.Get( _pageId.Value ); // validate/check for removed routes var editorRoutes = tbPageRoute.Text.SplitDelimitedValues().Distinct(); var databasePageRoutes = page.PageRoutes.ToList(); var deletedRouteIds = new List<int>(); var addedRoutes = new List<string>(); if ( editorRoutes.Any() ) { int? siteId = null; if ( page != null && page.Layout != null ) { siteId = page.Layout.SiteId; } // validate for any duplicate routes var duplicateRouteQry = routeService.Queryable() .Where( r => r.PageId != _pageId && editorRoutes.Contains( r.Route ) ); if ( siteId.HasValue ) { duplicateRouteQry = duplicateRouteQry .Where( r => r.Page != null && r.Page.Layout != null && r.Page.Layout.SiteId == siteId.Value ); } var duplicateRoutes = duplicateRouteQry .Select( r => r.Route ) .Distinct() .ToList(); if ( duplicateRoutes.Any() ) { // Duplicate routes nbPageRouteWarning.Title = "Duplicate Route(s)"; nbPageRouteWarning.Text = string.Format( "<p>The page route <strong>{0}</strong>, already exists for another page in the same site. Please choose a different route name.</p>", duplicateRoutes.AsDelimited( "</strong> and <strong>" ) ); nbPageRouteWarning.Dismissable = true; nbPageRouteWarning.Visible = true; CurrentTab = "Advanced Settings"; rptProperties.DataSource = _tabs; rptProperties.DataBind(); ShowSelectedPane(); return; } } // validate if removed routes can be deleted foreach ( var pageRoute in databasePageRoutes ) { if ( !editorRoutes.Contains( pageRoute.Route ) ) { // make sure the route can be deleted string errorMessage; if ( !routeService.CanDelete( pageRoute, out errorMessage ) ) { nbPageRouteWarning.Text = string.Format( "The page route <strong>{0}</strong>, cannot be removed. {1}", pageRoute.Route, errorMessage ); nbPageRouteWarning.NotificationBoxType = NotificationBoxType.Warning; nbPageRouteWarning.Dismissable = true; nbPageRouteWarning.Visible = true; CurrentTab = "Advanced Settings"; rptProperties.DataSource = _tabs; rptProperties.DataBind(); ShowSelectedPane(); return; } } } // take care of deleted routes foreach ( var pageRoute in databasePageRoutes ) { if ( !editorRoutes.Contains( pageRoute.Route ) ) { // if they removed the Route, remove it from the database page.PageRoutes.Remove( pageRoute ); routeService.Delete( pageRoute ); deletedRouteIds.Add( pageRoute.Id ); } } // take care of added routes foreach ( string route in editorRoutes ) { // if they added the Route, add it to the database if ( !databasePageRoutes.Any( a => a.Route == route ) ) { var pageRoute = new PageRoute(); pageRoute.Route = route.TrimStart( new char[] { '/' } ); pageRoute.Guid = Guid.NewGuid(); page.PageRoutes.Add( pageRoute ); addedRoutes.Add( route ); } } int parentPageId = ppParentPage.SelectedValueAsInt() ?? 0; if ( page.ParentPageId != parentPageId ) { if ( page.ParentPageId.HasValue ) { PageCache.Flush( page.ParentPageId.Value ); } if ( parentPageId != 0 ) { PageCache.Flush( parentPageId ); } } page.InternalName = tbPageName.Text; page.PageTitle = tbPageTitle.Text; page.BrowserTitle = tbBrowserTitle.Text; page.BodyCssClass = tbBodyCssClass.Text; if ( parentPageId != 0 ) { page.ParentPageId = parentPageId; } else { page.ParentPageId = null; } page.LayoutId = ddlLayout.SelectedValueAsInt().Value; int? orphanedIconFileId = null; page.IconCssClass = tbIconCssClass.Text; page.PageDisplayTitle = cbPageTitle.Checked; page.PageDisplayBreadCrumb = cbPageBreadCrumb.Checked; page.PageDisplayIcon = cbPageIcon.Checked; page.PageDisplayDescription = cbPageDescription.Checked; page.DisplayInNavWhen = ddlMenuWhen.SelectedValue.ConvertToEnumOrNull<DisplayInNavWhen>() ?? DisplayInNavWhen.WhenAllowed; page.MenuDisplayDescription = cbMenuDescription.Checked; page.MenuDisplayIcon = cbMenuIcon.Checked; page.MenuDisplayChildPages = cbMenuChildPages.Checked; page.BreadCrumbDisplayName = cbBreadCrumbName.Checked; page.BreadCrumbDisplayIcon = cbBreadCrumbIcon.Checked; page.RequiresEncryption = cbRequiresEncryption.Checked; page.EnableViewState = cbEnableViewState.Checked; page.IncludeAdminFooter = cbIncludeAdminFooter.Checked; page.AllowIndexing = cbAllowIndexing.Checked; page.OutputCacheDuration = tbCacheDuration.Text.AsIntegerOrNull() ?? 0; page.Description = tbDescription.Text; page.HeaderContent = ceHeaderContent.Text; // update PageContexts foreach ( var pageContext in page.PageContexts.ToList() ) { contextService.Delete( pageContext ); } page.PageContexts.Clear(); foreach ( var control in phContext.Controls ) { if ( control is RockTextBox ) { var tbContext = control as RockTextBox; if ( !string.IsNullOrWhiteSpace( tbContext.Text ) ) { var pageContext = new PageContext(); pageContext.Entity = tbContext.ID.Substring( 8 ).Replace( '_', '.' ); pageContext.IdParameter = tbContext.Text; page.PageContexts.Add( pageContext ); } } } // save page and it's routes if ( page.IsValid ) { rockContext.SaveChanges(); // remove any routes for this page that are no longer configured foreach (var existingRoute in RouteTable.Routes.OfType<Route>().Where(a => a.PageIds().Contains( page.Id) ) ) { if ( !editorRoutes.Any( a => a == existingRoute.Url ) ) { var pageAndRouteIds = existingRoute.DataTokens["PageRoutes"] as List<Rock.Web.PageAndRouteId>; pageAndRouteIds = pageAndRouteIds.Where( p => p.PageId != page.Id ).ToList(); if ( pageAndRouteIds.Any() ) { existingRoute.DataTokens["PageRoutes"] = pageAndRouteIds; } else { RouteTable.Routes.Remove( existingRoute ); } } } // Add any routes that were added foreach ( var pageRoute in new PageRouteService( rockContext ).GetByPageId( page.Id ) ) { if ( addedRoutes.Contains( pageRoute.Route ) ) { var pageAndRouteId = new Rock.Web.PageAndRouteId { PageId = pageRoute.PageId, RouteId = pageRoute.Id }; var existingRoute = RouteTable.Routes.OfType<Route>().FirstOrDefault( r => r.Url == pageRoute.Route ); if ( existingRoute != null ) { var pageAndRouteIds = existingRoute.DataTokens["PageRoutes"] as List<Rock.Web.PageAndRouteId>; pageAndRouteIds.Add( pageAndRouteId ); existingRoute.DataTokens["PageRoutes"] = pageAndRouteIds; } else { var pageAndRouteIds = new List<Rock.Web.PageAndRouteId>(); pageAndRouteIds.Add( pageAndRouteId ); RouteTable.Routes.AddPageRoute( pageRoute.Route, pageAndRouteIds ); } } } if ( orphanedIconFileId.HasValue ) { BinaryFileService binaryFileService = new BinaryFileService( rockContext ); var binaryFile = binaryFileService.Get( orphanedIconFileId.Value ); if ( binaryFile != null ) { // marked the old images as IsTemporary so they will get cleaned up later binaryFile.IsTemporary = true; rockContext.SaveChanges(); } } Rock.Web.Cache.PageCache.Flush( page.Id ); string script = "if (typeof window.parent.Rock.controls.modal.close === 'function') window.parent.Rock.controls.modal.close('PAGE_UPDATED');"; ScriptManager.RegisterStartupScript( this.Page, this.GetType(), "close-modal", script, true ); } } }
/// <summary> /// Adds the page route. If the route name already exists then the PageAndRouteId obj will be added to the DataTokens "PageRoutes" List. /// </summary> /// <param name="routes">The routes.</param> /// <param name="routeName">Name of the route.</param> /// <param name="pageAndRouteId">The page and route identifier.</param> public static void AddPageRoute(this Collection <RouteBase> routes, string routeName, Rock.Web.PageAndRouteId pageAndRouteId) { Route route; List <Route> filteredRoutes = new List <Route>(); // The list of Route Names being used is case sensitive but IIS's usage of them is not. This can cause problems when the same // route name is used on different Rock sites. In order for the correct route to be selected they must be group together. // So we need to check if an existing route has been created first and then add to the data tokens if it has. foreach (var rb in routes) { // Make sure this is a route if (rb.GetType() == typeof(Route)) { Route r = rb as Route; filteredRoutes.Add(r); } } /* * 2020-02-12 ETD * Rock needs to compare routes and group them for the domain matching logic. The same route on two or more different sites * need to be group together under one route, with each page-route having a DataToken in that route. Since IIS only matches * the static portion of the route Rock must also only match the static portion of the route. A simple string compare will * not work since a variable name can vary and create two different routes instead of one grouped route. e.g. one site * has entity/{id} and another has entity/{guid}. * * While the variable name is not significant their existance is. So before comparing two routes to see if they are * the same Rock will first remove the contents of the brackets. e.g. checkin/{KioskId}/{CheckinConfigId}/{GroupTypeIds} * is converted to checkin/{}/{}/{}. Then a case insensitive compare is done between the routes. This removes the * possible variations in variable names and allows the static contents to be compared and the routes grouped correctly. * * Note: Since grouped routes are under the same URL only the first one is displayed in RouteTable.Routes. * So in this example both pages are grouped under the route URL entity/{id}. In the UI they are * still displayed as seperate routes since it is two different PageRoute objects. * * This was done to resolve issue: https://github.com/SparkDevNetwork/Rock/issues/4102 */ var reg = new System.Text.RegularExpressions.Regex(@"\{.*?\}"); var routeNameReg = reg.Replace(routeName, "{}"); if (filteredRoutes.Where(r => string.Compare(reg.Replace(r.Url, "{}"), routeNameReg, true) == 0).Any()) { route = filteredRoutes.Where(r => string.Compare(reg.Replace(r.Url, "{}"), routeNameReg, true) == 0).First(); var pageRoutes = (List <Rock.Web.PageAndRouteId>)route.DataTokens["PageRoutes"]; if (pageRoutes == null) { route.DataTokens.Add("PageRoutes", pageAndRouteId); } else { pageRoutes.Add(pageAndRouteId); } } else { var pageRoutes = new List <Rock.Web.PageAndRouteId>(); pageRoutes.Add(pageAndRouteId); route = new Route(routeName, new Rock.Web.RockRouteHandler()); route.DataTokens = new RouteValueDictionary(); route.DataTokens.Add("RouteName", routeName); route.DataTokens.Add("PageRoutes", pageRoutes); routes.Add(route); } }
/// <summary> /// Handles the Click event of the btnSave control. /// </summary> /// <param name="sender">The source of the event.</param> /// <param name="e">The <see cref="EventArgs" /> instance containing the event data.</param> protected void btnSave_Click( object sender, EventArgs e ) { PageRoute pageRoute; var rockContext = new RockContext(); PageRouteService pageRouteService = new PageRouteService( rockContext ); int pageRouteId = int.Parse( hfPageRouteId.Value ); if ( pageRouteId == 0 ) { pageRoute = new PageRoute(); pageRouteService.Add( pageRoute ); } else { pageRoute = pageRouteService.Get( pageRouteId ); } pageRoute.Route = tbRoute.Text.Trim(); int selectedPageId = int.Parse( ppPage.SelectedValue ); pageRoute.PageId = selectedPageId; if ( !pageRoute.IsValid ) { // Controls will render the error messages return; } int? siteId = null; var pageCache = PageCache.Read( selectedPageId ); if ( pageCache != null && pageCache.Layout != null ) { siteId = pageCache.Layout.SiteId; } var duplicateRoutes = pageRouteService .Queryable().AsNoTracking() .Where( r => r.Route == pageRoute.Route && r.Id != pageRoute.Id ); if ( siteId.HasValue ) { duplicateRoutes = duplicateRoutes .Where( r => r.Page != null && r.Page.Layout != null && r.Page.Layout.SiteId == siteId.Value ); } if ( duplicateRoutes.Any() ) { // Duplicate nbErrorMessage.Title = "Duplicate Route"; nbErrorMessage.Text = "<p>There is already an existing route with this name for the selected page's site. Route names must be unique per site. Please choose a different route name.</p>"; nbErrorMessage.Visible = true; } else { rockContext.SaveChanges(); // Remove previous route var oldRoute = RouteTable.Routes.OfType<Route>().FirstOrDefault( a => a.RouteIds().Contains( pageRoute.Id ) ); if ( oldRoute != null ) { var pageAndRouteIds = oldRoute.DataTokens["PageRoutes"] as List<Rock.Web.PageAndRouteId>; pageAndRouteIds = pageAndRouteIds.Where( p => p.RouteId != pageRoute.Id ).ToList(); if ( pageAndRouteIds.Any() ) { oldRoute.DataTokens["PageRoutes"] = pageAndRouteIds; } else { RouteTable.Routes.Remove( oldRoute ); } } // Add new route var pageAndRouteId = new Rock.Web.PageAndRouteId { PageId = pageRoute.PageId, RouteId = pageRoute.Id }; var existingRoute = RouteTable.Routes.OfType<Route>().FirstOrDefault( r => r.Url == pageRoute.Route ); if ( existingRoute != null ) { var pageAndRouteIds = existingRoute.DataTokens["PageRoutes"] as List<Rock.Web.PageAndRouteId>; pageAndRouteIds.Add( pageAndRouteId ); existingRoute.DataTokens["PageRoutes"] = pageAndRouteIds; } else { var pageAndRouteIds = new List<Rock.Web.PageAndRouteId>(); pageAndRouteIds.Add( pageAndRouteId ); RouteTable.Routes.AddPageRoute( pageRoute.Route, pageAndRouteIds ); } NavigateToParentPage(); } }