├── .gitignore ├── DynamicRouting.Kentico.Base ├── Classes │ ├── Base │ │ ├── SlugGenerationQueueInfo.cs │ │ ├── SlugGenerationQueueInfoProvider.cs │ │ ├── UrlSlugInfo.cs │ │ ├── UrlSlugInfoProvider.cs │ │ ├── UrlSlugStagingTaskIgnoreInfo.cs │ │ └── UrlSlugStagingTaskIgnoreInfoProvider.cs │ ├── Models │ │ ├── NodeData.cs │ │ ├── NodeItemBuilderSetting.cs │ │ └── NodeUrlSlug.cs │ └── Other │ │ ├── DynamicRouteConfiguration.cs │ │ └── UrlSlugCollisionException.cs ├── DynamicRouting.Kentico.Base.csproj ├── Helpers │ ├── DynamicRouteEventHelper.cs │ ├── DynamicRouteInitializationModule.cs │ ├── DynamicRouteInternalHelper.cs │ └── EnvironmentHelper.cs ├── Overrides │ └── DocumentUrlProviderOverride.cs ├── Properties │ └── AssemblyInfo.cs ├── app.config └── packages.config ├── DynamicRouting.Kentico.MVC ├── BuildPackage.bat ├── ControllerExtensions.cs ├── DynamicHttpHandler.cs ├── DynamicRouteCachedController.cs ├── DynamicRouteConstraint.cs ├── DynamicRouteController.cs ├── DynamicRouteHandler.cs ├── DynamicRouteHelper.cs ├── DynamicRouteInitializationModule_MVC.cs ├── DynamicRouteTemplateController.cs ├── DynamicRouting.Kentico.MVC.csproj ├── DynamicRouting.Kentico.MVC.nuspec ├── DynamicRoutingAnalyzer.cs ├── DynamicRoutingAttribute.cs ├── EmptyPageTemplate.cs ├── EmptyPageTemplateFilter.cs ├── Events │ ├── DynamicUrlEvents.cs │ ├── GetCultureEventArgs.cs │ ├── GetCultureEventHandler.cs │ ├── GetPageEventArgs.cs │ ├── GetPageEventHandler.cs │ ├── RequestRoutingEventArgs.cs │ └── RequestRoutingEventHandler.cs ├── Implementations │ └── DynamicRouteHelper.cs ├── Interfaces │ └── IDynamicRouteHelper.cs ├── NoEmptyPageTemplateFilter.cs ├── Properties │ └── AssemblyInfo.cs ├── StaticRoutePriorityAttribute.cs ├── StaticRoutePriorityConstraint.cs ├── app.config └── packages.config ├── DynamicRouting.Kentico.Mother ├── App_Data │ └── Global │ │ └── Resources │ │ ├── Kentico.Builder.resx │ │ ├── Kentico.Components.resx │ │ ├── Kentico.FormBuilder.resx │ │ └── Kentico.PageBuilder.resx ├── BuildPackage.bat ├── DynamicRouteMacroMethods.cs ├── DynamicRouting.Kentico.csproj ├── DynamicRouting.nuspec ├── Helpers │ ├── DynamicRouteHelper.cs │ ├── DynamicRouteInitializationModule_Mother.cs │ ├── DynamicRouteScheduledTasks.cs │ └── SlugGenerationQueueUnigridExtension.cs ├── MacroMethodForDynamicRoutingMacroPageType.txt ├── Properties │ └── AssemblyInfo.cs ├── ShareableComponentBoilerplate.nuspec ├── Testing │ ├── QuickOperations.Designer.cs │ └── QuickOperations.cs ├── app.config └── packages.config ├── DynamicRouting.sln ├── DynamicRoutingStarterSite.zip ├── README.md ├── RenameProject.bat ├── Targets ├── Kentico.EmbeddedViews.targets └── Kentico.Resources.targets └── URLGenerator.zip /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | [Xx]64/ 19 | [Xx]86/ 20 | [Bb]uild/ 21 | bld/ 22 | [Bb]in/ 23 | [Oo]bj/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | artifacts/ 46 | 47 | *_i.c 48 | *_p.c 49 | *_i.h 50 | *.ilk 51 | *.meta 52 | *.obj 53 | *.pch 54 | *.pdb 55 | *.pgc 56 | *.pgd 57 | *.rsp 58 | *.sbr 59 | *.tlb 60 | *.tli 61 | *.tlh 62 | *.tmp 63 | *.tmp_proj 64 | *.log 65 | *.vspscc 66 | *.vssscc 67 | .builds 68 | *.pidb 69 | *.svclog 70 | *.scc 71 | 72 | # Chutzpah Test files 73 | _Chutzpah* 74 | 75 | # Visual C++ cache files 76 | ipch/ 77 | *.aps 78 | *.ncb 79 | *.opendb 80 | *.opensdf 81 | *.sdf 82 | *.cachefile 83 | *.VC.db 84 | 85 | # Visual Studio profiler 86 | *.psess 87 | *.vsp 88 | *.vspx 89 | *.sap 90 | 91 | # TFS 2012 Local Workspace 92 | $tf/ 93 | 94 | # Guidance Automation Toolkit 95 | *.gpState 96 | 97 | # ReSharper is a .NET coding add-in 98 | _ReSharper*/ 99 | *.[Rr]e[Ss]harper 100 | *.DotSettings.user 101 | 102 | # JustCode is a .NET coding add-in 103 | .JustCode 104 | 105 | # TeamCity is a build add-in 106 | _TeamCity* 107 | 108 | # DotCover is a Code Coverage Tool 109 | *.dotCover 110 | 111 | # NCrunch 112 | _NCrunch_* 113 | .*crunch*.local.xml 114 | nCrunchTemp_* 115 | 116 | # MightyMoose 117 | *.mm.* 118 | AutoTest.Net/ 119 | 120 | # Web workbench (sass) 121 | .sass-cache/ 122 | 123 | # Installshield output folder 124 | [Ee]xpress/ 125 | 126 | # DocProject is a documentation generator add-in 127 | DocProject/buildhelp/ 128 | DocProject/Help/*.HxT 129 | DocProject/Help/*.HxC 130 | DocProject/Help/*.hhc 131 | DocProject/Help/*.hhk 132 | DocProject/Help/*.hhp 133 | DocProject/Help/Html2 134 | DocProject/Help/html 135 | 136 | # Click-Once directory 137 | publish/ 138 | 139 | # Publish Web Output 140 | *.[Pp]ublish.xml 141 | *.azurePubxml 142 | # TODO: Un-comment the next line if you do not want to checkin 143 | # your web deploy settings because they may include unencrypted 144 | # passwords 145 | #*.pubxml 146 | 147 | *.publishproj 148 | 149 | # NuGet Packages 150 | *.nupkg 151 | # The packages folder can be ignored because of Package Restore 152 | **/packages/* 153 | # except build/, which is used as an MSBuild target. 154 | !**/packages/build/ 155 | # Uncomment if necessary however generally it will be regenerated when needed 156 | #!**/packages/repositories.config 157 | # NuGet v3's project.json files produces more ignoreable files 158 | *.nuget.props 159 | *.nuget.targets 160 | 161 | # Microsoft Azure Build Output 162 | csx/ 163 | *.build.csdef 164 | 165 | # Microsoft Azure Emulator 166 | ecf/ 167 | rcf/ 168 | # Microsoft Azure ApplicationInsights config file 169 | ApplicationInsights.config 170 | # Windows Store app package directory 171 | AppPackages/ 172 | BundleArtifacts/ 173 | 174 | # Visual Studio cache files 175 | # files ending in .cache can be ignored 176 | *.[Cc]ache 177 | # but keep track of directories ending in .cache 178 | !*.[Cc]ache/ 179 | 180 | # Others 181 | ClientBin/ 182 | [Ss]tyle[Cc]op.* 183 | ~$* 184 | *~ 185 | *.dbmdl 186 | *.dbproj.schemaview 187 | *.pfx 188 | *.publishsettings 189 | node_modules/ 190 | orleans.codegen.cs 191 | 192 | # RIA/Silverlight projects 193 | Generated_Code/ 194 | 195 | # Backup & report files from converting an old project file 196 | # to a newer Visual Studio version. Backup files are not needed, 197 | # because we have git ;-) 198 | _UpgradeReport_Files/ 199 | Backup*/ 200 | UpgradeLog*.XML 201 | UpgradeLog*.htm 202 | 203 | # SQL Server files 204 | *.mdf 205 | *.ldf 206 | 207 | # Business Intelligence projects 208 | *.rdl.data 209 | *.bim.layout 210 | *.bim_*.settings 211 | 212 | # Microsoft Fakes 213 | FakesAssemblies/ 214 | 215 | # GhostDoc plugin setting file 216 | *.GhostDoc.xml 217 | # Node.js Tools for Visual Studio 218 | .ntvs_analysis.dat 219 | 220 | # Visual Studio 6 build log 221 | *.plg 222 | 223 | # Visual Studio 6 workspace options file 224 | *.opt 225 | 226 | # Visual Studio LightSwitch build output 227 | **/*.HTMLClient/GeneratedArtifacts 228 | **/*.DesktopClient/GeneratedArtifacts 229 | **/*.DesktopClient/ModelManifest.xml 230 | **/*.Server/GeneratedArtifacts 231 | **/*.Server/ModelManifest.xml 232 | _Pvt_Extensions 233 | # LightSwitch generated files 234 | GeneratedArtifacts/ 235 | ModelManifest.xml 236 | 237 | # Paket dependency manager 238 | .paket/paket.exe 239 | 240 | # FAKE - F# Make 241 | .fake/ 242 | 243 | ## Windows ignores 244 | # Windows image file caches 245 | Thumbs.db 246 | ehthumbs.db 247 | 248 | # Folder config file 249 | Desktop.ini 250 | 251 | # Recycle Bin used on file shares 252 | $RECYCLE.BIN/ 253 | 254 | # Windows Installer files 255 | *.cab 256 | *.msi 257 | *.msm 258 | *.msp 259 | 260 | # Windows shortcuts 261 | *.lnk 262 | 263 | # Ignore all smart search indexes, but not the other system folder contents 264 | 265 | */obj/ 266 | 267 | * - Copy* 268 | *.bak 269 | *.cfs 270 | *.del 271 | *.exclude 272 | *.fdt 273 | *.fdt 274 | *.fdx 275 | *.fdx 276 | *.frq 277 | *.log 278 | *.nrm 279 | *.prx 280 | *.tii 281 | *.tis 282 | export*.zip 283 | /git-update.log.txt 284 | 285 | # Removing nuget build file 286 | 287 | #Include Packages folder since Rx-Core is no longer retreivable through nuget 288 | /Lib 289 | /Lib/*.* 290 | /DynamicRouting.Kentico.MVC/App_Data/* -------------------------------------------------------------------------------- /DynamicRouting.Kentico.Base/Classes/Base/SlugGenerationQueueInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data; 3 | using System.Runtime.Serialization; 4 | 5 | using CMS; 6 | using CMS.DataEngine; 7 | using CMS.Helpers; 8 | using DynamicRouting; 9 | 10 | [assembly: RegisterObjectType(typeof(SlugGenerationQueueInfo), SlugGenerationQueueInfo.OBJECT_TYPE)] 11 | 12 | namespace DynamicRouting 13 | { 14 | /// 15 | /// Data container class for . 16 | /// 17 | [Serializable] 18 | public partial class SlugGenerationQueueInfo : AbstractInfo 19 | { 20 | /// 21 | /// Object type. 22 | /// 23 | public const string OBJECT_TYPE = "dynamicrouting.sluggenerationqueue"; 24 | 25 | 26 | /// 27 | /// Type information. 28 | /// 29 | public static readonly ObjectTypeInfo TYPEINFO = new ObjectTypeInfo(typeof(SlugGenerationQueueInfoProvider), OBJECT_TYPE, "DynamicRouting.SlugGenerationQueue", "SlugGenerationQueueID", "SlugGenerationQueueLastModified", "SlugGenerationQueueGuid", null, null, null, null, null, null) 30 | { 31 | ModuleName = "DynamicRouting.Kentico", 32 | TouchCacheDependencies = true, 33 | SupportsCloning = false, 34 | AllowDataExport = false, 35 | AllowRestore = false, 36 | }; 37 | 38 | 39 | /// 40 | /// Slug generation queue ID. 41 | /// 42 | [DatabaseField] 43 | public virtual int SlugGenerationQueueID 44 | { 45 | get 46 | { 47 | return ValidationHelper.GetInteger(GetValue("SlugGenerationQueueID"), 0); 48 | } 49 | set 50 | { 51 | SetValue("SlugGenerationQueueID", value); 52 | } 53 | } 54 | 55 | 56 | /// 57 | /// Slug generation queue node item. 58 | /// 59 | [DatabaseField] 60 | public virtual string SlugGenerationQueueNodeItem 61 | { 62 | get 63 | { 64 | return ValidationHelper.GetString(GetValue("SlugGenerationQueueNodeItem"), String.Empty); 65 | } 66 | set 67 | { 68 | SetValue("SlugGenerationQueueNodeItem", value); 69 | } 70 | } 71 | 72 | 73 | /// 74 | /// If true, then it is currently being processed asyncly.. 75 | /// 76 | [DatabaseField] 77 | public virtual bool SlugGenerationQueueRunning 78 | { 79 | get 80 | { 81 | return ValidationHelper.GetBoolean(GetValue("SlugGenerationQueueRunning"), false); 82 | } 83 | set 84 | { 85 | SetValue("SlugGenerationQueueRunning", value); 86 | } 87 | } 88 | 89 | 90 | /// 91 | /// Thread ID of the thread running this. 92 | /// 93 | [DatabaseField] 94 | public virtual int SlugGenerationQueueThreadID 95 | { 96 | get 97 | { 98 | return ValidationHelper.GetInteger(GetValue("SlugGenerationQueueThreadID"), 0); 99 | } 100 | set 101 | { 102 | SetValue("SlugGenerationQueueThreadID", value, 0); 103 | } 104 | } 105 | 106 | 107 | /// 108 | /// The ID of the Application running this task, this is to ensure that other Applications (MVC Site vs. Mother) do not try to run tasks when the other is already running.. 109 | /// 110 | [DatabaseField] 111 | public virtual string SlugGenerationQueueApplicationID 112 | { 113 | get 114 | { 115 | return ValidationHelper.GetString(GetValue("SlugGenerationQueueApplicationID"), String.Empty); 116 | } 117 | set 118 | { 119 | SetValue("SlugGenerationQueueApplicationID", value, String.Empty); 120 | } 121 | } 122 | 123 | 124 | /// 125 | /// Errors that occurred while generating.. 126 | /// 127 | [DatabaseField] 128 | public virtual string SlugGenerationQueueErrors 129 | { 130 | get 131 | { 132 | return ValidationHelper.GetString(GetValue("SlugGenerationQueueErrors"), String.Empty); 133 | } 134 | set 135 | { 136 | SetValue("SlugGenerationQueueErrors", value, String.Empty); 137 | } 138 | } 139 | 140 | 141 | /// 142 | /// Slug generation queue started. 143 | /// 144 | [DatabaseField] 145 | public virtual DateTime SlugGenerationQueueStarted 146 | { 147 | get 148 | { 149 | return ValidationHelper.GetDateTime(GetValue("SlugGenerationQueueStarted"), DateTimeHelper.ZERO_TIME); 150 | } 151 | set 152 | { 153 | SetValue("SlugGenerationQueueStarted", value, DateTimeHelper.ZERO_TIME); 154 | } 155 | } 156 | 157 | 158 | /// 159 | /// If failed, what time it ended. 160 | /// 161 | [DatabaseField] 162 | public virtual DateTime SlugGenerationQueueEnded 163 | { 164 | get 165 | { 166 | return ValidationHelper.GetDateTime(GetValue("SlugGenerationQueueEnded"), DateTimeHelper.ZERO_TIME); 167 | } 168 | set 169 | { 170 | SetValue("SlugGenerationQueueEnded", value, DateTimeHelper.ZERO_TIME); 171 | } 172 | } 173 | 174 | 175 | /// 176 | /// Slug generation queue guid. 177 | /// 178 | [DatabaseField] 179 | public virtual Guid SlugGenerationQueueGuid 180 | { 181 | get 182 | { 183 | return ValidationHelper.GetGuid(GetValue("SlugGenerationQueueGuid"), Guid.Empty); 184 | } 185 | set 186 | { 187 | SetValue("SlugGenerationQueueGuid", value); 188 | } 189 | } 190 | 191 | 192 | /// 193 | /// Slug generation queue last modified. 194 | /// 195 | [DatabaseField] 196 | public virtual DateTime SlugGenerationQueueLastModified 197 | { 198 | get 199 | { 200 | return ValidationHelper.GetDateTime(GetValue("SlugGenerationQueueLastModified"), DateTimeHelper.ZERO_TIME); 201 | } 202 | set 203 | { 204 | SetValue("SlugGenerationQueueLastModified", value); 205 | } 206 | } 207 | 208 | 209 | /// 210 | /// Deletes the object using appropriate provider. 211 | /// 212 | protected override void DeleteObject() 213 | { 214 | SlugGenerationQueueInfoProvider.DeleteSlugGenerationQueueInfo(this); 215 | } 216 | 217 | 218 | /// 219 | /// Updates the object using appropriate provider. 220 | /// 221 | protected override void SetObject() 222 | { 223 | SlugGenerationQueueInfoProvider.SetSlugGenerationQueueInfo(this); 224 | } 225 | 226 | 227 | /// 228 | /// Constructor for de-serialization. 229 | /// 230 | /// Serialization info. 231 | /// Streaming context. 232 | protected SlugGenerationQueueInfo(SerializationInfo info, StreamingContext context) 233 | : base(info, context, TYPEINFO) 234 | { 235 | } 236 | 237 | 238 | /// 239 | /// Creates an empty instance of the class. 240 | /// 241 | public SlugGenerationQueueInfo() 242 | : base(TYPEINFO) 243 | { 244 | } 245 | 246 | 247 | /// 248 | /// Creates a new instances of the class from the given . 249 | /// 250 | /// DataRow with the object data. 251 | public SlugGenerationQueueInfo(DataRow dr) 252 | : base(TYPEINFO, dr) 253 | { 254 | } 255 | } 256 | } -------------------------------------------------------------------------------- /DynamicRouting.Kentico.Base/Classes/Base/SlugGenerationQueueInfoProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data; 3 | 4 | using CMS.Base; 5 | using CMS.DataEngine; 6 | using CMS.Helpers; 7 | 8 | namespace DynamicRouting 9 | { 10 | /// 11 | /// Class providing management. 12 | /// 13 | public partial class SlugGenerationQueueInfoProvider : AbstractInfoProvider 14 | { 15 | /// 16 | /// Creates an instance of . 17 | /// 18 | public SlugGenerationQueueInfoProvider() 19 | : base(SlugGenerationQueueInfo.TYPEINFO) 20 | { 21 | } 22 | 23 | 24 | /// 25 | /// Returns a query for all the objects. 26 | /// 27 | public static ObjectQuery GetSlugGenerationQueues() 28 | { 29 | return ProviderObject.GetObjectQuery(); 30 | } 31 | 32 | 33 | /// 34 | /// Returns with specified ID. 35 | /// 36 | /// ID. 37 | public static SlugGenerationQueueInfo GetSlugGenerationQueueInfo(int id) 38 | { 39 | return ProviderObject.GetInfoById(id); 40 | } 41 | 42 | 43 | /// 44 | /// Sets (updates or inserts) specified . 45 | /// 46 | /// to be set. 47 | public static void SetSlugGenerationQueueInfo(SlugGenerationQueueInfo infoObj) 48 | { 49 | // Set required field if not set. 50 | if(DataHelper.GetNull(infoObj.GetValue("SlugGenerationQueueRunning")) == null) { 51 | infoObj.SlugGenerationQueueRunning = false; 52 | } 53 | ProviderObject.SetInfo(infoObj); 54 | } 55 | 56 | 57 | /// 58 | /// Deletes specified . 59 | /// 60 | /// to be deleted. 61 | public static void DeleteSlugGenerationQueueInfo(SlugGenerationQueueInfo infoObj) 62 | { 63 | ProviderObject.DeleteInfo(infoObj); 64 | } 65 | 66 | 67 | /// 68 | /// Deletes with specified ID. 69 | /// 70 | /// ID. 71 | public static void DeleteSlugGenerationQueueInfo(int id) 72 | { 73 | SlugGenerationQueueInfo infoObj = GetSlugGenerationQueueInfo(id); 74 | DeleteSlugGenerationQueueInfo(infoObj); 75 | } 76 | } 77 | } -------------------------------------------------------------------------------- /DynamicRouting.Kentico.Base/Classes/Base/UrlSlugInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data; 3 | using System.Runtime.Serialization; 4 | using System.Collections.Generic; 5 | 6 | using CMS; 7 | using CMS.DataEngine; 8 | using CMS.Helpers; 9 | using DynamicRouting; 10 | using CMS.Base; 11 | 12 | [assembly: RegisterObjectType(typeof(UrlSlugInfo), UrlSlugInfo.OBJECT_TYPE)] 13 | 14 | namespace DynamicRouting 15 | { 16 | /// 17 | /// Data container class for . 18 | /// 19 | [Serializable] 20 | public partial class UrlSlugInfo : AbstractInfo 21 | { 22 | /// 23 | /// Object type. 24 | /// 25 | public const string OBJECT_TYPE = "dynamicrouting.urlslug"; 26 | 27 | 28 | /// 29 | /// Type information. 30 | /// 31 | public static readonly ObjectTypeInfo TYPEINFO = new ObjectTypeInfo(typeof(UrlSlugInfoProvider), OBJECT_TYPE, "DynamicRouting.UrlSlug", "UrlSlugID", "UrlSlugLastModified", "UrlSlugGuid", null, "UrlSlug", null, null, null, null) 32 | { 33 | ModuleName = "DynamicRouting.Kentico", 34 | TouchCacheDependencies = true, 35 | SupportsCloning = false, 36 | AllowDataExport = true, 37 | AllowRestore = false, 38 | DependsOn = new List() 39 | { 40 | new ObjectDependency("UrlSlugNodeID", "cms.node", ObjectDependencyEnum.Required), 41 | }, 42 | // Allowing export of Custom Slugs only, any non customized should be auto generated and previous URLs should be in the Document's Alternate Urls 43 | ImportExportSettings = 44 | { 45 | IsExportable = true, 46 | WhereCondition = "UrlSlugIsCustom = 1", 47 | ObjectTreeLocations = new List() 48 | { 49 | // Adds the custom class into a new category in the Global objects section of the export tree 50 | new ObjectTreeLocation(GLOBAL, "DynamicRouting", "CustomizedUrlSlugs"), 51 | }, 52 | }, 53 | SynchronizationSettings = 54 | { 55 | LogSynchronization = SynchronizationTypeEnum.LogSynchronization, 56 | LogCondition = ShouldCreateSynchronizationTask, 57 | ObjectTreeLocations = new List() 58 | { 59 | // Adds the custom class into a new category in the Global objects section of the staging tree 60 | new ObjectTreeLocation(GLOBAL, "DynamicRouting", "CustomUrlSlugs") 61 | }, 62 | }, 63 | }; 64 | 65 | private static bool ShouldCreateSynchronizationTask(BaseInfo classObj) 66 | { 67 | // Ignore any Url Slugs that should be ignored. 68 | // Only Url Slugs that are either made customized, the custom Url was modified, was Un-customized, or if this task was generated from the Staging Module in which case every Url Slug will be checked. 69 | // These staging tasks are manually handled since non-customized Url Slugs are dynamically generated based on the url Pattern of the page type. 70 | UrlSlugInfo UrlSlug = (UrlSlugInfo)classObj; 71 | if(UrlSlug.UrlSlugID <= 0) 72 | { 73 | return false; 74 | } 75 | return !DynamicRouteInternalHelper.ShouldIgnoreStagingTaskOfUrlSlug(UrlSlug.UrlSlugID); 76 | } 77 | 78 | protected override bool CheckPermissionsInternal(PermissionsEnum permission, string siteName, IUserInfo userInfo, bool exceptionOnFailure) 79 | { 80 | switch (permission) 81 | { 82 | case PermissionsEnum.Read: 83 | return userInfo.IsAuthorizedPerResource("DynamicRouting.Kentico", "ManageUrlSlug", siteName, exceptionOnFailure) || 84 | base.CheckPermissionsInternal(permission, siteName, userInfo, exceptionOnFailure); 85 | case PermissionsEnum.Create: 86 | return userInfo.IsAuthorizedPerResource("DynamicRouting.Kentico", "ManageUrlSlug", siteName, exceptionOnFailure) || 87 | base.CheckPermissionsInternal(permission, siteName, userInfo, exceptionOnFailure); 88 | case PermissionsEnum.Modify: 89 | return userInfo.IsAuthorizedPerResource("DynamicRouting.Kentico", "ManageUrlSlug", siteName, exceptionOnFailure) || 90 | base.CheckPermissionsInternal(permission, siteName, userInfo, exceptionOnFailure); 91 | case PermissionsEnum.Delete: 92 | return userInfo.IsAuthorizedPerResource("DynamicRouting.Kentico", "ManageUrlSlug", siteName, exceptionOnFailure) || 93 | base.CheckPermissionsInternal(permission, siteName, userInfo, exceptionOnFailure); 94 | case PermissionsEnum.Destroy: 95 | return userInfo.IsAuthorizedPerResource("DynamicRouting.Kentico", "ManageUrlSlug", siteName, exceptionOnFailure) || 96 | base.CheckPermissionsInternal(permission, siteName, userInfo, exceptionOnFailure); 97 | default: 98 | return base.CheckPermissionsInternal(permission, siteName, userInfo, exceptionOnFailure); 99 | } 100 | } 101 | 102 | 103 | /// 104 | /// Url slug ID. 105 | /// 106 | [DatabaseField] 107 | public virtual int UrlSlugID 108 | { 109 | get 110 | { 111 | return ValidationHelper.GetInteger(GetValue("UrlSlugID"), 0); 112 | } 113 | set 114 | { 115 | SetValue("UrlSlugID", value); 116 | } 117 | } 118 | 119 | 120 | /// 121 | /// The part of the url after the domain extension that identifies this page. Ex "/My-Page". 122 | /// 123 | [DatabaseField] 124 | public virtual string UrlSlug 125 | { 126 | get 127 | { 128 | return ValidationHelper.GetString(GetValue("UrlSlug"), String.Empty); 129 | } 130 | set 131 | { 132 | SetValue("UrlSlug", value); 133 | } 134 | } 135 | 136 | 137 | /// 138 | /// What node this URL slug belongs to. 139 | /// 140 | [DatabaseField] 141 | public virtual int UrlSlugNodeID 142 | { 143 | get 144 | { 145 | return ValidationHelper.GetInteger(GetValue("UrlSlugNodeID"), 0); 146 | } 147 | set 148 | { 149 | SetValue("UrlSlugNodeID", value); 150 | } 151 | } 152 | 153 | 154 | /// 155 | /// The Culture this URL slug applies to. Can be null if this URL slug is the fall back URL for all cultural variations of this node.. 156 | /// 157 | [DatabaseField] 158 | public virtual string UrlSlugCultureCode 159 | { 160 | get 161 | { 162 | return ValidationHelper.GetString(GetValue("UrlSlugCultureCode"), String.Empty); 163 | } 164 | set 165 | { 166 | SetValue("UrlSlugCultureCode", value, String.Empty); 167 | } 168 | } 169 | 170 | 171 | /// 172 | /// If checked, this indicates that the Url Slug was added manually and should not be overwritten during document URL slug updates.. 173 | /// 174 | [DatabaseField] 175 | public virtual bool UrlSlugIsCustom 176 | { 177 | get 178 | { 179 | return ValidationHelper.GetBoolean(GetValue("UrlSlugIsCustom"), false); 180 | } 181 | set 182 | { 183 | SetValue("UrlSlugIsCustom", value); 184 | } 185 | } 186 | 187 | 188 | /// 189 | /// Url slug guid. 190 | /// 191 | [DatabaseField] 192 | public virtual Guid UrlSlugGuid 193 | { 194 | get 195 | { 196 | return ValidationHelper.GetGuid(GetValue("UrlSlugGuid"), Guid.Empty); 197 | } 198 | set 199 | { 200 | SetValue("UrlSlugGuid", value); 201 | } 202 | } 203 | 204 | 205 | /// 206 | /// Url slug last modified. 207 | /// 208 | [DatabaseField] 209 | public virtual DateTime UrlSlugLastModified 210 | { 211 | get 212 | { 213 | return ValidationHelper.GetDateTime(GetValue("UrlSlugLastModified"), DateTimeHelper.ZERO_TIME); 214 | } 215 | set 216 | { 217 | SetValue("UrlSlugLastModified", value); 218 | } 219 | } 220 | 221 | 222 | /// 223 | /// Deletes the object using appropriate provider. 224 | /// 225 | protected override void DeleteObject() 226 | { 227 | UrlSlugInfoProvider.DeleteUrlSlugInfo(this); 228 | } 229 | 230 | 231 | /// 232 | /// Updates the object using appropriate provider. 233 | /// 234 | protected override void SetObject() 235 | { 236 | UrlSlugInfoProvider.SetUrlSlugInfo(this); 237 | } 238 | 239 | 240 | /// 241 | /// Constructor for de-serialization. 242 | /// 243 | /// Serialization info. 244 | /// Streaming context. 245 | protected UrlSlugInfo(SerializationInfo info, StreamingContext context) 246 | : base(info, context, TYPEINFO) 247 | { 248 | } 249 | 250 | 251 | /// 252 | /// Creates an empty instance of the class. 253 | /// 254 | public UrlSlugInfo() 255 | : base(TYPEINFO) 256 | { 257 | } 258 | 259 | 260 | /// 261 | /// Creates a new instances of the class from the given . 262 | /// 263 | /// DataRow with the object data. 264 | public UrlSlugInfo(DataRow dr) 265 | : base(TYPEINFO, dr) 266 | { 267 | } 268 | } 269 | } -------------------------------------------------------------------------------- /DynamicRouting.Kentico.Base/Classes/Base/UrlSlugInfoProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data; 3 | 4 | using CMS.Base; 5 | using CMS.DataEngine; 6 | using CMS.Helpers; 7 | 8 | namespace DynamicRouting 9 | { 10 | /// 11 | /// Class providing management. 12 | /// 13 | public partial class UrlSlugInfoProvider : AbstractInfoProvider 14 | { 15 | /// 16 | /// Creates an instance of . 17 | /// 18 | public UrlSlugInfoProvider() 19 | : base(UrlSlugInfo.TYPEINFO) 20 | { 21 | } 22 | 23 | 24 | /// 25 | /// Returns a query for all the objects. 26 | /// 27 | public static ObjectQuery GetUrlSlugs() 28 | { 29 | return ProviderObject.GetObjectQuery(); 30 | } 31 | 32 | 33 | /// 34 | /// Returns with specified ID. 35 | /// 36 | /// ID. 37 | public static UrlSlugInfo GetUrlSlugInfo(int id) 38 | { 39 | return ProviderObject.GetInfoById(id); 40 | } 41 | 42 | /// 43 | /// Returns with specified ID. 44 | /// 45 | /// ID. 46 | public static UrlSlugInfo GetUrlSlugInfo(Guid guid) 47 | { 48 | return ProviderObject.GetInfoByGuid(guid); 49 | } 50 | 51 | 52 | /// 53 | /// Sets (updates or inserts) specified . 54 | /// 55 | /// to be set. 56 | public static void SetUrlSlugInfo(UrlSlugInfo infoObj) 57 | { 58 | ProviderObject.SetInfo(infoObj); 59 | } 60 | 61 | 62 | /// 63 | /// Deletes specified . 64 | /// 65 | /// to be deleted. 66 | public static void DeleteUrlSlugInfo(UrlSlugInfo infoObj) 67 | { 68 | ProviderObject.DeleteInfo(infoObj); 69 | } 70 | 71 | 72 | /// 73 | /// Deletes with specified ID. 74 | /// 75 | /// ID. 76 | public static void DeleteUrlSlugInfo(int id) 77 | { 78 | UrlSlugInfo infoObj = GetUrlSlugInfo(id); 79 | DeleteUrlSlugInfo(infoObj); 80 | } 81 | 82 | /// 83 | /// Deletes with specified GUID. 84 | /// 85 | /// GUID. 86 | public static void DeleteUrlSlugInfo(Guid guid) 87 | { 88 | UrlSlugInfo infoObj = GetUrlSlugInfo(guid); 89 | DeleteUrlSlugInfo(infoObj); 90 | } 91 | } 92 | } -------------------------------------------------------------------------------- /DynamicRouting.Kentico.Base/Classes/Base/UrlSlugStagingTaskIgnoreInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data; 3 | using System.Runtime.Serialization; 4 | using System.Collections.Generic; 5 | 6 | using CMS; 7 | using CMS.DataEngine; 8 | using CMS.Helpers; 9 | using DynamicRouting; 10 | 11 | [assembly: RegisterObjectType(typeof(UrlSlugStagingTaskIgnoreInfo), UrlSlugStagingTaskIgnoreInfo.OBJECT_TYPE)] 12 | 13 | namespace DynamicRouting 14 | { 15 | /// 16 | /// Data container class for . 17 | /// 18 | [Serializable] 19 | public partial class UrlSlugStagingTaskIgnoreInfo : AbstractInfo 20 | { 21 | /// 22 | /// Object type. 23 | /// 24 | public const string OBJECT_TYPE = "dynamicrouting.urlslugstagingtaskignore"; 25 | 26 | 27 | /// 28 | /// Type information. 29 | /// 30 | public static readonly ObjectTypeInfo TYPEINFO = new ObjectTypeInfo(typeof(UrlSlugStagingTaskIgnoreInfoProvider), OBJECT_TYPE, "DynamicRouting.UrlSlugStagingTaskIgnore", "UrlSlugStagingTaskIgnoreID", "UrlSlugStagingTaskIgnoreLastModified", null, null, null, null, null, null, null) 31 | { 32 | ModuleName = "DynamicRouting.Kentico", 33 | TouchCacheDependencies = true, 34 | DependsOn = new List() 35 | { 36 | new ObjectDependency("UrlSlugStagingTaskIgnoreUrlSlugID", "dynamicrouting.urlslug", ObjectDependencyEnum.Required), 37 | }, 38 | }; 39 | 40 | 41 | /// 42 | /// Url slug staging task ignore ID. 43 | /// 44 | [DatabaseField] 45 | public virtual int UrlSlugStagingTaskIgnoreID 46 | { 47 | get 48 | { 49 | return ValidationHelper.GetInteger(GetValue("UrlSlugStagingTaskIgnoreID"), 0); 50 | } 51 | set 52 | { 53 | SetValue("UrlSlugStagingTaskIgnoreID", value); 54 | } 55 | } 56 | 57 | 58 | /// 59 | /// Url slug staging task ignore url slug ID. 60 | /// 61 | [DatabaseField] 62 | public virtual int UrlSlugStagingTaskIgnoreUrlSlugID 63 | { 64 | get 65 | { 66 | return ValidationHelper.GetInteger(GetValue("UrlSlugStagingTaskIgnoreUrlSlugID"), 0); 67 | } 68 | set 69 | { 70 | SetValue("UrlSlugStagingTaskIgnoreUrlSlugID", value); 71 | } 72 | } 73 | 74 | 75 | /// 76 | /// Url slug staging task ignore last modified. 77 | /// 78 | [DatabaseField] 79 | public virtual DateTime UrlSlugStagingTaskIgnoreLastModified 80 | { 81 | get 82 | { 83 | return ValidationHelper.GetDateTime(GetValue("UrlSlugStagingTaskIgnoreLastModified"), DateTimeHelper.ZERO_TIME); 84 | } 85 | set 86 | { 87 | SetValue("UrlSlugStagingTaskIgnoreLastModified", value); 88 | } 89 | } 90 | 91 | 92 | /// 93 | /// Deletes the object using appropriate provider. 94 | /// 95 | protected override void DeleteObject() 96 | { 97 | UrlSlugStagingTaskIgnoreInfoProvider.DeleteUrlSlugStagingTaskIgnoreInfo(this); 98 | } 99 | 100 | 101 | /// 102 | /// Updates the object using appropriate provider. 103 | /// 104 | protected override void SetObject() 105 | { 106 | UrlSlugStagingTaskIgnoreInfoProvider.SetUrlSlugStagingTaskIgnoreInfo(this); 107 | } 108 | 109 | 110 | /// 111 | /// Constructor for de-serialization. 112 | /// 113 | /// Serialization info. 114 | /// Streaming context. 115 | protected UrlSlugStagingTaskIgnoreInfo(SerializationInfo info, StreamingContext context) 116 | : base(info, context, TYPEINFO) 117 | { 118 | } 119 | 120 | 121 | /// 122 | /// Creates an empty instance of the class. 123 | /// 124 | public UrlSlugStagingTaskIgnoreInfo() 125 | : base(TYPEINFO) 126 | { 127 | } 128 | 129 | 130 | /// 131 | /// Creates a new instances of the class from the given . 132 | /// 133 | /// DataRow with the object data. 134 | public UrlSlugStagingTaskIgnoreInfo(DataRow dr) 135 | : base(TYPEINFO, dr) 136 | { 137 | } 138 | } 139 | } -------------------------------------------------------------------------------- /DynamicRouting.Kentico.Base/Classes/Base/UrlSlugStagingTaskIgnoreInfoProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data; 3 | 4 | using CMS.Base; 5 | using CMS.DataEngine; 6 | using CMS.Helpers; 7 | 8 | namespace DynamicRouting 9 | { 10 | /// 11 | /// Class providing management. 12 | /// 13 | public partial class UrlSlugStagingTaskIgnoreInfoProvider : AbstractInfoProvider 14 | { 15 | /// 16 | /// Creates an instance of . 17 | /// 18 | public UrlSlugStagingTaskIgnoreInfoProvider() 19 | : base(UrlSlugStagingTaskIgnoreInfo.TYPEINFO) 20 | { 21 | } 22 | 23 | 24 | /// 25 | /// Returns a query for all the objects. 26 | /// 27 | public static ObjectQuery GetUrlSlugStagingTaskIgnores() 28 | { 29 | return ProviderObject.GetObjectQuery(); 30 | } 31 | 32 | 33 | /// 34 | /// Returns with specified ID. 35 | /// 36 | /// ID. 37 | public static UrlSlugStagingTaskIgnoreInfo GetUrlSlugStagingTaskIgnoreInfo(int id) 38 | { 39 | return ProviderObject.GetInfoById(id); 40 | } 41 | 42 | 43 | /// 44 | /// Sets (updates or inserts) specified . 45 | /// 46 | /// to be set. 47 | public static void SetUrlSlugStagingTaskIgnoreInfo(UrlSlugStagingTaskIgnoreInfo infoObj) 48 | { 49 | ProviderObject.SetInfo(infoObj); 50 | } 51 | 52 | 53 | /// 54 | /// Deletes specified . 55 | /// 56 | /// to be deleted. 57 | public static void DeleteUrlSlugStagingTaskIgnoreInfo(UrlSlugStagingTaskIgnoreInfo infoObj) 58 | { 59 | ProviderObject.DeleteInfo(infoObj); 60 | } 61 | 62 | 63 | /// 64 | /// Deletes with specified ID. 65 | /// 66 | /// ID. 67 | public static void DeleteUrlSlugStagingTaskIgnoreInfo(int id) 68 | { 69 | UrlSlugStagingTaskIgnoreInfo infoObj = GetUrlSlugStagingTaskIgnoreInfo(id); 70 | DeleteUrlSlugStagingTaskIgnoreInfo(infoObj); 71 | } 72 | } 73 | } -------------------------------------------------------------------------------- /DynamicRouting.Kentico.Base/Classes/Models/NodeItemBuilderSetting.cs: -------------------------------------------------------------------------------- 1 | using CMS.MacroEngine; 2 | using CMS.SiteProvider; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using System.Xml.Serialization; 9 | 10 | namespace DynamicRouting 11 | { 12 | [Serializable] 13 | public class NodeItemBuilderSettings 14 | { 15 | /// 16 | /// Mainly stored for serialization so the Macro Resolver can be restored after deserialization 17 | /// 18 | public string SiteName { get; set; } 19 | 20 | /// 21 | /// List of all Culture Codes for the site. 22 | /// 23 | public List CultureCodes { get; set; } 24 | /// 25 | /// The Deafult content Culture code for this site. 26 | /// 27 | public string DefaultCultureCode { get; set; } 28 | /// 29 | /// If true, a Url Slug will be generated for a culture of a Node even if that document doesn't explicitely exist. 30 | /// 31 | public bool GenerateIfCultureDoesntExist { get; set; } 32 | 33 | /// 34 | /// If this is false, then although it will queue up the Item, it will not run the queue automatically, used primarily in the Class update 35 | /// 36 | public bool CheckQueueImmediately { get; set; } = true; 37 | 38 | [NonSerialized] 39 | private MacroResolver _BaseResolver; 40 | 41 | /// 42 | /// The Base Macro Resolver, this is used in building the Url Slugs. 43 | /// 44 | [XmlIgnore] 45 | public MacroResolver BaseResolver 46 | { 47 | get 48 | { 49 | if (_BaseResolver == null) 50 | { 51 | // Rebuild 52 | SiteInfo Site = SiteInfoProvider.GetSiteInfo(SiteName); 53 | _BaseResolver = MacroResolver.GetInstance(); 54 | _BaseResolver.AddAnonymousSourceData(new object[] { Site }); 55 | } 56 | return _BaseResolver; 57 | } 58 | set 59 | { 60 | _BaseResolver = value; 61 | } 62 | } 63 | /// 64 | /// If True, then will only perform updates where needed, and will not check children beyond the main scope unless a change is found (unless CheckEntireTree is true) 65 | /// 66 | public bool CheckingForUpdates { get; set; } = false; 67 | 68 | 69 | /// 70 | /// If true, then the parent of the main node should be checked along with it's children (the main node's siblings). Usually set from DynamicRouteInternalHelper.CheckSiblings 71 | /// 72 | public bool BuildSiblings { get; set; } 73 | 74 | /// 75 | /// If true, then the Children should be checked on the applicable nodes. Usually set from DynamicRouteInternalHelper.CheckChildren 76 | /// 77 | public bool BuildChildren { get; set; } 78 | 79 | /// 80 | /// If true, then all descendents need to be checked for updates. Usually set from DynamicRouteInternalHelper.CheckDescendents 81 | /// 82 | public bool BuildDescendents { get; set; } 83 | 84 | /// 85 | /// Should only be used for deserialization 86 | /// 87 | public NodeItemBuilderSettings() 88 | { 89 | 90 | } 91 | 92 | /// 93 | /// Constructor of the NodeItemBuilderSettings that is used to guide the build processes. 94 | /// 95 | /// List of the site's culture code 96 | /// The site's default culture code 97 | /// If the slugs should generate for each culture even if they don't exist. 98 | /// The Base Macro Resolver 99 | /// If updates should be checked 100 | /// If the entire tree needs to be checked, usually triggered by site, culture, or page type changes 101 | /// If siblings need to be built (if NodeOrder is found) 102 | /// If the children of the item need to be checked (has various Parent attributes) 103 | /// If descendents need to be checked even if the parent of it doesn't change. 104 | /// The SiteName 105 | public NodeItemBuilderSettings(List CultureCodes, string DefaultCultureCode, bool GenerateIfCultureDoesntExist, MacroResolver BaseResolver, bool CheckingForUpdates, bool CheckEntireTree, bool BuildSiblings, bool BuildChildren, bool BuildDescendents, string SiteName) 106 | { 107 | this.SiteName = SiteName; 108 | this.CultureCodes = CultureCodes; 109 | this.DefaultCultureCode = DefaultCultureCode; 110 | this.GenerateIfCultureDoesntExist = GenerateIfCultureDoesntExist; 111 | this.BaseResolver = BaseResolver; 112 | this.CheckingForUpdates = CheckingForUpdates; 113 | this.BuildSiblings = BuildSiblings; 114 | this.BuildChildren = BuildChildren; 115 | this.BuildDescendents = BuildDescendents; 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /DynamicRouting.Kentico.Base/Classes/Models/NodeUrlSlug.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace DynamicRouting 4 | { 5 | /// 6 | /// Represents a Node Url Slug, including references back to existing slug guid and settings 7 | /// 8 | [Serializable] 9 | public class NodeUrlSlug 10 | { 11 | public string CultureCode { get; set; } 12 | public int SiteID { get; set; } 13 | public string UrlSlug { get; set; } 14 | public bool IsDefault { get; set; } 15 | public bool IsCustom { get; set; } 16 | public bool IsNewOrUpdated { get; set; } = false; 17 | public bool Delete { get; set; } = false; 18 | public string PreviousUrlSlug { get; set; } 19 | public Guid ExistingNodeSlugGuid { get; set; } 20 | public NodeUrlSlug() 21 | { 22 | 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /DynamicRouting.Kentico.Base/Classes/Other/DynamicRouteConfiguration.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace DynamicRouting.Kentico.MVC 8 | { 9 | /// 10 | /// Dynamic Route Configuration Class, set in the Base so it is accessable by the Events 11 | /// 12 | public struct DynamicRouteConfiguration 13 | { 14 | public string ControllerName { get; set; } 15 | 16 | public string ActionName { get; set; } 17 | 18 | public string ViewName { get; set; } 19 | 20 | public Type ModelType { get; set; } 21 | 22 | public bool IncludeDocumentInOutputCache { get; set; } 23 | 24 | public bool UseOutputCaching { get; set; } 25 | 26 | public DynamicRouteType RouteType { get; set; } 27 | 28 | public Dictionary RouteValues { get; set; } 29 | 30 | public DynamicRouteConfiguration(string controllerName, string actionName, string viewName, Type modelType, DynamicRouteType routeType,bool includeDocumentInOutputCache, bool useOutputCaching) 31 | { 32 | ViewName = viewName; 33 | ModelType = modelType; 34 | RouteType = routeType; 35 | 36 | IncludeDocumentInOutputCache = includeDocumentInOutputCache; 37 | 38 | // Adjust based on Route Type 39 | switch (RouteType) 40 | { 41 | case DynamicRouteType.View: 42 | ControllerName = "DynamicRoute"+(useOutputCaching ? "Cached" : ""); 43 | ActionName = "RenderView"; 44 | UseOutputCaching = useOutputCaching; 45 | break; 46 | case DynamicRouteType.ViewWithModel: 47 | ControllerName = "DynamicRoute" + (useOutputCaching ? "Cached" : ""); 48 | ActionName = "RenderViewWithModel"; 49 | UseOutputCaching = useOutputCaching; 50 | break; 51 | case DynamicRouteType.Controller: 52 | default: 53 | ControllerName = controllerName; 54 | ActionName = actionName; 55 | UseOutputCaching = false; 56 | break; 57 | } 58 | RouteValues = new Dictionary(); 59 | } 60 | } 61 | 62 | public enum DynamicRouteType 63 | { 64 | Controller, View, ViewWithModel 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /DynamicRouting.Kentico.Base/Classes/Other/UrlSlugCollisionException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace DynamicRouting.Kentico.Classes 8 | { 9 | /// 10 | /// Wrapper for normal Exceptions for when a Url Slug collission occurrs 11 | /// 12 | public class UrlSlugCollisionException : Exception 13 | { 14 | public UrlSlugCollisionException() : base() 15 | { 16 | 17 | } 18 | public UrlSlugCollisionException(string message) : base(message) 19 | { 20 | 21 | } 22 | 23 | public UrlSlugCollisionException(string message, Exception innerException) : base(message, innerException) 24 | { 25 | 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /DynamicRouting.Kentico.Base/Helpers/DynamicRouteEventHelper.cs: -------------------------------------------------------------------------------- 1 | namespace DynamicRouting 2 | { 3 | /// 4 | /// Event helper, these should be called from Global events to help trigger the proper updates 5 | /// 6 | public static class DynamicRouteEventHelper 7 | { 8 | public static void CultureVariationSettingsChanged(string SiteName) 9 | { 10 | // Build all, update all 11 | DynamicRouteInternalHelper.RebuildRoutesBySite(SiteName); 12 | } 13 | 14 | public static void SiteLanguageChanged(string SiteName) 15 | { 16 | // Build all, update all 17 | DynamicRouteInternalHelper.RebuildRoutesBySite(SiteName); 18 | } 19 | 20 | public static void SiteDefaultLanguageChanged(string SiteName) 21 | { 22 | // Build all, update all 23 | DynamicRouteInternalHelper.RebuildRoutesBySite(SiteName); 24 | } 25 | 26 | /// 27 | /// Rebuilds the Url Slugs affected by a class Url Pattern Change. 28 | /// 29 | /// 30 | public static void ClassUrlPatternChanged(string ClassName) 31 | { 32 | DynamicRouteInternalHelper.RebuildRoutesByClass(ClassName); 33 | } 34 | 35 | public static void ExcludedClassesChanged(string SiteName = null) 36 | { 37 | if (!string.IsNullOrWhiteSpace(SiteName)) 38 | { 39 | DynamicRouteInternalHelper.RebuildRoutesBySite(SiteName); 40 | } else 41 | { 42 | DynamicRouteInternalHelper.RebuildRoutes(); 43 | 44 | } 45 | } 46 | 47 | /// 48 | /// Build ParentNodes, and Parent's immediate children, build the children and update recursively only if changes detected. 49 | /// 50 | /// 51 | public static void DocumentDeleted(int ParentNodeID) 52 | { 53 | DynamicRouteInternalHelper.RebuildRoutesByNode(ParentNodeID); 54 | } 55 | 56 | /// 57 | /// Build both ParentNodes, and Parent's immediate children, build the children and update recursively only if changes detected. 58 | /// 59 | /// 60 | /// 61 | public static void DocumentMoved(int OldParentNodeID, int NewParentNodeID) 62 | { 63 | DynamicRouteInternalHelper.RebuildRoutesByNode(OldParentNodeID); 64 | DynamicRouteInternalHelper.RebuildRoutesByNode(NewParentNodeID); 65 | } 66 | 67 | /// 68 | /// Build ParentNode, and Parent's immediate children, build the children and update recursively only if changes detected. 69 | /// 70 | /// 71 | public static void DocumentInsertUpdated(int NodeID) 72 | { 73 | DynamicRouteInternalHelper.RebuildRoutesByNode(NodeID); 74 | } 75 | 76 | /// 77 | /// Build ParentNode, and Parent's immediate children, build the children and update recursively only if changes detected. 78 | /// 79 | /// 80 | public static void DocumentInsertUpdated_CheckOnly(int NodeID) 81 | { 82 | DynamicRouteInternalHelper.RebuildRoutesByNode_CheckOnly(NodeID); 83 | } 84 | 85 | /// 86 | /// Build Node (and upward), and build the children and update recursively only if changes detected. 87 | /// 88 | /// 89 | public static void UrlSlugModified(int UrlSlugID) 90 | { 91 | // Convert UrlSlugID to NodeID 92 | int NodeID = UrlSlugInfoProvider.GetUrlSlugInfo(UrlSlugID).UrlSlugNodeID; 93 | DynamicRouteInternalHelper.RebuildRoutesByNode(NodeID); 94 | } 95 | 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /DynamicRouting.Kentico.Base/Helpers/EnvironmentHelper.cs: -------------------------------------------------------------------------------- 1 | using CMS.Base; 2 | using CMS.SiteProvider; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using System.Web; 9 | 10 | namespace DynamicRouting.Helpers 11 | { 12 | public class EnvironmentHelper 13 | { 14 | /// 15 | /// Gets the Url requested, handling any Virtual Directories 16 | /// 17 | /// The Request 18 | /// SiteName, if provided will also remove the site's forbidden and replacement characters 19 | /// The Url for lookup 20 | public static string GetUrl(HttpRequestBase Request, string SiteName = "") 21 | { 22 | return GetUrl(Request.Url.AbsolutePath, Request.ApplicationPath, SiteName); 23 | } 24 | 25 | /// 26 | /// Gets the Url requested, handling any Virtual Directories 27 | /// 28 | /// The Request 29 | /// SiteName, if provided will also remove the site's forbidden and replacement characters 30 | /// The Url for lookup 31 | public static string GetUrl(HttpRequest Request, string SiteName = "") 32 | { 33 | return GetUrl(Request.Url.AbsolutePath, Request.ApplicationPath, SiteName); 34 | } 35 | 36 | /// 37 | /// Gets the Url requested, handling any Virtual Directories 38 | /// 39 | /// The Request 40 | /// SiteName, if provided will also remove the site's forbidden and replacement characters 41 | /// The Url for lookup 42 | public static string GetUrl(IRequest Request, string SiteName = "") 43 | { 44 | return GetUrl(Request.Url.AbsolutePath, HttpContext.Current.Request.ApplicationPath, SiteName); 45 | } 46 | 47 | /// 48 | /// Removes Application Path from Url if present and ensures starts with / 49 | /// 50 | /// The Url (Relative) 51 | /// 52 | /// SiteName, if provided will also remove the site's forbidden and replacement characters 53 | /// 54 | public static string GetUrl(string RelativeUrl, string ApplicationPath, string SiteName = "") 55 | { 56 | // Remove Application Path from Relative Url if it exists at the beginning 57 | if (!string.IsNullOrWhiteSpace(ApplicationPath) && ApplicationPath != "/" && RelativeUrl.ToLower().IndexOf(ApplicationPath.ToLower()) == 0) 58 | { 59 | RelativeUrl = RelativeUrl.Substring(ApplicationPath.Length); 60 | } 61 | 62 | return DynamicRouteInternalHelper.GetCleanUrl(RelativeUrl, SiteName); 63 | } 64 | 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /DynamicRouting.Kentico.Base/Overrides/DocumentUrlProviderOverride.cs: -------------------------------------------------------------------------------- 1 | using CMS; 2 | using CMS.DocumentEngine; 3 | using CMS.EventLog; 4 | using CMS.Helpers; 5 | using CMS.SiteProvider; 6 | using DynamicRouting.Kentico.Base; 7 | using System; 8 | using System.Collections.Generic; 9 | using System.Linq; 10 | using System.Text; 11 | using System.Threading.Tasks; 12 | 13 | [assembly: RegisterCustomProvider(typeof(DynamicRoutingDocumentURLProvider))] 14 | 15 | namespace DynamicRouting.Kentico.Base 16 | { 17 | 18 | 19 | public class DynamicRoutingDocumentURLProvider : DocumentURLProvider 20 | { 21 | /// 22 | /// Returns relative URL for the specified tree node, using the Url Slug if the class is not in the excluded list. 23 | /// 24 | /// Tree node 25 | /// The Relative Url 26 | protected override string GetUrlInternal(TreeNode node) 27 | { 28 | try 29 | { 30 | if (node == null) 31 | { 32 | return base.GetUrlInternal(node); 33 | } 34 | if (!DynamicRouteInternalHelper.UrlSlugExcludedClassNames().Contains(node.ClassName.ToLower())) 35 | { 36 | var FoundSlug = CacheHelper.Cache(cs => 37 | { 38 | if (cs.Cached) 39 | { 40 | cs.CacheDependency = CacheHelper.GetCacheDependency("DynamicRouting.UrlSlug|all"); 41 | } 42 | return UrlSlugInfoProvider.GetUrlSlugs() 43 | .WhereEquals("UrlSlugNodeID", node.NodeID) 44 | .WhereEquals("UrlSlugCultureCode", node.DocumentCulture) 45 | .FirstOrDefault(); 46 | }, new CacheSettings(1440, "GetUrlSlugByNode", node.NodeID, node.DocumentCulture)); 47 | 48 | if (FoundSlug != null) 49 | { 50 | return FoundSlug.UrlSlug; 51 | } 52 | } 53 | } catch(Exception ex) { 54 | EventLogProvider.LogException("DynamicRouting", "DocumentUrlProvider_GetUrlInternal_Error", ex, additionalMessage: "for node " + (node == null || node.NodeGUID == null ? "null" : node.NodeGUID.ToString())); 55 | } 56 | return base.GetUrlInternal(node); 57 | } 58 | 59 | /// 60 | /// Returns presentation URL for the specified node, using UrlSlug if the class is not in the excluded list. This is the absolute URL where live presentation of given node can be found. 61 | /// 62 | /// Tree node to return presentation URL for. 63 | /// A preferred domain name that should be used as the host part of the URL. Preferred domain must be assigned to the site as a domain alias otherwise site main domain is used. 64 | /// 65 | protected override string GetPresentationUrlInternal(TreeNode node, string preferredDomainName = null) 66 | { 67 | try 68 | { 69 | if (node == null) 70 | { 71 | return base.GetPresentationUrlInternal(node, preferredDomainName); 72 | } 73 | 74 | if (!DynamicRouteInternalHelper.UrlSlugExcludedClassNames().Contains(node.ClassName.ToLower())) 75 | { 76 | var FoundSlug = CacheHelper.Cache(cs => 77 | { 78 | if (cs.Cached) 79 | { 80 | cs.CacheDependency = CacheHelper.GetCacheDependency("DynamicRouting.UrlSlug|all"); 81 | } 82 | return UrlSlugInfoProvider.GetUrlSlugs() 83 | .WhereEquals("UrlSlugNodeID", node.NodeID) 84 | .WhereEquals("UrlSlugCultureCode", node.DocumentCulture) 85 | .FirstOrDefault(); 86 | }, new CacheSettings(1440, "GetUrlSlugByNode", node.NodeID, node.DocumentCulture)); 87 | 88 | if (FoundSlug != null) 89 | { 90 | SiteInfo site = node.Site; 91 | string url = FoundSlug.UrlSlug; 92 | if (!string.IsNullOrEmpty(site.SitePresentationURL)) 93 | { 94 | return URLHelper.CombinePath(url, '/', site.SitePresentationURL, null); 95 | } 96 | if (!string.IsNullOrEmpty(preferredDomainName)) 97 | { 98 | return URLHelper.GetAbsoluteUrl(url, preferredDomainName); 99 | } 100 | return URLHelper.GetAbsoluteUrl(url, site.DomainName); 101 | } 102 | } 103 | } catch(Exception ex) 104 | { 105 | EventLogProvider.LogException("DynamicRouting", "DocumentUrlProvider_GetPresentationUrlInternal_Error", ex, additionalMessage: "for node " + (node == null || node.NodeGUID == null ? "null" : node.NodeGUID.ToString())); 106 | } 107 | 108 | return base.GetPresentationUrlInternal(node, preferredDomainName); 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /DynamicRouting.Kentico.Base/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | using CMS; 5 | 6 | // General Information about an assembly is controlled through the following 7 | // set of attributes. Change these attribute values to modify the information 8 | // associated with an assembly. 9 | [assembly: AssemblyTitle("DynamicRouting.Kentico.Base")] 10 | [assembly: AssemblyDescription("Base Assembly used for DynamicRouting.Kentico and DynamicRouting.Kentico.MVC")] 11 | [assembly: AssemblyConfiguration("")] 12 | [assembly: AssemblyCompany("")] 13 | [assembly: AssemblyProduct("DynamicRouting.Kentico.Base")] 14 | [assembly: AssemblyCopyright("2019 Kentico Community")] 15 | [assembly: AssemblyTrademark("")] 16 | [assembly: AssemblyCulture("")] 17 | [assembly: AssemblyDiscoverable] 18 | 19 | // Setting ComVisible to false makes the types in this assembly not visible 20 | // to COM components. If you need to access a type in this assembly from 21 | // COM, set the ComVisible attribute to true on that type. 22 | [assembly: ComVisible(false)] 23 | 24 | // The following GUID is for the ID of the typelib if this project is exposed to COM 25 | [assembly: Guid("66553b70-ef4a-40ba-b077-f5b88826eb82")] 26 | 27 | // Version information for an assembly consists of the following four values: 28 | // 29 | // Major Version 30 | // Minor Version 31 | // Build Number 32 | // Revision 33 | // 34 | // You can specify all the values or you can default the Build and Revision Numbers 35 | // by using the '*' as shown below: 36 | // [assembly: AssemblyVersion("1.0.*")] 37 | [assembly: AssemblyVersion("12.29.20.0")] 38 | [assembly: AssemblyFileVersion("12.29.20.0")] 39 | -------------------------------------------------------------------------------- /DynamicRouting.Kentico.Base/app.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /DynamicRouting.Kentico.Base/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /DynamicRouting.Kentico.MVC/BuildPackage.bat: -------------------------------------------------------------------------------- 1 | nuget pack DynamicRouting.Kentico.MVC.csproj -Prop Configuration=Release 2 | @echo off 3 | set /p DUMMY="Hit ENTER to exit..." -------------------------------------------------------------------------------- /DynamicRouting.Kentico.MVC/ControllerExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Web.Mvc; 3 | 4 | namespace DynamicRouting.Kentico.MVC 5 | { 6 | public static class ControllerExtensions 7 | { 8 | /** 9 | * "Controller".Length 10 | */ 11 | private const int CONTROLLER_SUFFIX_LENGTH = 10; 12 | 13 | public static string ControllerNamePrefix(this T _) where T : Controller 14 | { 15 | var controllerType = typeof(T); 16 | 17 | return GetControllerNamePrefixFromType(controllerType); 18 | } 19 | 20 | public static string ControllerNamePrefix(this Type controllerType) 21 | { 22 | return GetControllerNamePrefixFromType(controllerType); 23 | } 24 | 25 | private static string GetControllerNamePrefixFromType(Type controllerType) 26 | { 27 | if (!typeof(Controller).IsAssignableFrom(controllerType)) 28 | { 29 | throw new ArgumentException($"Type [{controllerType.Name}] is not assignable from [{nameof(Controller)}]"); 30 | } 31 | 32 | return controllerType 33 | .Name 34 | .Substring(0, controllerType.Name.Length - CONTROLLER_SUFFIX_LENGTH); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /DynamicRouting.Kentico.MVC/DynamicHttpHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Web; 3 | using System.Web.Mvc; 4 | using CMS.Base; 5 | using CMS.DataEngine; 6 | using CMS.Helpers; 7 | using RequestContext = System.Web.Routing.RequestContext; 8 | using System.Web.SessionState; 9 | using Kentico.PageBuilder.Web.Mvc; 10 | using Kentico.PageBuilder.Web.Mvc.PageTemplates; 11 | using Newtonsoft.Json.Linq; 12 | using System.Linq; 13 | using DynamicRouting.Interfaces; 14 | using DynamicRouting.Implementations; 15 | 16 | namespace DynamicRouting.Kentico.MVC 17 | { 18 | public class DynamicHttpHandler : IHttpHandler, IRequiresSessionState 19 | { 20 | private readonly IComponentDefinitionProvider _pageTemplateDefinitionProvider; 21 | private readonly IDynamicRouteHelper mDynamicRouteHelper; 22 | public RequestContext RequestContext { get; set; } 23 | 24 | public DynamicHttpHandler(RequestContext requestContext) 25 | { 26 | RequestContext = requestContext; 27 | _pageTemplateDefinitionProvider = new ComponentDefinitionProvider(); 28 | mDynamicRouteHelper = new BaseDynamicRouteHelper(); 29 | } 30 | 31 | public bool IsReusable 32 | { 33 | get 34 | { 35 | return false; 36 | } 37 | } 38 | 39 | /// 40 | /// Gets the page, and based on the Class of the Page, attempts to get the Dynamic routing information and processes. 41 | /// 42 | /// 43 | public void ProcessRequest(HttpContext context) 44 | { 45 | var node = mDynamicRouteHelper.GetPage(AddPageToCacheDependency: false); 46 | 47 | var routePair = ResolveRouteValues(node); 48 | 49 | RequestRoutingEventArgs RequestArgs = new RequestRoutingEventArgs() 50 | { 51 | Page = node, 52 | Configuration = routePair, 53 | CurrentRequestContext = RequestContext 54 | }; 55 | 56 | // Use event to allow users to overwrite the Dynamic Routing Data 57 | using (var RequestRoutingHandler = DynamicRoutingEvents.RequestRouting.StartEvent(RequestArgs)) 58 | { 59 | // Check if the OutputCache was disabled or enabled during the event args 60 | if (routePair.ControllerName.Equals("DynamicRouteCached", StringComparison.InvariantCultureIgnoreCase) && !routePair.UseOutputCaching) 61 | { 62 | routePair.ControllerName = "DynamicRoute"; 63 | } 64 | else if (routePair.ControllerName.Equals("DynamicRoute", StringComparison.InvariantCultureIgnoreCase) && routePair.UseOutputCaching) 65 | { 66 | routePair.ControllerName = "DynamicRouteCached"; 67 | } 68 | 69 | // Handle passing the Include In Output Cache 70 | switch (routePair.ControllerName.ToLower()) 71 | { 72 | case "dynamicroute": 73 | case "dynamicroutecached": 74 | case "dynamicroutetemplate": 75 | RequestArgs.CurrentRequestContext.RouteData.Values["IncludeDocumentInOutputCache"] = routePair.IncludeDocumentInOutputCache; 76 | break; 77 | } 78 | 79 | // Setup routing with new values 80 | RequestArgs.CurrentRequestContext.RouteData.Values["Controller"] = routePair.ControllerName; 81 | RequestArgs.CurrentRequestContext.RouteData.Values["Action"] = routePair.ActionName; 82 | 83 | foreach (string Key in routePair.RouteValues.Keys) 84 | { 85 | RequestArgs.CurrentRequestContext.RouteData.Values[Key] = routePair.RouteValues[Key]; 86 | } 87 | 88 | // Allow users to adjust the RequestContext further 89 | RequestRoutingHandler.FinishEvent(); 90 | 91 | // Pass back context 92 | RequestContext = RequestArgs.CurrentRequestContext; 93 | } 94 | 95 | IControllerFactory factory = ControllerBuilder.Current.GetControllerFactory(); 96 | IController controller = factory.CreateController(RequestContext, routePair.ControllerName); 97 | controller.Execute(RequestContext); 98 | 99 | factory.ReleaseController(controller); 100 | } 101 | 102 | /// 103 | /// Determines where the Controller and Action should be based on the dynamic routing data 104 | /// 105 | /// 106 | /// 107 | private DynamicRouteConfiguration ResolveRouteValues(ITreeNode node) 108 | { 109 | string defaultController = RequestContext.RouteData.Values.ContainsKey("controller") 110 | ? RequestContext.RouteData.Values["controller"].ToString() 111 | : ""; 112 | 113 | string defaultAction = RequestContext.RouteData.Values.ContainsKey("action") 114 | ? RequestContext.RouteData.Values["action"].ToString() 115 | : ""; 116 | 117 | if (string.IsNullOrWhiteSpace(defaultController)) 118 | { 119 | defaultController = "DynamicRoute"; 120 | } 121 | if (string.IsNullOrWhiteSpace(defaultAction)) 122 | { 123 | defaultAction = "RouteValuesNotFound"; 124 | } 125 | 126 | if (node is null) 127 | { 128 | return new DynamicRouteConfiguration(defaultController, defaultAction, null, null, DynamicRouteType.Controller, false, false); 129 | } 130 | 131 | if (PageHasTemplate(node)) 132 | { 133 | var PageTemplateRouteConfig = new DynamicRouteConfiguration("DynamicRouteTemplate", "Index", null, null, DynamicRouteType.Controller, DynamicRouteInternalHelper.GetDefaultAddPageToCacheDependency(), false); 134 | string PageTemplateControllerName = GetPageTemplateController(node); 135 | 136 | // When the Dynamic Route Template Controller renders the Page Template, the Route Controller needs to match or it won't look in the right spot for the view 137 | if (!string.IsNullOrWhiteSpace(PageTemplateControllerName)) 138 | { 139 | PageTemplateRouteConfig.RouteValues.Add("TemplateControllerName", PageTemplateControllerName); 140 | } 141 | return PageTemplateRouteConfig; 142 | } 143 | 144 | if (!DynamicRoutingAnalyzer.TryFindMatch(node.ClassName, out var match)) 145 | { 146 | return new DynamicRouteConfiguration(defaultController, defaultAction, null, null, DynamicRouteType.Controller, false, false); 147 | } 148 | 149 | return match; 150 | } 151 | 152 | /// 153 | /// Checks if the current page is using a template or not. 154 | /// 155 | /// The Tree Node 156 | /// If it has a template or not 157 | private bool PageHasTemplate(ITreeNode Page) 158 | { 159 | string TemplateConfiguration = GetTemplateConfiguration(Page); 160 | return !string.IsNullOrWhiteSpace(TemplateConfiguration) && !TemplateConfiguration.ToLower().Contains("\"empty.template\""); 161 | } 162 | 163 | private string GetPageTemplateController(ITreeNode Page) 164 | { 165 | string TemplateConfiguration = GetTemplateConfiguration(Page); 166 | if (!string.IsNullOrWhiteSpace(TemplateConfiguration) && !TemplateConfiguration.ToLower().Contains("\"empty.template\"")) 167 | { 168 | var json = JObject.Parse(TemplateConfiguration); 169 | var templateIdentifier = ValidationHelper.GetString(json["identifier"], ""); 170 | 171 | // Return the controller name, if it has any 172 | return _pageTemplateDefinitionProvider.GetAll() 173 | .FirstOrDefault(def => def.Identifier.Equals(templateIdentifier, StringComparison.InvariantCultureIgnoreCase)) 174 | ?.ControllerName; 175 | } 176 | else 177 | { 178 | // No template 179 | return null; 180 | } 181 | } 182 | 183 | private string GetTemplateConfiguration(ITreeNode Page) 184 | { 185 | string TemplateConfiguration = ValidationHelper.GetString(Page.GetValue("DocumentPageTemplateConfiguration"), ""); 186 | 187 | // Check Temp Page builder widgets to detect a switch in template 188 | var InstanceGuid = ValidationHelper.GetGuid(URLHelper.GetQueryValue(HttpContext.Current.Request.Url.AbsoluteUri, "instance"), Guid.Empty); 189 | if (InstanceGuid != Guid.Empty) 190 | { 191 | var Table = ConnectionHelper.ExecuteQuery(String.Format("select PageBuilderTemplateConfiguration from Temp_PageBuilderWidgets where PageBuilderWidgetsGuid = '{0}'", InstanceGuid.ToString()), null, QueryTypeEnum.SQLQuery).Tables[0]; 192 | if (Table.Rows.Count > 0) 193 | { 194 | TemplateConfiguration = ValidationHelper.GetString(Table.Rows[0]["PageBuilderTemplateConfiguration"], ""); 195 | } 196 | } 197 | return TemplateConfiguration; 198 | } 199 | } 200 | } -------------------------------------------------------------------------------- /DynamicRouting.Kentico.MVC/DynamicRouteCachedController.cs: -------------------------------------------------------------------------------- 1 | using CMS.DataEngine; 2 | using CMS.Helpers; 3 | using CMS.SiteProvider; 4 | using DynamicRouting.Implementations; 5 | using DynamicRouting.Interfaces; 6 | using Kentico.PageBuilder.Web.Mvc; 7 | using Kentico.Web.Mvc; 8 | using System; 9 | using System.Web.Mvc; 10 | 11 | namespace DynamicRouting.Kentico.MVC 12 | { 13 | 14 | public class DynamicRouteCachedController : Controller 15 | { 16 | private IDynamicRouteHelper mDynamicRouteHelper; 17 | public DynamicRouteCachedController() 18 | { 19 | mDynamicRouteHelper = new BaseDynamicRouteHelper(); 20 | } 21 | 22 | /// 23 | /// Renders the Dynamic Route View (no model) 24 | /// 25 | /// 26 | [OutputCache(CacheProfile = "DynamicRouteController")] 27 | public ActionResult RenderView(bool? IncludeDocumentInOutputCache = null) 28 | { 29 | if (!IncludeDocumentInOutputCache.HasValue) 30 | { 31 | IncludeDocumentInOutputCache = DynamicRouteInternalHelper.GetDefaultAddPageToCacheDependency(); 32 | } 33 | // Get default Add Page to Output Dependency 34 | var node = mDynamicRouteHelper.GetPage(AddPageToCacheDependency: IncludeDocumentInOutputCache.Value); 35 | var routeConfig = mDynamicRouteHelper.GetRouteConfiguration(node); 36 | HttpContext.Kentico().PageBuilder().Initialize(node.DocumentID); 37 | 38 | return View(routeConfig.ViewName); 39 | } 40 | 41 | /// 42 | /// Renders the View with either an ITreeNode model or the given Model Type 43 | /// 44 | /// 45 | [OutputCache(CacheProfile = "DynamicRouteController")] 46 | public ActionResult RenderViewWithModel(bool? IncludeDocumentInOutputCache = null) 47 | { 48 | if (!IncludeDocumentInOutputCache.HasValue) 49 | { 50 | IncludeDocumentInOutputCache = DynamicRouteInternalHelper.GetDefaultAddPageToCacheDependency(); 51 | } 52 | var node = mDynamicRouteHelper.GetPage(AddPageToCacheDependency: IncludeDocumentInOutputCache.Value); 53 | var routeConfig = mDynamicRouteHelper.GetRouteConfiguration(node); 54 | HttpContext.Kentico().PageBuilder().Initialize(node.DocumentID); 55 | 56 | // Convert type 57 | if (routeConfig.ModelType != null) 58 | { 59 | try { 60 | return View(routeConfig.ViewName, Convert.ChangeType(node, routeConfig.ModelType)); 61 | } catch(InvalidCastException ex) 62 | { 63 | throw new InvalidCastException(ex.Message + ", this may be caused by the generated PageType class not being found in the project, or if it's located in an assembly that does not have [assembly: AssemblyDiscoverable] in it's AssemblyInfo.cs. The found page is of type "+(node == null ? "Null" : node.GetType().FullName), ex); 64 | } 65 | } 66 | else 67 | { 68 | return View(routeConfig.ViewName, node); 69 | } 70 | } 71 | 72 | 73 | /// 74 | /// Returns an error message when the page is found but no DynamicRouting assembly tags were configured. 75 | /// 76 | /// 77 | public ActionResult RouteValuesNotFound() 78 | { 79 | var node = mDynamicRouteHelper.GetPage(AddPageToCacheDependency: DynamicRouteInternalHelper.GetDefaultAddPageToCacheDependency()); 80 | return Content($"

No Route Value Found

No DynamicRouting assembly tag was found for the class {node.ClassName}, could not route page {node.NodeAliasPath}

"); 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /DynamicRouting.Kentico.MVC/DynamicRouteConstraint.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Web; 3 | using System.Web.Routing; 4 | using CMS.Helpers; 5 | using DynamicRouting.Implementations; 6 | using DynamicRouting.Interfaces; 7 | 8 | namespace DynamicRouting.Kentico.MVC 9 | { 10 | public class DynamicRouteConstraint : IRouteConstraint 11 | { 12 | 13 | private IDynamicRouteHelper mDynamicRouteHelper; 14 | public DynamicRouteConstraint() 15 | { 16 | mDynamicRouteHelper = new BaseDynamicRouteHelper(); 17 | } 18 | 19 | /// 20 | /// Returns true if a Page is found using the Dynamic Routing 21 | /// 22 | /// 23 | /// 24 | /// 25 | /// 26 | /// 27 | /// 28 | public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection) 29 | { 30 | // Never match on URL generation, this is called with the Url.Action and HtmlLink and shouldn't ever be used. 31 | if(routeDirection == RouteDirection.UrlGeneration) 32 | { 33 | return false; 34 | } 35 | 36 | string controllerName = values.ContainsKey("controller") 37 | ? ValidationHelper.GetString(values["controller"], "") 38 | : ""; 39 | 40 | if (controllerName.Equals("KenticoFormWidget", StringComparison.InvariantCultureIgnoreCase)) 41 | { 42 | return false; 43 | } 44 | 45 | var page = mDynamicRouteHelper.GetPage(AddPageToCacheDependency: false); 46 | return page != null && !DynamicRouteInternalHelper.UrlSlugExcludedClassNames().Contains(page.ClassName.ToLower()); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /DynamicRouting.Kentico.MVC/DynamicRouteController.cs: -------------------------------------------------------------------------------- 1 | using CMS.DataEngine; 2 | using CMS.Helpers; 3 | using CMS.SiteProvider; 4 | using DynamicRouting.Implementations; 5 | using DynamicRouting.Interfaces; 6 | using Kentico.PageBuilder.Web.Mvc; 7 | using Kentico.Web.Mvc; 8 | using System; 9 | using System.Web.Mvc; 10 | 11 | namespace DynamicRouting.Kentico.MVC 12 | { 13 | public class DynamicRouteController : Controller 14 | { 15 | 16 | private IDynamicRouteHelper mDynamicRouteHelper; 17 | 18 | public DynamicRouteController() 19 | { 20 | mDynamicRouteHelper = new BaseDynamicRouteHelper(); 21 | } 22 | 23 | /// 24 | /// Renders the Dynamic Route View (no model) 25 | /// 26 | /// 27 | public ActionResult RenderView(bool? IncludeDocumentInOutputCache = null) 28 | { 29 | if (!IncludeDocumentInOutputCache.HasValue) 30 | { 31 | IncludeDocumentInOutputCache = DynamicRouteInternalHelper.GetDefaultAddPageToCacheDependency(); 32 | } 33 | // Get default Add Page to Output Dependency 34 | var node = mDynamicRouteHelper.GetPage(AddPageToCacheDependency: IncludeDocumentInOutputCache.Value); 35 | var routeConfig = mDynamicRouteHelper.GetRouteConfiguration(node); 36 | HttpContext.Kentico().PageBuilder().Initialize(node.DocumentID); 37 | 38 | return View(routeConfig.ViewName); 39 | } 40 | 41 | /// 42 | /// Renders the View with either an ITreeNode model or the given Model Type 43 | /// 44 | /// 45 | public ActionResult RenderViewWithModel(bool? IncludeDocumentInOutputCache = null) 46 | { 47 | if (!IncludeDocumentInOutputCache.HasValue) 48 | { 49 | IncludeDocumentInOutputCache = DynamicRouteInternalHelper.GetDefaultAddPageToCacheDependency(); 50 | } 51 | var node = mDynamicRouteHelper.GetPage(AddPageToCacheDependency: IncludeDocumentInOutputCache.Value); 52 | var routeConfig = mDynamicRouteHelper.GetRouteConfiguration(node); 53 | HttpContext.Kentico().PageBuilder().Initialize(node.DocumentID); 54 | 55 | // Convert type 56 | if (routeConfig.ModelType != null) 57 | { 58 | try { 59 | return View(routeConfig.ViewName, Convert.ChangeType(node, routeConfig.ModelType)); 60 | } catch(InvalidCastException ex) 61 | { 62 | throw new InvalidCastException(ex.Message + ", this may be caused by the generated PageType class not being found in the project, or if it's located in an assembly that does not have [assembly: AssemblyDiscoverable] in it's AssemblyInfo.cs. The found page is of type "+(node == null ? "Null" : node.GetType().FullName), ex); 63 | } 64 | } 65 | else 66 | { 67 | return View(routeConfig.ViewName, node); 68 | } 69 | } 70 | 71 | 72 | /// 73 | /// Returns an error message when the page is found but no DynamicRouting assembly tags were configured. 74 | /// 75 | /// 76 | public ActionResult RouteValuesNotFound() 77 | { 78 | var node = mDynamicRouteHelper.GetPage(AddPageToCacheDependency: DynamicRouteInternalHelper.GetDefaultAddPageToCacheDependency()); 79 | return Content($"

No Route Value Found

No DynamicRouting assembly tag was found for the class {node.ClassName}, could not route page {node.NodeAliasPath}

"); 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /DynamicRouting.Kentico.MVC/DynamicRouteHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Web; 2 | using System.Web.Routing; 3 | 4 | namespace DynamicRouting.Kentico.MVC 5 | { 6 | public class DynamicRouteHandler : IRouteHandler 7 | { 8 | public IHttpHandler GetHttpHandler(RequestContext requestContext) 9 | { 10 | return new DynamicHttpHandler(requestContext); 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /DynamicRouting.Kentico.MVC/DynamicRouteHelper.cs: -------------------------------------------------------------------------------- 1 | using CMS.Base; 2 | using DynamicRouting.Implementations; 3 | using DynamicRouting.Kentico.MVC; 4 | using System; 5 | using System.Collections.Generic; 6 | 7 | namespace DynamicRouting 8 | { 9 | [Obsolete("It is recommended you use the IDynamicRouteHelper interface on your constructor instead. You may need to add to your AutoFac ContainerBuilder 'builder.RegisterType(typeof(BaseDynamicRouteHelper)).As(typeof(IDynamicRouteHelper));'")] 10 | public class DynamicRouteHelper 11 | { 12 | public DynamicRouteHelper() 13 | { 14 | } 15 | 16 | /// 17 | /// Gets the CMS Page using Dynamic Routing, returning the culture variation that either matches the given culture or the Slug's culture, or the default site culture if not found. 18 | /// 19 | /// The Url (part after the domain), if empty will use the Current Request 20 | /// The Culture, not needed if the Url contains the culture that the UrlSlug has as part of it's generation. 21 | /// The Site Name, defaults to current site. 22 | /// List of columns you wish to include in the data returned. 23 | /// If true, the found page will have it's DocumentID added to the request's Output Cache Dependency 24 | /// The Page that matches the Url Slug, for the given or matching culture (or default culture if one isn't found). 25 | public static ITreeNode GetPage(string Url = "", string Culture = "", string SiteName = "", IEnumerable Columns = null, bool AddPageToCacheDependency = true) 26 | { 27 | return new BaseDynamicRouteHelper().GetPage(Url, Culture, SiteName, Columns, AddPageToCacheDependency); 28 | } 29 | 30 | /// 31 | /// Gets the CMS Page using Dynamic Routing, returning the culture variation that either matches the given culture or the Slug's culture, or the default site culture if not found. 32 | /// 33 | /// The Url (part after the domain), if empty will use the Current Request 34 | /// The Culture, not needed if the Url contains the culture that the UrlSlug has as part of it's generation. 35 | /// The Site Name, defaults to current site. 36 | /// List of columns you wish to include in the data returned. 37 | /// If true, the found page will have it's DocumentID added to the request's Output Cache Dependency 38 | /// The Page that matches the Url Slug, for the given or matching culture (or default culture if one isn't found). 39 | public static T GetPage(string Url = "", string Culture = "", string SiteName = "", IEnumerable Columns = null, bool AddPageToCacheDependency = true) where T : ITreeNode 40 | { 41 | return new BaseDynamicRouteHelper().GetPage(Url, Culture, SiteName, Columns, AddPageToCacheDependency); 42 | } 43 | 44 | /// 45 | /// Gets the Page's Url Slug based on the given DocumentID and it's Culture. 46 | /// 47 | /// The Document ID 48 | /// 49 | public static string GetPageUrl(int DocumentID) 50 | { 51 | return new BaseDynamicRouteHelper().GetPageUrl(DocumentID); 52 | } 53 | 54 | /// 55 | /// Gets the Page's Url Slug based on the given DocumentGuid and it's Culture. 56 | /// 57 | /// The Document Guid 58 | /// The UrlSlug (with ~ prepended) or Null if page not found. 59 | public static string GetPageUrl(Guid DocumentGuid) 60 | { 61 | return new BaseDynamicRouteHelper().GetPageUrl(DocumentGuid); 62 | } 63 | 64 | /// 65 | /// Gets the Page's Url Slug based on the given NodeAliasPath, Culture and SiteName. If Culture not found, then will prioritize the Site's Default Culture, then Cultures by alphabetical order. 66 | /// 67 | /// The Node alias path you wish to select 68 | /// The Document Culture, if not provided will use default Site's Culture. 69 | /// The Site Name, if not provided then the Current Site's name is used. 70 | /// The UrlSlug (with ~ prepended) or Null if page not found. 71 | public static string GetPageUrl(string NodeAliasPath, string DocumentCulture = null, string SiteName = null) 72 | { 73 | return new BaseDynamicRouteHelper().GetPageUrl(NodeAliasPath, DocumentCulture, SiteName); 74 | } 75 | 76 | /// 77 | /// Gets the Page's Url Slug based on the given NodeGuid and Culture. If Culture not found, then will prioritize the Site's Default Culture, then Cultures by alphabetical order. 78 | /// 79 | /// The Node to find the Url Slug 80 | /// The Document Culture, if not provided will use default Site's Culture. 81 | /// The UrlSlug (with ~ prepended) or Null if page not found. 82 | public static string GetPageUrl(Guid NodeGuid, string DocumentCulture = null) 83 | { 84 | return new BaseDynamicRouteHelper().GetPageUrl(NodeGuid, DocumentCulture); 85 | } 86 | 87 | /// 88 | /// Gets the Page's Url Slug based on the given NodeID and Culture. If Culture not found, then will prioritize the Site's Default Culture, then Cultures by alphabetical order. 89 | /// 90 | /// The NodeID 91 | /// The Document Culture, if not provided will use default Site's Culture. 92 | /// The Site Name, if not provided then will query the NodeID to find it's site. 93 | /// The UrlSlug (with ~ prepended) or Null if page not found. 94 | public static string GetPageUrl(int NodeID, string DocumentCulture = null, string SiteName = null) 95 | { 96 | return new BaseDynamicRouteHelper().GetPageUrl(NodeID, DocumentCulture, SiteName); 97 | } 98 | 99 | /// 100 | /// Gets the Route Configuration based on The node's Class Name. 101 | /// 102 | /// The ITreeNode object 103 | /// The Route Configuration, empty DynamicRouteconfiguration if not found 104 | public static DynamicRouteConfiguration GetRouteConfiguration(ITreeNode node) 105 | { 106 | return new BaseDynamicRouteHelper().GetRouteConfiguration(node); 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /DynamicRouting.Kentico.MVC/DynamicRouteInitializationModule_MVC.cs: -------------------------------------------------------------------------------- 1 | using CMS; 2 | using CMS.DataEngine; 3 | using DynamicRouting.Kentico.MVC; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | [assembly: RegisterModule(typeof(DynamicRouteInitializationModule_MVC))] 11 | 12 | namespace DynamicRouting.Kentico.MVC 13 | { 14 | public class DynamicRouteInitializationModule_MVC : Module 15 | { 16 | public DynamicRouteInitializationModule_MVC() : base("DynamicRouteInitializationModule_MVC") 17 | { 18 | 19 | } 20 | 21 | protected override void OnInit() 22 | { 23 | base.OnInit(); 24 | 25 | // Call OnInit of the Base 26 | var BaseInitializationModule = new DynamicRouteInitializationModule_Base(); 27 | BaseInitializationModule.Init(); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /DynamicRouting.Kentico.MVC/DynamicRouteTemplateController.cs: -------------------------------------------------------------------------------- 1 | using CMS.Base; 2 | using CMS.DocumentEngine; 3 | using DynamicRouting.Implementations; 4 | using DynamicRouting.Interfaces; 5 | using Kentico.PageBuilder.Web.Mvc; 6 | using Kentico.PageBuilder.Web.Mvc.PageTemplates; 7 | using Kentico.Web.Mvc; 8 | using System.Web.Mvc; 9 | 10 | 11 | namespace DynamicRouting.Kentico.MVC 12 | { 13 | public class DynamicRouteTemplateController : PageTemplateController 14 | { 15 | private IDynamicRouteHelper mDynamicRouteHelper; 16 | 17 | public DynamicRouteTemplateController() 18 | { 19 | this.mDynamicRouteHelper = new BaseDynamicRouteHelper(); 20 | } 21 | 22 | /// 23 | /// Gets the node based on the current request url and then renders the template result. 24 | /// 25 | public ActionResult Index(string TemplateControllerName = null, bool? IncludeDocumentInOutputCache = null) 26 | { 27 | if (!IncludeDocumentInOutputCache.HasValue) 28 | { 29 | IncludeDocumentInOutputCache = DynamicRouteInternalHelper.GetDefaultAddPageToCacheDependency(); 30 | } 31 | ITreeNode FoundNode = mDynamicRouteHelper.GetPage(Columns: new string[] { "DocumentID" }, AddPageToCacheDependency: IncludeDocumentInOutputCache.Value); 32 | if (FoundNode != null) 33 | { 34 | HttpContext.Kentico().PageBuilder().Initialize(FoundNode.DocumentID); 35 | if (!string.IsNullOrWhiteSpace(TemplateControllerName)) 36 | { 37 | // Adjust the route data to point to the template's controller if it has one. 38 | HttpContext.Request.RequestContext.RouteData.Values["Controller"] = TemplateControllerName; 39 | } 40 | return new TemplateResult(FoundNode.DocumentID); 41 | } 42 | else 43 | { 44 | return new HttpNotFoundResult(); 45 | } 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /DynamicRouting.Kentico.MVC/DynamicRouting.Kentico.MVC.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $id$ 5 | $version$ 6 | $title$ 7 | Kentico Community 8 | Kentico Community 9 | https://github.com/KenticoDevTrev/DynamicRouting 10 | http://www.kentico.com/favicon.ico 11 | false 12 | $description$ 13 | Fixed bug in the LocalizationContext using the CultureName vs. CultureCode 14 | Copyright 2019 Kentico Community 15 | Dynamic Routing, Kentico, MVC 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /DynamicRouting.Kentico.MVC/DynamicRoutingAnalyzer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | 6 | namespace DynamicRouting.Kentico.MVC 7 | { 8 | public static class DynamicRoutingAnalyzer 9 | { 10 | private static readonly Dictionary classNameLookup = 11 | new Dictionary(); 12 | 13 | static DynamicRoutingAnalyzer() 14 | { 15 | var attributes = AppDomain 16 | .CurrentDomain 17 | .GetAssemblies() 18 | .Where(a => !a.FullName.StartsWith("CMS.") && !a.FullName.StartsWith("Kentico.")) 19 | .SelectMany(a => a.GetCustomAttributes()); 20 | 21 | foreach (var attribute in attributes) 22 | { 23 | if (attribute == null) 24 | { 25 | continue; 26 | } 27 | foreach (string pageClassName in attribute.PageClassNames) 28 | { 29 | string pageClassNameLookup = pageClassName.ToLowerInvariant(); 30 | if (classNameLookup.TryGetValue(pageClassNameLookup, out var pair)) 31 | { 32 | throw new Exception( 33 | "Duplicate Annotation: " + 34 | $"{pair.ControllerName}Controller.{pair.ActionName} already registered for NodeClassName {pageClassNameLookup}. " + 35 | $"Cannot be registered for {attribute.ControllerName}.{attribute.ActionMethodName}" 36 | ); 37 | } 38 | 39 | classNameLookup.Add(pageClassNameLookup, new DynamicRouteConfiguration( 40 | controllerName: attribute.ControllerName, 41 | actionName: attribute.ActionMethodName, 42 | viewName: attribute.ViewName, 43 | modelType: attribute.ModelType, 44 | routeType: attribute.RouteType, 45 | includeDocumentInOutputCache: attribute.IncludeDocumentInOutputCache, 46 | useOutputCaching: attribute.UseOutputCaching 47 | )); 48 | } 49 | } 50 | } 51 | 52 | public static bool TryFindMatch(string nodeClassName, out DynamicRouteConfiguration match) 53 | { 54 | return classNameLookup.TryGetValue(nodeClassName.ToLowerInvariant(), out match); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /DynamicRouting.Kentico.MVC/DynamicRoutingAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | 4 | namespace DynamicRouting.Kentico.MVC 5 | { 6 | /// 7 | /// Marks the given as the handler for HTTP requests 8 | /// for the specified matching 9 | /// for custom Page Types. 10 | /// 11 | [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] 12 | public class DynamicRoutingAttribute : Attribute 13 | { 14 | /// 15 | /// Marks the given as the handler for HTTP requests 16 | /// for the specified matching 17 | /// for custom Page Types. 18 | /// 19 | /// The Controller Type 20 | /// The Page Class Names 21 | /// The Optional Action Method in the Controller to handle this request. 22 | public DynamicRoutingAttribute(Type controllerType, string[] pageClassNames, string actionMethodName = null) 23 | { 24 | if (controllerType is null) 25 | { 26 | throw new ArgumentNullException(nameof(controllerType)); 27 | } 28 | 29 | if (pageClassNames is null) 30 | { 31 | throw new ArgumentNullException(nameof(pageClassNames)); 32 | } 33 | 34 | ControllerName = controllerType.ControllerNamePrefix(); 35 | 36 | if (!string.IsNullOrWhiteSpace(actionMethodName)) { 37 | ActionMethodName = actionMethodName; 38 | } else 39 | { 40 | ActionMethodName = "Index"; 41 | } 42 | 43 | PageClassNames = pageClassNames 44 | .Select(n => n.ToLowerInvariant()) 45 | .ToArray(); 46 | RouteType = DynamicRouteType.Controller; 47 | UseOutputCaching = false; 48 | } 49 | 50 | /// 51 | /// Pages with the given Class Name will be routed to this View with the given Type (inheriting from ITreeNode) 52 | /// 53 | /// The View name that should be rendered. 54 | /// The Model that inherits 55 | /// The Class Name that this Dynamic Route applies to. 56 | /// If true, will add the Document ID to the repsonse's Cache Dependencies 57 | /// If true, will use an Output Cached Controller to render this. 58 | 59 | public DynamicRoutingAttribute(string viewName, Type modelType, string pageClassName, bool includeDocumentInOutputCache = true, bool useOutputCaching = false) 60 | { 61 | if (string.IsNullOrWhiteSpace(viewName)) 62 | { 63 | throw new ArgumentNullException(nameof(viewName)); 64 | } 65 | 66 | if (modelType is null) 67 | { 68 | throw new ArgumentNullException(nameof(modelType)); 69 | } 70 | 71 | if (string.IsNullOrWhiteSpace(pageClassName)) 72 | { 73 | throw new ArgumentNullException(nameof(pageClassName)); 74 | } 75 | 76 | ViewName = viewName; 77 | ModelType = modelType; 78 | PageClassNames = new string[] { pageClassName }; 79 | RouteType = DynamicRouteType.ViewWithModel; 80 | UseOutputCaching = useOutputCaching; 81 | IncludeDocumentInOutputCache = includeDocumentInOutputCache; 82 | } 83 | 84 | /// 85 | /// Pages with the given Class Names will be routed to this View. 86 | /// 87 | /// The View name that should be rendered. 88 | /// The Class Names that this Dynamic Route applies to. 89 | /// Will pass the page as the model for this view. If false, will not pass a model. 90 | /// If true, will add the Document ID to the repsonse's Cache Dependencies 91 | /// If true, will use an Output Cached Controller to render this. 92 | public DynamicRoutingAttribute(string viewName, string[] pageClassNames, bool IncludePageModel = true, bool includeDocumentInOutputCache = true, bool useOutputCaching = false) 93 | { 94 | if (string.IsNullOrWhiteSpace(viewName)) 95 | { 96 | throw new ArgumentNullException(nameof(viewName)); 97 | } 98 | 99 | if (pageClassNames is null) 100 | { 101 | throw new ArgumentNullException(nameof(pageClassNames)); 102 | } 103 | 104 | ViewName = viewName; 105 | PageClassNames = pageClassNames 106 | .Select(n => n.ToLowerInvariant()) 107 | .ToArray(); 108 | 109 | if(IncludePageModel) 110 | { 111 | RouteType = DynamicRouteType.ViewWithModel; 112 | } else 113 | { 114 | RouteType = DynamicRouteType.View; 115 | } 116 | UseOutputCaching = useOutputCaching; 117 | IncludeDocumentInOutputCache = includeDocumentInOutputCache; 118 | } 119 | 120 | /// 121 | /// The name of the that 122 | /// handles requests for routes for the given 123 | /// 124 | public string ControllerName { get; } 125 | 126 | /// 127 | /// values. 128 | /// 129 | public string[] PageClassNames { get; } 130 | 131 | /// 132 | /// The name of the action method that will handle the request 133 | /// 134 | public string ActionMethodName { get; } 135 | 136 | public Type ModelType { get; } 137 | 138 | public string ViewName { get; } 139 | 140 | public bool UseOutputCaching { get; } 141 | 142 | public bool IncludeDocumentInOutputCache { get; } 143 | 144 | public DynamicRouteType RouteType { get; } 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /DynamicRouting.Kentico.MVC/EmptyPageTemplate.cs: -------------------------------------------------------------------------------- 1 | using Kentico.PageBuilder.Web.Mvc.PageTemplates; 2 | 3 | // This is used as a trigger for the given page to ignore Page Templates, as Kentico by default requires a page template if one is selectable. 4 | [assembly: RegisterPageTemplate("Empty.Template", "No Template", customViewName: "", Description = "No Template (Use standard Routing)", IconClass = "icon-modal-close")] -------------------------------------------------------------------------------- /DynamicRouting.Kentico.MVC/EmptyPageTemplateFilter.cs: -------------------------------------------------------------------------------- 1 | using Kentico.PageBuilder.Web.Mvc.PageTemplates; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace DynamicRouting.Kentico.MVC 9 | { 10 | /// 11 | /// This is to prevent a template from automatically being assigned. If there is at least 1 non-empty template that is available, this will add the "Empty" template as an option so the user can select. 12 | /// 13 | public class EmptyPageTemplateFilter : IPageTemplateFilter 14 | { 15 | public IEnumerable Filter(IEnumerable pageTemplates, PageTemplateFilterContext context) 16 | { 17 | // only add empty option if there is 1 non empty template remaining, so user has to choose. 18 | var NonEmptyTemplates = pageTemplates.Where(t => !GetTemplates().Contains(t.Identifier)); 19 | if (NonEmptyTemplates.Count() > 0) 20 | { 21 | return pageTemplates; 22 | } 23 | else 24 | { 25 | // Remove the empty template as an option 26 | return pageTemplates.Where(t => !GetTemplates().Contains(t.Identifier)); 27 | } 28 | } 29 | 30 | // Gets all page templates that are allowed for landing pages 31 | public IEnumerable GetTemplates() => new string[] { "Empty.Template" }; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /DynamicRouting.Kentico.MVC/Events/DynamicUrlEvents.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.Text; 4 | using System.Threading.Tasks; 5 | 6 | namespace DynamicRouting 7 | { 8 | 9 | public static class DynamicRoutingEvents 10 | { 11 | /// 12 | /// Overwrites the handling of finding a page based on the request. 13 | /// 14 | public static GetPageEventHandler GetPage; 15 | 16 | /// 17 | /// Allows overwrite of how to get the current culture 18 | /// 19 | public static GetCultureEventHandler GetCulture; 20 | 21 | /// 22 | /// Allows you to adjust the MVC Routing by modifying the Request Context 23 | /// 24 | public static RequestRoutingEventHandler RequestRouting; 25 | 26 | static DynamicRoutingEvents() 27 | { 28 | GetPage = new GetPageEventHandler() 29 | { 30 | Name = "DynamicRoutingEvents.GetPage" 31 | }; 32 | 33 | GetCulture = new GetCultureEventHandler() 34 | { 35 | Name = "DynamicRoutingEvents.GetCulture" 36 | }; 37 | 38 | RequestRouting = new RequestRoutingEventHandler() 39 | { 40 | Name = "DynamicRoutingEvents.RequestRouting" 41 | }; 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /DynamicRouting.Kentico.MVC/Events/GetCultureEventArgs.cs: -------------------------------------------------------------------------------- 1 | using CMS.Base; 2 | using CMS.DocumentEngine; 3 | using System; 4 | using System.Web; 5 | 6 | namespace DynamicRouting 7 | { 8 | public class GetCultureEventArgs : CMSEventArgs 9 | { 10 | /// 11 | /// The Culture, this is what you should set when determining the culture 12 | /// 13 | public string Culture { get; set; } 14 | 15 | /// 16 | /// The Site's Default culture, based on the Current Site 17 | /// 18 | public string DefaultCulture { get; set; } 19 | 20 | /// 21 | /// The Site Code Name of the current site 22 | /// 23 | public string SiteName { get; set; } 24 | 25 | /// 26 | /// The HttpRequest 27 | /// 28 | public HttpRequest Request { get; set; } 29 | 30 | /// 31 | /// True if Kentico's Preview is enable, if true the culture will be set by the PreviewEnabled after the "Before" event. 32 | /// 33 | public bool PreviewEnabled { get; set; } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /DynamicRouting.Kentico.MVC/Events/GetCultureEventHandler.cs: -------------------------------------------------------------------------------- 1 | using CMS.Base; 2 | using DynamicRouting; 3 | 4 | namespace DynamicRouting 5 | { 6 | public class GetCultureEventHandler : AdvancedHandler 7 | { 8 | public GetCultureEventHandler() 9 | { 10 | 11 | } 12 | 13 | public GetCultureEventHandler StartEvent(GetCultureEventArgs CultureArgs) 14 | { 15 | return base.StartEvent(CultureArgs); 16 | } 17 | 18 | public void FinishEvent() 19 | { 20 | base.Finish(); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /DynamicRouting.Kentico.MVC/Events/GetPageEventArgs.cs: -------------------------------------------------------------------------------- 1 | using CMS.Base; 2 | using CMS.DocumentEngine; 3 | using System; 4 | using System.Web; 5 | 6 | namespace DynamicRouting 7 | { 8 | public class GetPageEventArgs : CMSEventArgs 9 | { 10 | /// 11 | /// The Page that is found, this is what will be returned from the GetPage function, set this. 12 | /// 13 | public ITreeNode FoundPage { get; set; } 14 | 15 | /// 16 | /// The Request's Relative Url (no query strings), cleaned to be proper lookup format 17 | /// 18 | public string RelativeUrl { get; set; } 19 | 20 | /// 21 | /// The Request's Culture 22 | /// 23 | public string Culture { get; set; } 24 | 25 | /// 26 | /// The Site's default culture 27 | /// 28 | public string DefaultCulture { get; set; } 29 | 30 | /// 31 | /// The current SiteName 32 | /// 33 | public string SiteName { get; set; } 34 | 35 | /// 36 | /// If Kentico's Preview mode is active (Preview/Edit) 37 | /// 38 | public bool PreviewEnabled { get; set; } 39 | 40 | /// 41 | /// The User's requested Columns to return with the page data 42 | /// 43 | public string ColumnsVal { get; set; } 44 | 45 | /// 46 | /// The full HttpRequest object 47 | /// 48 | public HttpRequest Request { get; set; } 49 | 50 | /// 51 | /// If an exception occurred between the Before and After (while looking up), this is the exception. Can be used for custom logging. 52 | /// 53 | public Exception ExceptionOnLookup { get; set; } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /DynamicRouting.Kentico.MVC/Events/GetPageEventHandler.cs: -------------------------------------------------------------------------------- 1 | using CMS.Base; 2 | using DynamicRouting; 3 | 4 | namespace DynamicRouting 5 | { 6 | public class GetPageEventHandler : AdvancedHandler 7 | { 8 | public GetPageEventHandler() 9 | { 10 | 11 | } 12 | 13 | public GetPageEventHandler StartEvent(GetPageEventArgs PageArgs) 14 | { 15 | return base.StartEvent(PageArgs); 16 | } 17 | 18 | public void FinishEvent() 19 | { 20 | base.Finish(); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /DynamicRouting.Kentico.MVC/Events/RequestRoutingEventArgs.cs: -------------------------------------------------------------------------------- 1 | using CMS.Base; 2 | using CMS.DocumentEngine; 3 | using DynamicRouting.Kentico.MVC; 4 | using System; 5 | using System.Web; 6 | using System.Web.Routing; 7 | 8 | namespace DynamicRouting 9 | { 10 | public class RequestRoutingEventArgs : CMSEventArgs 11 | { 12 | /// 13 | /// The Page found by the Dynamic Routing 14 | /// 15 | public ITreeNode Page { get; set; } 16 | 17 | /// 18 | /// The Route Configuration that was determined by the MVC DynamicRouting attributes. 19 | /// The RouteData's Controller and Action values are automatically set to the Configuration.ControllerName and Configuration.ActionName 20 | /// UseOutputCaching only applies to the DynamicRoute Controller, and IncludeDocumentInOutputCache applies to Template and DynamicRoute Controllers 21 | /// 22 | /// 23 | public DynamicRouteConfiguration Configuration { get; set; } 24 | 25 | /// 26 | /// The Request Context, you can adjust the Route values through RequestContext.RouteData.Values. 27 | /// Configuration.ControllerName = "Blog"; 28 | /// Configuration.ActionName = "Listing"; 29 | /// CurrentRequestContext.RouteData.Values["SomeProperty"] = "Hello"; 30 | /// This would route to the BlogController.Listing(string SomePropertyName) with SomePropertyName equalling Hello 31 | /// 32 | public RequestContext CurrentRequestContext { get; set; } 33 | 34 | public RequestRoutingEventArgs() 35 | { 36 | 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /DynamicRouting.Kentico.MVC/Events/RequestRoutingEventHandler.cs: -------------------------------------------------------------------------------- 1 | using CMS.Base; 2 | using DynamicRouting; 3 | 4 | namespace DynamicRouting 5 | { 6 | public class RequestRoutingEventHandler : AdvancedHandler 7 | { 8 | public RequestRoutingEventHandler() 9 | { 10 | 11 | } 12 | 13 | public RequestRoutingEventHandler StartEvent(RequestRoutingEventArgs RequestArgs) 14 | { 15 | return base.StartEvent(RequestArgs); 16 | } 17 | 18 | public void FinishEvent() 19 | { 20 | base.Finish(); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /DynamicRouting.Kentico.MVC/Interfaces/IDynamicRouteHelper.cs: -------------------------------------------------------------------------------- 1 | using CMS.Base; 2 | using DynamicRouting.Kentico.MVC; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace DynamicRouting.Interfaces 10 | { 11 | public interface IDynamicRouteHelper 12 | { 13 | /// 14 | /// Gets the CMS Page using Dynamic Routing, returning the culture variation that either matches the given culture or the Slug's culture, or the default site culture if not found. 15 | /// 16 | /// The Url (part after the domain), if empty will use the Current Request 17 | /// The Culture, not needed if the Url contains the culture that the UrlSlug has as part of it's generation. 18 | /// The Site Name, defaults to current site. 19 | /// List of columns you wish to include in the data returned. 20 | /// If true, the found page will have it's DocumentID added to the request's Output Cache Dependency 21 | /// The Page that matches the Url Slug, for the given or matching culture (or default culture if one isn't found). 22 | ITreeNode GetPage(string Url = "", string Culture = "", string SiteName = "", IEnumerable Columns = null, bool AddPageToCacheDependency = true); 23 | 24 | /// 25 | /// Gets the CMS Page using Dynamic Routing, returning the culture variation that either matches the given culture or the Slug's culture, or the default site culture if not found. 26 | /// 27 | /// The Url (part after the domain), if empty will use the Current Request 28 | /// The Culture, not needed if the Url contains the culture that the UrlSlug has as part of it's generation. 29 | /// The Site Name, defaults to current site. 30 | /// List of columns you wish to include in the data returned. 31 | /// If true, the found page will have it's DocumentID added to the request's Output Cache Dependency 32 | /// The Page that matches the Url Slug, for the given or matching culture (or default culture if one isn't found). 33 | T GetPage(string Url = "", string Culture = "", string SiteName = "", IEnumerable Columns = null, bool AddPageToCacheDependency = true) where T : ITreeNode; 34 | 35 | 36 | /// 37 | /// Gets the Page's Url Slug based on the given DocumentID and it's Culture. 38 | /// 39 | /// The Document ID 40 | /// 41 | string GetPageUrl(int DocumentID); 42 | 43 | 44 | /// 45 | /// Gets the Page's Url Slug based on the given DocumentGuid and it's Culture. 46 | /// 47 | /// The Document Guid 48 | /// The UrlSlug (with ~ prepended) or Null if page not found. 49 | string GetPageUrl(Guid DocumentGuid); 50 | 51 | 52 | /// 53 | /// Gets the Page's Url Slug based on the given NodeAliasPath, Culture and SiteName. If Culture not found, then will prioritize the Site's Default Culture, then Cultures by alphabetical order. 54 | /// 55 | /// The Node alias path you wish to select 56 | /// The Document Culture, if not provided will use default Site's Culture. 57 | /// The Site Name, if not provided then the Current Site's name is used. 58 | /// The UrlSlug (with ~ prepended) or Null if page not found. 59 | string GetPageUrl(string NodeAliasPath, string DocumentCulture = null, string SiteName = null); 60 | 61 | 62 | /// 63 | /// Gets the Page's Url Slug based on the given NodeGuid and Culture. If Culture not found, then will prioritize the Site's Default Culture, then Cultures by alphabetical order. 64 | /// 65 | /// The Node to find the Url Slug 66 | /// The Document Culture, if not provided will use default Site's Culture. 67 | /// The UrlSlug (with ~ prepended) or Null if page not found. 68 | string GetPageUrl(Guid NodeGuid, string DocumentCulture = null); 69 | 70 | 71 | /// 72 | /// Gets the Page's Url Slug based on the given NodeID and Culture. If Culture not found, then will prioritize the Site's Default Culture, then Cultures by alphabetical order. 73 | /// 74 | /// The NodeID 75 | /// The Document Culture, if not provided will use default Site's Culture. 76 | /// The Site Name, if not provided then will query the NodeID to find it's site. 77 | /// The UrlSlug (with ~ prepended) or Null if page not found. 78 | string GetPageUrl(int NodeID, string DocumentCulture = null, string SiteName = null); 79 | 80 | 81 | /// 82 | /// Gets the Route Configuration based on The node's Class Name. 83 | /// 84 | /// The ITreeNode object 85 | /// The Route Configuration, empty DynamicRouteconfiguration if not found 86 | DynamicRouteConfiguration GetRouteConfiguration(ITreeNode node); 87 | 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /DynamicRouting.Kentico.MVC/NoEmptyPageTemplateFilter.cs: -------------------------------------------------------------------------------- 1 | using Kentico.PageBuilder.Web.Mvc.PageTemplates; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace DynamicRouting.Kentico.MVC 6 | { 7 | /// 8 | /// Removes the Empty.Template always from the options, thus disabling it. 9 | /// 10 | public class NoEmptyPageTemplateFilter : IPageTemplateFilter 11 | { 12 | public IEnumerable Filter(IEnumerable pageTemplates, PageTemplateFilterContext context) 13 | { 14 | // Remove Empty.Template always 15 | return pageTemplates.Where(t => !GetTemplates().Contains(t.Identifier)); 16 | } 17 | 18 | // Gets all page templates that are allowed for landing pages 19 | public IEnumerable GetTemplates() => new string[] { "Empty.Template" }; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /DynamicRouting.Kentico.MVC/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | using CMS; 5 | 6 | // General Information about an assembly is controlled through the following 7 | // set of attributes. Change these attribute values to modify the information 8 | // associated with an assembly. 9 | [assembly: AssemblyTitle("DynamicRouting.Kentico.MVC")] 10 | [assembly: AssemblyDescription("Allows for automatic detection of pages based on URL and routing based on refractoring. For the MVC Site only.")] 11 | [assembly: AssemblyConfiguration("")] 12 | [assembly: AssemblyCompany("")] 13 | [assembly: AssemblyProduct("DynamicRouting.Kentico.MVC")] 14 | [assembly: AssemblyCopyright("2019 Kentico Community")] 15 | [assembly: AssemblyTrademark("")] 16 | [assembly: AssemblyCulture("")] 17 | [assembly: AssemblyDiscoverable()] 18 | 19 | // Setting ComVisible to false makes the types in this assembly not visible 20 | // to COM components. If you need to access a type in this assembly from 21 | // COM, set the ComVisible attribute to true on that type. 22 | [assembly: ComVisible(false)] 23 | 24 | // The following GUID is for the ID of the typelib if this project is exposed to COM 25 | [assembly: Guid("8a9f8e7f-c18a-4d90-924a-27b50f5e51ff")] 26 | 27 | // Version information for an assembly consists of the following four values: 28 | // 29 | // Major Version 30 | // Minor Version 31 | // Build Number 32 | // Revision 33 | // 34 | // You can specify all the values or you can default the Build and Revision Numbers 35 | // by using the '*' as shown below: 36 | // [assembly: AssemblyVersion("1.0.*")] 37 | [assembly: AssemblyVersion("12.29.20.0")] 38 | [assembly: AssemblyFileVersion("12.29.20.0")] 39 | -------------------------------------------------------------------------------- /DynamicRouting.Kentico.MVC/StaticRoutePriorityAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace DynamicRouting.Kentico.MVC 4 | { 5 | /// 6 | /// This attribute indicates that Routes that map to this Controller should be enforced even if a page is found via Dynamic Routing. 7 | /// Example: If the Controller is ApiController and a request comes through as /api/GetItems, without this attribute if someone creates 8 | /// a page that has a UrlSlug of /api/GetItems then the request would be dynamically routed. With this attribute, the request would 9 | /// properly go to your ApiController class. 10 | /// 11 | [AttributeUsage(AttributeTargets.Class)] 12 | public class StaticRoutePriorityAttribute : Attribute 13 | { 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /DynamicRouting.Kentico.MVC/StaticRoutePriorityConstraint.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.Web; 7 | using System.Web.Mvc; 8 | using System.Web.Routing; 9 | using CacheHelper = CMS.Helpers.CacheHelper; 10 | using CacheSettings = CMS.Helpers.CacheSettings; 11 | 12 | namespace DynamicRouting.Kentico.MVC 13 | { 14 | /// 15 | /// Checks if the Controller the route is mapping to has the StaticRoutePriorityAttribute 16 | /// 17 | public class StaticRoutePriorityConstraint : IRouteConstraint 18 | { 19 | public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection) 20 | { 21 | // Check if the controller is found and has the KMVCRouteOverPathPriority attribute. 22 | string ControllerName = (values.ContainsKey("controller") ? values["controller"].ToString() : ""); 23 | return CacheHelper.Cache(cs => 24 | { 25 | // Check if the Route that it found has the override 26 | IControllerFactory factory = ControllerBuilder.Current.GetControllerFactory(); 27 | try 28 | { 29 | var Controller = factory.CreateController(new RequestContext(httpContext, new RouteData(route, null)), ControllerName); 30 | return Attribute.GetCustomAttribute(Controller.GetType(), typeof(StaticRoutePriorityAttribute)) != null; 31 | } 32 | catch (Exception) 33 | { 34 | return false; 35 | } 36 | }, new CacheSettings(1440, "StaticRoutePriorityConstraint", ControllerName)); 37 | } 38 | } 39 | 40 | 41 | } -------------------------------------------------------------------------------- /DynamicRouting.Kentico.MVC/app.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /DynamicRouting.Kentico.MVC/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /DynamicRouting.Kentico.Mother/App_Data/Global/Resources/Kentico.Builder.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | The builder got into invalid state when changing sections. Please reload the builder. 122 | 123 | 124 | The custom element '{0}' has been already registered. 125 | 126 | 127 | An error has occurred. If the problem persists, contact your system administrator. 128 | 129 | 130 | Please select an option 131 | 132 | 133 | The inline editor '{0}' has been already registered. 134 | 135 | 136 | Expected property editor identifier of type string when registering an inline editor. 137 | 138 | 139 | The inline editor '{0}' is missing init function. 140 | 141 | 142 | Expected a function argument when registering an inline editor '{0}' 143 | 144 | 145 | The inline editor '{0}' is missing a property name data attribute or its value is empty. 146 | 147 | 148 | Registration of the inline editor '{0}' failed. 149 | 150 | 151 | The handlers of inline editor '{0}' are not of type object. 152 | 153 | 154 | Message received from forbidden origin: {0}. 155 | 156 | 157 | Loading 158 | 159 | 160 | Apply 161 | 162 | 163 | Cancel 164 | 165 | 166 | Close 167 | 168 | -------------------------------------------------------------------------------- /DynamicRouting.Kentico.Mother/App_Data/Global/Resources/Kentico.Components.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | Select media file 122 | 123 | 124 | Select 125 | 126 | 127 | Media file selector 128 | 129 | 130 | The media library was not found. 131 | 132 | 133 | You are not authorized to view the media library content. 134 | 135 | 136 | This folder is empty 137 | 138 | 139 | The selected file(s) are not valid. 140 | 141 | 142 | Collapse 143 | 144 | 145 | Expand 146 | 147 | 148 | clear the selection 149 | 150 | 151 | or 152 | 153 | 154 | Select different file 155 | 156 | 157 | {0} out of {1} files selected 158 | 159 | 160 | Library: 161 | 162 | 163 | Drop file here or click to browse 164 | 165 | 166 | Select media files 167 | 168 | 169 | Filter the folder 170 | 171 | 172 | No items match your filter.<br />Perhaps they hide in a different folder. 173 | 174 | 175 | Show all 176 | 177 | 178 | An error occurred while uploading the file '{0}'. Contact your system administrator. 179 | 180 | 181 | Change file 182 | 183 | 184 | Remove file 185 | 186 | 187 | Clear all 188 | 189 | 190 | No items selected. 191 | 192 | 193 | Page selector 194 | 195 | 196 | Select page 197 | 198 | 199 | Selected page 200 | 201 | 202 | Select 203 | 204 | 205 | Clear 206 | 207 | 208 | No page selected 209 | 210 | 211 | Invalid page 212 | 213 | 214 | The selected page has been deleted or you don't have permissions to select it. 215 | 216 | 217 | The selected page has been deleted or you don't have permissions to select it. Please select a different page. 218 | 219 | 220 | {0} files selected 221 | 222 | 223 | Media files could not be loaded. Contact your system administrator. 224 | 225 | 226 | Type of the file '{0}' is not allowed. 227 | 228 | 229 | All files were successfully uploaded. 230 | 231 | 232 | Preconfigured root page '{0}' was not found. 233 | 234 | 235 | Close 236 | 237 | 238 | Error 239 | 240 | 241 | Success 242 | 243 | 244 | Upload of the file '{0}' timed out. 245 | 246 | 247 | Missing file 248 | 249 | 250 | File error 251 | 252 | 253 | Type of the selected media file is not allowed or you don't have permissions to see the file. 254 | 255 | 256 | Selected media file has been deleted. 257 | 258 | 259 | Path selector 260 | 261 | 262 | Select 263 | 264 | 265 | Select 266 | 267 | 268 | You are not authorized to view child pages of '{0}' page. 269 | 270 | -------------------------------------------------------------------------------- /DynamicRouting.Kentico.Mother/App_Data/Global/Resources/Kentico.PageBuilder.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | Some widgets are not allowed here! 122 | 123 | 124 | This widget is not allowed here! 125 | 126 | 127 | The Page builder is initialized with missing page identifier. 128 | 129 | 130 | Add section 131 | 132 | 133 | Delete section 134 | 135 | 136 | Move section 137 | 138 | 139 | Sections 140 | 141 | 142 | Sections with the following identifiers are not registered in the system: {0}. Saving the page will result in lost data. 143 | 144 | 145 | Are you sure you want to remove the section? 146 | 147 | 148 | Change section type 149 | 150 | 151 | Add widget 152 | 153 | 154 | Delete widget 155 | 156 | 157 | Move widget 158 | 159 | 160 | Widgets 161 | 162 | 163 | Widgets with the following identifiers are not registered in the system: {0}. Saving the page will result in lost data. 164 | 165 | 166 | Personalize 167 | 168 | 169 | Are you sure you want to remove the selected widget? 170 | 171 | 172 | Display variant 173 | 174 | 175 | Condition types 176 | 177 | 178 | Personalize content based on: 179 | 180 | 181 | No widgets found. 182 | 183 | 184 | Some widgets cannot be displayed, because sections with the following identifiers do not contain enough widget zones: {0}. Saving the page will result in lost widget data. 185 | 186 | 187 | Condition types with the following identifiers are not registered in the system: {0}. Saving the page will result in lost data. 188 | 189 | 190 | Original 191 | 192 | 193 | Apply 194 | 195 | 196 | Back 197 | 198 | 199 | Delete variant 200 | 201 | 202 | Are you sure you want to remove the variant? 203 | 204 | 205 | Sections with the following section identifiers do not contain any widget zones: {0}. Sections without widget zones are not supported. 206 | 207 | 208 | Add variant 209 | 210 | 211 | Add new personalization variant 212 | 213 | 214 | Partial view of the condition type configuration must contain a form. 215 | 216 | 217 | Invalid format of response of the configuration form POST message. Expected is one of: application/json OR text/html. 218 | 219 | 220 | Areas with the following identifiers are missing from page markup: {0}. Saving the page will result in lost data. 221 | 222 | 223 | Configure widget 224 | 225 | 226 | Configure section 227 | 228 | 229 | Configure template 230 | 231 | 232 | Edit variant 233 | 234 | 235 | Display one of the variants: 236 | 237 | 238 | {0} properties 239 | 240 | 241 | Are you sure you want to close the dialog? 242 | 243 | You have unsaved changes. 244 | 245 | Press OK to continue or Cancel to stay on the current page. 246 | 247 | 248 | Apply variant changes 249 | 250 | 251 | Apply 252 | 253 | 254 | Apply properties 255 | 256 | 257 | Variant name 258 | 259 | 260 | Please provide variant name. 261 | 262 | 263 | Cancel 264 | 265 | 266 | Variant name wasn't found in form POST data. 267 | 268 | 269 | Change priority 270 | 271 | 272 | Default 273 | 274 | 275 | Page builder built-in section. 276 | 277 | 278 | Change template 279 | 280 | 281 | Change template 282 | 283 | 284 | You can now select a new template for your page. Note that changing the template may result in loss of your widget data.<br> 285 | <a href="{0}" target="_blank">Read more about page templates in our documentation</a> 286 | 287 | 288 | Are you sure you want to change the page template? 289 | 290 | All page content, i.e. widgets and sections with their data and properties, will be lost. 291 | 292 | Press OK to continue or Cancel to keep this template. 293 | 294 | -------------------------------------------------------------------------------- /DynamicRouting.Kentico.Mother/BuildPackage.bat: -------------------------------------------------------------------------------- 1 | nuget pack DynamicRouting.csproj -Prop Configuration=Release 2 | @echo off 3 | set /p DUMMY="Hit ENTER to exit..." -------------------------------------------------------------------------------- /DynamicRouting.Kentico.Mother/DynamicRouteMacroMethods.cs: -------------------------------------------------------------------------------- 1 | using CMS; 2 | using CMS.Helpers; 3 | using CMS.MacroEngine; 4 | using CMS.SiteProvider; 5 | using DynamicRouting.Kentico; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Linq; 9 | using System.Text; 10 | using System.Threading.Tasks; 11 | 12 | [assembly: RegisterExtension(typeof(DynamicRouteMacroMethods), typeof(UtilNamespace))] 13 | 14 | namespace DynamicRouting.Kentico 15 | { 16 | public class DynamicRouteMacroMethods : MacroMethodContainer 17 | { 18 | [MacroMethod(typeof(string), "Retrieves the Parent's Url Slug", 0)] 19 | public static object ParentUrl(EvaluationContext context, params object[] parameters) 20 | { 21 | // Based on the Macro Resolver which has the TreeNode Data, return the ParentUrl 22 | int NodeID = ValidationHelper.GetInteger(context.Resolver.ResolveMacros("{% NodeID %}"), 0); 23 | int NodeParentID = ValidationHelper.GetInteger(context.Resolver.ResolveMacros("{% NodeParentID %}"), 0); 24 | string Culture = ValidationHelper.GetString(context.Resolver.ResolveMacros("{% DocumentCulture %}"), "en-US"); 25 | string DefaultCulture = DynamicRouteInternalHelper.SiteContextSafe().DefaultVisitorCulture; 26 | return CacheHelper.Cache(cs => 27 | { 28 | UrlSlugInfo Slug = UrlSlugInfoProvider.GetUrlSlugs() 29 | .WhereEquals("UrlSlugNodeID", NodeParentID) 30 | .OrderBy($"case when UrlSlugCultureCode = '{Culture}' then 0 else 1 end, case when UrlSlugCultureCode = '{DefaultCulture}' then 0 else 1 end") 31 | .Columns("UrlSlug") 32 | .FirstOrDefault(); 33 | if(cs.Cached) 34 | { 35 | cs.CacheDependency = CacheHelper.GetCacheDependency("dynamicrouting.urlslug|all"); 36 | } 37 | return Slug != null ? Slug.UrlSlug : ""; 38 | }, new CacheSettings(1440, "GetUrlSlug", NodeParentID, Culture, DefaultCulture)); 39 | 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /DynamicRouting.Kentico.Mother/DynamicRouting.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $id$ 5 | $version$ 6 | $title$ 7 | $author$ 8 | $author$ 9 | https://github.com/KenticoDevTrev/DynamicRouting 10 | https://raw.githubusercontent.com/Kentico/devnet.kentico.com/master/marketplace/assets/generic-integration.png 11 | false 12 | $description$ 13 | 14 | 2019 Kentico Community 15 | Dynamic Routing Kentico 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /DynamicRouting.Kentico.Mother/Helpers/DynamicRouteHelper.cs: -------------------------------------------------------------------------------- 1 | using CMS.Base; 2 | using CMS.DataEngine; 3 | using CMS.DocumentEngine; 4 | using CMS.Helpers; 5 | using CMS.Localization; 6 | using CMS.PortalEngine; 7 | using CMS.SiteProvider; 8 | using DynamicRouting.Helpers; 9 | using System; 10 | using System.Collections.Generic; 11 | using System.Data; 12 | using System.Linq; 13 | using System.Web; 14 | 15 | namespace DynamicRouting.Kentico 16 | { 17 | public static class DynamicRouteHelper 18 | { 19 | /// 20 | /// Gets the CMS Page using the given Url Slug. 21 | /// 22 | /// The UrlSlug to look up (part after the domain) 23 | /// The Culture, not needed if the Url contains the culture that the UrlSlug has as part of it's generation. 24 | /// The Site Name, defaults to current site. 25 | /// List of columns you wish to include in the data returned. 26 | /// The Page that matches the Url Slug, for the given or matching culture (or default culture if one isn't found). 27 | public static ITreeNode GetPage(string UrlSlug = "", string Culture = "", string SiteName = "", IEnumerable Columns = null) 28 | { 29 | // Load defaults 30 | SiteName = (!string.IsNullOrWhiteSpace(SiteName) ? SiteName : DynamicRouteInternalHelper.SiteContextSafe().SiteName); 31 | string DefaultCulture = DynamicRouteInternalHelper.SiteContextSafe().DefaultVisitorCulture; 32 | 33 | // Handle Preview, during Route Config the Preview isn't available and isn't really needed, so ignore the thrown exception 34 | bool PreviewEnabled = false; 35 | try 36 | { 37 | PreviewEnabled = PortalContext.ViewMode != ViewModeEnum.LiveSite; 38 | } 39 | catch (InvalidOperationException) { } 40 | 41 | // set the culture 42 | if(string.IsNullOrWhiteSpace(Culture)) { 43 | Culture = LocalizationContext.CurrentCulture.CultureName; 44 | } 45 | 46 | // Convert Columns to 47 | string ColumnsVal = Columns != null ? string.Join(",", Columns.Distinct()) : "*"; 48 | 49 | // Run any GetPage Event hooks which allow the users to set the Found Page 50 | ITreeNode FoundPage = null; 51 | 52 | try 53 | { 54 | // Get Page based on Url 55 | FoundPage = CacheHelper.Cache(cs => 56 | { 57 | // Using custom query as Kentico's API was not properly handling a Join and where. 58 | DataTable NodeTable = ConnectionHelper.ExecuteQuery("DynamicRouting.UrlSlug.GetDocumentsByUrlSlug", new QueryDataParameters() 59 | { 60 | {"@Url", UrlSlug }, 61 | {"@Culture", Culture }, 62 | {"@DefaultCulture", DefaultCulture }, 63 | {"@SiteName", SiteName }, 64 | {"@PreviewEnabled", PreviewEnabled } 65 | }, topN: 1, columns: "DocumentID, ClassName").Tables[0]; 66 | if (NodeTable.Rows.Count > 0) 67 | { 68 | int DocumentID = ValidationHelper.GetInteger(NodeTable.Rows[0]["DocumentID"], 0); 69 | string ClassName = ValidationHelper.GetString(NodeTable.Rows[0]["ClassName"], ""); 70 | 71 | DocumentQuery Query = DocumentHelper.GetDocuments(ClassName) 72 | .WhereEquals("DocumentID", DocumentID); 73 | 74 | // Handle Columns 75 | if (!string.IsNullOrWhiteSpace(ColumnsVal)) 76 | { 77 | Query.Columns(ColumnsVal); 78 | } 79 | 80 | // Handle Preview 81 | if (PreviewEnabled) 82 | { 83 | Query.LatestVersion(true) 84 | .Published(false); 85 | } 86 | else 87 | { 88 | Query.PublishedVersion(true); 89 | } 90 | 91 | TreeNode Page = Query.FirstOrDefault(); 92 | 93 | // Cache dependencies on the Url Slugs and also the DocumentID if available. 94 | if (cs.Cached) 95 | { 96 | if (Page != null) 97 | { 98 | cs.CacheDependency = CacheHelper.GetCacheDependency(new string[] { 99 | "dynamicrouting.urlslug|all", 100 | "dynamicrouting.versionhistoryurlslug|all", 101 | "dynamicrouting.versionhistoryurlslug|bydocumentid|"+Page.DocumentID, 102 | "documentid|" + Page.DocumentID, }); 103 | } 104 | else 105 | { 106 | cs.CacheDependency = CacheHelper.GetCacheDependency(new string[] { "dynamicrouting.urlslug|all", "dynamicrouting.versionhistoryurlslug|all" }); 107 | } 108 | 109 | } 110 | 111 | // Return Page Data 112 | return Query.FirstOrDefault(); 113 | } 114 | else 115 | { 116 | return null; 117 | } 118 | }, new CacheSettings(1440, "DynamicRoutine.GetPageMother", UrlSlug, Culture, DefaultCulture, SiteName, PreviewEnabled, ColumnsVal)); 119 | } 120 | catch (Exception ex) 121 | { 122 | } 123 | return FoundPage; 124 | } 125 | 126 | /// 127 | /// Gets the CMS Page using Dynamic Routing, returning the culture variation that either matches the given culture or the Slug's culture, or the default site culture if not found. 128 | /// 129 | /// The Url (part after the domain), if empty will use the Current Request 130 | /// The Culture, not needed if the Url contains the culture that the UrlSlug has as part of it's generation. 131 | /// The Site Name, defaults to current site. 132 | /// List of columns you wish to include in the data returned. 133 | /// The Page that matches the Url Slug, for the given or matching culture (or default culture if one isn't found). 134 | public static T GetPage(string Url = "", string Culture = "", string SiteName = "", IEnumerable Columns = null) where T : ITreeNode 135 | { 136 | return (T)GetPage(Url, Culture, SiteName, Columns); 137 | } 138 | 139 | /// 140 | /// Gets the Page's Url Slug based on the given DocumentID and it's Culture. 141 | /// 142 | /// The Document ID 143 | /// 144 | public static string GetPageUrl(int DocumentID) 145 | { 146 | return DynamicRouteInternalHelper.GetPageUrl(DocumentID); 147 | } 148 | 149 | /// 150 | /// Gets the Page's Url Slug based on the given DocumentGuid and it's Culture. 151 | /// 152 | /// The Document Guid 153 | /// The UrlSlug (with ~ prepended) or Null if page not found. 154 | public static string GetPageUrl(Guid DocumentGuid) 155 | { 156 | return DynamicRouteInternalHelper.GetPageUrl(DocumentGuid); 157 | } 158 | 159 | /// 160 | /// Gets the Page's Url Slug based on the given NodeAliasPath, Culture and SiteName. If Culture not found, then will prioritize the Site's Default Culture, then Cultures by alphabetical order. 161 | /// 162 | /// The Node alias path you wish to select 163 | /// The Document Culture, if not provided will use default Site's Culture. 164 | /// The Site Name, if not provided then the Current Site's name is used. 165 | /// The UrlSlug (with ~ prepended) or Null if page not found. 166 | public static string GetPageUrl(string NodeAliasPath, string DocumentCulture = null, string SiteName = null) 167 | { 168 | return DynamicRouteInternalHelper.GetPageUrl(NodeAliasPath, DocumentCulture, SiteName); 169 | } 170 | 171 | /// 172 | /// Gets the Page's Url Slug based on the given NodeGuid and Culture. If Culture not found, then will prioritize the Site's Default Culture, then Cultures by alphabetical order. 173 | /// 174 | /// The Node to find the Url Slug 175 | /// The Document Culture, if not provided will use default Site's Culture. 176 | /// The UrlSlug (with ~ prepended) or Null if page not found. 177 | public static string GetPageUrl(Guid NodeGuid, string DocumentCulture = null) 178 | { 179 | return DynamicRouteInternalHelper.GetPageUrl(NodeGuid, DocumentCulture); 180 | } 181 | 182 | /// 183 | /// Gets the Page's Url Slug based on the given NodeID and Culture. If Culture not found, then will prioritize the Site's Default Culture, then Cultures by alphabetical order. 184 | /// 185 | /// The NodeID 186 | /// The Document Culture, if not provided will use default Site's Culture. 187 | /// The Site Name, if not provided then will query the NodeID to find it's site. 188 | /// The UrlSlug (with ~ prepended) or Null if page not found. 189 | public static string GetPageUrl(int NodeID, string DocumentCulture = null, string SiteName = null) 190 | { 191 | return DynamicRouteInternalHelper.GetPageUrl(NodeID, DocumentCulture, SiteName); 192 | } 193 | 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /DynamicRouting.Kentico.Mother/Helpers/DynamicRouteInitializationModule_Mother.cs: -------------------------------------------------------------------------------- 1 | using CMS; 2 | using CMS.DataEngine; 3 | using CMS.Modules; 4 | using DynamicRouting.Kentico.Mother; 5 | 6 | [assembly: RegisterModule(typeof(DynamicRouteInitializationModule_Mother))] 7 | 8 | namespace DynamicRouting.Kentico.Mother 9 | { 10 | public class DynamicRouteInitializationModule_Mother : Module 11 | { 12 | public DynamicRouteInitializationModule_Mother() : base("DynamicRouteInitializationModule_Mother") 13 | { 14 | 15 | } 16 | 17 | protected override void OnInit() 18 | { 19 | base.OnInit(); 20 | 21 | // Call OnInit of the Base 22 | var BaseInitializationModule = new DynamicRouteInitializationModule_Base(); 23 | BaseInitializationModule.Init(); 24 | 25 | // Nuget Manifest Build 26 | ModulePackagingEvents.Instance.BuildNuSpecManifest.After += BuildNuSpecManifest_After; 27 | } 28 | 29 | private void BuildNuSpecManifest_After(object sender, BuildNuSpecManifestEventArgs e) 30 | { 31 | if (e.ResourceName.Equals("DynamicRouting.Kentico", System.StringComparison.InvariantCultureIgnoreCase)) 32 | { 33 | e.Manifest.Metadata.Owners = "Kentico Community"; 34 | e.Manifest.Metadata.ProjectUrl = "https://github.com/KenticoDevTrev/DynamicRouting"; 35 | e.Manifest.Metadata.IconUrl = "http://www.kentico.com/favicon.ico"; 36 | e.Manifest.Metadata.Copyright = "Copyright 2019 Kentico Community"; 37 | e.Manifest.Metadata.Title = "Dynamic Routing for Kentico v12 SP"; 38 | e.Manifest.Metadata.ReleaseNotes = "Added Security (Read permissioN) to Quick Operations, fixed Culture bug."; 39 | } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /DynamicRouting.Kentico.Mother/Helpers/DynamicRouteScheduledTasks.cs: -------------------------------------------------------------------------------- 1 | using CMS.Scheduler; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace DynamicRouting.Kentico 9 | { 10 | public class DynamicRouteScheduledTasks : ITask 11 | { 12 | public string Execute(TaskInfo task) 13 | { 14 | string Result = ""; 15 | switch (task.TaskName.ToLower()) 16 | { 17 | case "checkurlslugqueue": 18 | DynamicRouteInternalHelper.CheckUrlSlugGenerationQueue(); 19 | break; 20 | } 21 | return Result; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /DynamicRouting.Kentico.Mother/Helpers/SlugGenerationQueueUnigridExtension.cs: -------------------------------------------------------------------------------- 1 | using CMS.Base.Web.UI; 2 | using CMS.Helpers; 3 | using CMS.UIControls; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace DynamicRouting.Kentico 11 | { 12 | public class SlugGenerationQueueUnigridExtension : ControlExtender 13 | { 14 | public override void OnInit() 15 | { 16 | Control.OnAction += Control_OnAction; 17 | Control.OnExternalDataBound += Control_OnExternalDataBound; 18 | } 19 | 20 | private object Control_OnExternalDataBound(object sender, string sourceName, object parameter) 21 | { 22 | switch(sourceName.ToLower()) 23 | { 24 | case "haserrors": 25 | return string.IsNullOrWhiteSpace(ValidationHelper.GetString(parameter, "")) ? "No" : "Yes"; 26 | default: 27 | return parameter; 28 | } 29 | } 30 | 31 | private void Control_OnAction(string actionName, object actionArgument) 32 | { 33 | switch (actionName.ToLower()) 34 | { 35 | case "run": 36 | int SlugQueueID = ValidationHelper.GetInteger(actionArgument, 0); 37 | if(SlugQueueID > 0) 38 | { 39 | DynamicRouteInternalHelper.RunSlugGenerationQueueItem(SlugQueueID); 40 | } 41 | break; 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /DynamicRouting.Kentico.Mother/MacroMethodForDynamicRoutingMacroPageType.txt: -------------------------------------------------------------------------------- 1 | using CMS; 2 | using CMS.Helpers; 3 | using CMS.MacroEngine; 4 | using CMS.SiteProvider; 5 | using CMSApp.Old_App_Code; 6 | using System; 7 | using System.Linq; 8 | 9 | [assembly: RegisterExtension(typeof(ToolsMVCMacroMethods), typeof(UtilNamespace))] 10 | [assembly: RegisterExtension(typeof(ToolsMVCMacroMethods), typeof(bool))] 11 | 12 | namespace CMSApp.Old_App_Code 13 | { 14 | public class ToolsMVCMacroMethods : MacroMethodContainer 15 | { 16 | [MacroMethod(typeof(string), "Gets the Proper Url Pattern Title based on the settings of the DynamicRouting.Macro fields", 3)] 17 | [MacroMethodParam(0, "IncludeSubTitle", typeof(bool), "If the Sub Title should be appended to the title for the URL")] 18 | [MacroMethodParam(1, "Title", typeof(string), "The Title")] 19 | [MacroMethodParam(2, "SubTitle", typeof(string), "The Sub Title.")] 20 | public static object SubTitleAddition(EvaluationContext context, params object[] parameters) 21 | { 22 | // Based on the Macro Resolver which has the TreeNode Data, return the ParentUrl 23 | if(parameters.Length < 3) 24 | { 25 | throw new NotSupportedException(); 26 | } 27 | bool IncludeSubTitle = ValidationHelper.GetBoolean(parameters[0], false); 28 | string Title = ValidationHelper.GetString(parameters[1], ""); 29 | string SubTitle = ValidationHelper.GetString(parameters[2], ""); 30 | 31 | if(IncludeSubTitle && !string.IsNullOrWhiteSpace(SubTitle)) 32 | { 33 | return Title + " " + SubTitle; 34 | } else 35 | { 36 | return Title; 37 | } 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /DynamicRouting.Kentico.Mother/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | using CMS; 5 | 6 | // General Information about an assembly is controlled through the following 7 | // set of attributes. Change these attribute values to modify the information 8 | // associated with an assembly. 9 | [assembly: AssemblyTitle("Dynamic Routing for Kentico MVC")] 10 | [assembly: AssemblyDescription("Allows for automatic detection of pages based on URL and routing based on refractoring. For the Mother application only.")] 11 | [assembly: AssemblyConfiguration("")] 12 | [assembly: AssemblyCompany("Mixed")] 13 | [assembly: AssemblyProduct("Dynamic Routing for Kentico")] 14 | [assembly: AssemblyCopyright("2019 Kentico Community")] 15 | [assembly: AssemblyTrademark("")] 16 | [assembly: AssemblyCulture("")] 17 | [assembly: AssemblyDiscoverable] 18 | 19 | // Setting ComVisible to false makes the types in this assembly not visible 20 | // to COM components. If you need to access a type in this assembly from 21 | // COM, set the ComVisible attribute to true on that type. 22 | [assembly: ComVisible(false)] 23 | 24 | // The following GUID is for the ID of the typelib if this project is exposed to COM 25 | [assembly: Guid("8e010e77-58f7-4cad-98ac-81804edd4a68")] 26 | 27 | // Version information for an assembly consists of the following four values: 28 | // 29 | // Major Version 30 | // Minor Version 31 | // Build Number 32 | // Revision 33 | // 34 | // You can specify all the values or you can default the Build and Revision Numbers 35 | // by using the '*' as shown below: 36 | [assembly: AssemblyVersion("12.29.20.0")] 37 | [assembly: AssemblyFileVersion("12.29.20.0")] 38 | -------------------------------------------------------------------------------- /DynamicRouting.Kentico.Mother/ShareableComponentBoilerplate.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $id$ 5 | $version$ 6 | $title$ 7 | $author$ 8 | $author$ 9 | http://www.github.com/YourAccount/YourProject 10 | http://www.kentico.com/favicon.ico 11 | false 12 | $description$ 13 | 14 | Copyright 2019 Kentico Community 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /DynamicRouting.Kentico.Mother/Testing/QuickOperations.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // 5 | // Changes to this file may cause incorrect behavior and will be lost if 6 | // the code is regenerated. 7 | // 8 | //------------------------------------------------------------------------------ 9 | 10 | namespace CMSApp.CMSModules.DynamicRouting 11 | { 12 | 13 | 14 | public partial class QuickOperations 15 | { 16 | 17 | /// 18 | /// form1 control. 19 | /// 20 | /// 21 | /// Auto-generated field. 22 | /// To modify move field declaration from designer file to code-behind file. 23 | /// 24 | protected global::System.Web.UI.HtmlControls.HtmlForm form1; 25 | 26 | /// 27 | /// btnRebuildSite control. 28 | /// 29 | /// 30 | /// Auto-generated field. 31 | /// To modify move field declaration from designer file to code-behind file. 32 | /// 33 | protected global::System.Web.UI.WebControls.Button btnRebuildSite; 34 | 35 | /// 36 | /// tbxPath control. 37 | /// 38 | /// 39 | /// Auto-generated field. 40 | /// To modify move field declaration from designer file to code-behind file. 41 | /// 42 | protected global::CMS.FormEngine.Web.UI.FormControl tbxPath; 43 | 44 | /// 45 | /// btnRebuildSubTree control. 46 | /// 47 | /// 48 | /// Auto-generated field. 49 | /// To modify move field declaration from designer file to code-behind file. 50 | /// 51 | protected global::System.Web.UI.WebControls.Button btnRebuildSubTree; 52 | 53 | /// 54 | /// btnRunQueue control. 55 | /// 56 | /// 57 | /// Auto-generated field. 58 | /// To modify move field declaration from designer file to code-behind file. 59 | /// 60 | protected global::System.Web.UI.WebControls.Button btnRunQueue; 61 | 62 | /// 63 | /// tbxRouteToTest control. 64 | /// 65 | /// 66 | /// Auto-generated field. 67 | /// To modify move field declaration from designer file to code-behind file. 68 | /// 69 | protected global::System.Web.UI.WebControls.TextBox tbxRouteToTest; 70 | 71 | /// 72 | /// btnCheckUrl control. 73 | /// 74 | /// 75 | /// Auto-generated field. 76 | /// To modify move field declaration from designer file to code-behind file. 77 | /// 78 | protected global::System.Web.UI.WebControls.Button btnCheckUrl; 79 | 80 | /// 81 | /// btnCheckUrl control. 82 | /// 83 | /// 84 | /// Auto-generated field. 85 | /// To modify move field declaration from designer file to code-behind file. 86 | /// 87 | protected global::System.Web.UI.WebControls.Button btnCleanWipe; 88 | 89 | /// 90 | /// ltrPageFound control. 91 | /// 92 | /// 93 | /// Auto-generated field. 94 | /// To modify move field declaration from designer file to code-behind file. 95 | /// 96 | protected global::System.Web.UI.WebControls.Literal ltrPageFound; 97 | 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /DynamicRouting.Kentico.Mother/Testing/QuickOperations.cs: -------------------------------------------------------------------------------- 1 | using CMS.Base; 2 | using CMS.DataEngine; 3 | using CMS.DocumentEngine; 4 | using CMS.EventLog; 5 | using CMS.Membership; 6 | using CMS.SiteProvider; 7 | using CMS.UIControls; 8 | using DynamicRouting; 9 | using DynamicRouting.Kentico; 10 | using System; 11 | using System.Collections.Generic; 12 | using System.Linq; 13 | 14 | namespace CMSApp.CMSModules.DynamicRouting 15 | { 16 | public partial class QuickOperations : CMSPage 17 | { 18 | protected override void OnPreInit(EventArgs e) 19 | { 20 | CurrentUserInfo currentUser = MembershipContext.AuthenticatedUser; 21 | 22 | // Ensure access 23 | if (!currentUser.IsAuthorizedPerResource("DynamicRouting.Kentico", "Read")) 24 | { 25 | RedirectToAccessDenied("DynamicRouting.Kentico", "Read"); 26 | } 27 | 28 | base.OnPreInit(e); 29 | } 30 | 31 | protected void Page_Load(object sender, EventArgs e) 32 | { 33 | 34 | } 35 | 36 | protected void btnRebuildSite_Click(object sender, EventArgs e) 37 | { 38 | DynamicRouteInternalHelper.RebuildRoutesBySite(DynamicRouteInternalHelper.SiteContextSafe().SiteName); 39 | } 40 | 41 | protected void btnRebuildSubTree_Click(object sender, EventArgs e) 42 | { 43 | TreeNode Page = DocumentHelper.GetDocuments().Path("/"+tbxPath.Value.ToString().Trim('%').Trim('/')).FirstOrDefault(); 44 | 45 | if (Page != null) 46 | { 47 | DynamicRouteInternalHelper.RebuildSubtreeRoutesByNode(Page.NodeID); 48 | } 49 | } 50 | 51 | protected void btnCheckUrl_Click(object sender, EventArgs e) 52 | { 53 | ITreeNode Node = DynamicRouteHelper.GetPage(tbxRouteToTest.Text); 54 | if (Node != null) 55 | { 56 | ltrPageFound.Text = $"FOUND! {Node.NodeAliasPath} {Node.ClassName} {Node.DocumentCulture}"; 57 | } 58 | else 59 | { 60 | ltrPageFound.Text = "No Node Found"; 61 | } 62 | } 63 | 64 | protected void btnCleanWipe_Click(object sender, EventArgs e) 65 | { 66 | // clear out 67 | ConnectionHelper.ExecuteNonQuery("truncate table DynamicRouting_SlugGenerationQueue", null, QueryTypeEnum.SQLQuery, true); 68 | ConnectionHelper.ExecuteNonQuery("truncate table DynamicRouting_UrlSlug", null, QueryTypeEnum.SQLQuery, true); 69 | ConnectionHelper.ExecuteNonQuery("truncate table DynamicRouting_UrlSlugStagingTaskIgnore", null, QueryTypeEnum.SQLQuery, true); 70 | foreach(SiteInfo Site in SiteInfoProvider.GetSites()) { 71 | DynamicRouteInternalHelper.RebuildRoutesBySite(Site.SiteName); 72 | } 73 | } 74 | } 75 | } -------------------------------------------------------------------------------- /DynamicRouting.Kentico.Mother/app.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /DynamicRouting.Kentico.Mother/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /DynamicRouting.sln: -------------------------------------------------------------------------------- 1 | Microsoft Visual Studio Solution File, Format Version 12.00 2 | # Visual Studio Version 16 3 | VisualStudioVersion = 16.0.29424.173 4 | MinimumVisualStudioVersion = 10.0.40219.1 5 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DynamicRouting.Kentico.Base", "DynamicRouting.Kentico.Base\DynamicRouting.Kentico.Base.csproj", "{66553B70-EF4A-40BA-B077-F5B88826EB82}" 6 | EndProject 7 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DynamicRouting.Kentico.MVC", "DynamicRouting.Kentico.MVC\DynamicRouting.Kentico.MVC.csproj", "{8A9F8E7F-C18A-4D90-924A-27B50F5E51FF}" 8 | EndProject 9 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DynamicRouting.Kentico", "DynamicRouting.Kentico.Mother\DynamicRouting.Kentico.csproj", "{60D45549-BB62-4999-8F29-987B58B33127}" 10 | EndProject 11 | Global 12 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 13 | Debug|Any CPU = Debug|Any CPU 14 | Release|Any CPU = Release|Any CPU 15 | EndGlobalSection 16 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 17 | {66553B70-EF4A-40BA-B077-F5B88826EB82}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 18 | {66553B70-EF4A-40BA-B077-F5B88826EB82}.Debug|Any CPU.Build.0 = Debug|Any CPU 19 | {66553B70-EF4A-40BA-B077-F5B88826EB82}.Release|Any CPU.ActiveCfg = Release|Any CPU 20 | {66553B70-EF4A-40BA-B077-F5B88826EB82}.Release|Any CPU.Build.0 = Release|Any CPU 21 | {8A9F8E7F-C18A-4D90-924A-27B50F5E51FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 22 | {8A9F8E7F-C18A-4D90-924A-27B50F5E51FF}.Debug|Any CPU.Build.0 = Debug|Any CPU 23 | {8A9F8E7F-C18A-4D90-924A-27B50F5E51FF}.Release|Any CPU.ActiveCfg = Release|Any CPU 24 | {8A9F8E7F-C18A-4D90-924A-27B50F5E51FF}.Release|Any CPU.Build.0 = Release|Any CPU 25 | {60D45549-BB62-4999-8F29-987B58B33127}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 26 | {60D45549-BB62-4999-8F29-987B58B33127}.Debug|Any CPU.Build.0 = Debug|Any CPU 27 | {60D45549-BB62-4999-8F29-987B58B33127}.Release|Any CPU.ActiveCfg = Release|Any CPU 28 | {60D45549-BB62-4999-8F29-987B58B33127}.Release|Any CPU.Build.0 = Release|Any CPU 29 | EndGlobalSection 30 | GlobalSection(SolutionProperties) = preSolution 31 | HideSolutionNode = FALSE 32 | EndGlobalSection 33 | GlobalSection(ExtensibilityGlobals) = postSolution 34 | SolutionGuid = {7DBE6E9A-E9DA-49A3-AD90-5CC16067A619} 35 | EndGlobalSection 36 | EndGlobal 37 | -------------------------------------------------------------------------------- /DynamicRoutingStarterSite.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KenticoDevTrev/DynamicRouting/b352bee0ccd67a3d28e5de6dbcced522657664c4/DynamicRoutingStarterSite.zip -------------------------------------------------------------------------------- /RenameProject.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | echo --INSTRUCTIONS-- 3 | echo Do not use any of these characters: " ' @ $ # `, if you wish to use any of these you must do so manually after this script is done. 4 | set /p ToolName="Enter ToolName (a-Z_): " 5 | set /p Prefix="Enter Assembly Prefix (a-Z_.), usually Company (ex 'HBS.'): " 6 | set /p PostFix="Enter Assembly Postfix (a-Z_.) (ex '.Kentico.MVC'): " 7 | set /p GitHubUrl="Enter GitHub Url: " 8 | set /p Tags="Enter NuGet Tags (space separated): " 9 | set /p AssemblyTitle="Enter Assembly Title: " 10 | set /p AssemblyDescription="Enter Assembly Description: " 11 | set /p AssemblyCompany="Enter Assembly Company: " 12 | set /p AssemblyProduct="Enter Assembly Product Name: " 13 | set /p AssemblyCopyright="Enter Assembly Copyright: " 14 | 15 | cd ShareableComponentBoilerplate.Views 16 | cd Views 17 | cd Shared 18 | cd Widgets 19 | powershell -Command "(gc _ShareableComponentBoilerplate.cshtml) -replace 'CompanyName.ShareableComponentBoilerplate.Kentico.MVC', '%Prefix%%ToolName%%PostFix%' | Out-File -encoding ASCII _ShareableComponentBoilerplate.cshtml" 20 | powershell -Command "(gc _ShareableComponentBoilerplate.cshtml) -replace 'ShareableComponentBoilerplate', '%ToolName%' | Out-File -encoding ASCII _ShareableComponentBoilerplate.cshtml" 21 | rename _ShareableComponentBoilerplate.cshtml _%ToolName%.cshtml 22 | cd.. 23 | cd.. 24 | cd.. 25 | 26 | cd Properties 27 | powershell -Command "(gc AssemblyInfo.cs) -replace 'AssemblyTitleHere', '%AssemblyTitle%' | Out-File -encoding ASCII AssemblyInfo.cs" 28 | powershell -Command "(gc AssemblyInfo.cs) -replace 'AssemblyDescriptionHere', '%AssemblyDescription%' | Out-File -encoding ASCII AssemblyInfo.cs" 29 | powershell -Command "(gc AssemblyInfo.cs) -replace 'AssemblyCompanyHere', '%AssemblyCompany%' | Out-File -encoding ASCII AssemblyInfo.cs" 30 | powershell -Command "(gc AssemblyInfo.cs) -replace 'AssemblyProductHere', '%AssemblyProduct%' | Out-File -encoding ASCII AssemblyInfo.cs" 31 | powershell -Command "(gc AssemblyInfo.cs) -replace 'AssemblyCopyrightHere', '%AssemblyCopyright%' | Out-File -encoding ASCII AssemblyInfo.cs" 32 | powershell -Command "(gc AssemblyInfo.cs) -replace '3703e296-cd7c-47ff-9ab5-19b621c80b15', [GUID]::NewGuid() | Out-File -encoding ASCII AssemblyInfo.cs" 33 | cd.. 34 | 35 | powershell -Command "(gc ShareableComponentBoilerplate.Views.csproj) -replace 'CompanyName.ShareableComponentBoilerplate.Kentico.MVC', '%Prefix%%ToolName%%PostFix%' | Out-File -encoding ASCII ShareableComponentBoilerplate.Views.csproj" 36 | powershell -Command "(gc ShareableComponentBoilerplate.Views.csproj) -replace 'ShareableComponentBoilerplate', '%ToolName%' | Out-File -encoding ASCII ShareableComponentBoilerplate.Views.csproj" 37 | 38 | rename ShareableComponentBoilerplate.Views.csproj %ToolName%.Views.csproj 39 | cd.. 40 | 41 | rename ShareableComponentBoilerplate.Views %ToolName%.Views 42 | 43 | cd ShareableComponentBoilerplate 44 | powershell -Command "(gc BuildPackage.bat) -replace 'ShareableComponentBoilerplate', '%ToolName%' | Out-File -encoding ASCII BuildPackage.bat" 45 | 46 | powershell -Command "(gc ShareableComponentBoilerplateWidget.cs) -replace 'CompanyName.ShareableComponentBoilerplate.Kentico.MVC', '%Prefix%%ToolName%%PostFix%' | Out-File -encoding ASCII ShareableComponentBoilerplateWidget.cs" 47 | powershell -Command "(gc ShareableComponentBoilerplateWidget.cs) -replace 'ShareableComponentBoilerplate', '%ToolName%' | Out-File -encoding ASCII ShareableComponentBoilerplateWidget.cs" 48 | powershell -Command "(gc ShareableComponentBoilerplateWidget.cs) -replace 'CompanyName.', '%Prefix%' | Out-File -encoding ASCII ShareableComponentBoilerplateWidget.cs" 49 | rename ShareableComponentBoilerplateWidget.cs %ToolName%Widget.cs 50 | 51 | powershell -Command "(gc ShareableComponentBoilerplateWidgetModel.cs) -replace 'CompanyName.ShareableComponentBoilerplate.Kentico.MVC', '%Prefix%%ToolName%%PostFix%' | Out-File -encoding ASCII ShareableComponentBoilerplateWidgetModel.cs" 52 | powershell -Command "(gc ShareableComponentBoilerplateWidgetModel.cs) -replace 'ShareableComponentBoilerplate', '%ToolName%' | Out-File -encoding ASCII ShareableComponentBoilerplateWidgetModel.cs" 53 | rename ShareableComponentBoilerplateWidgetModel.cs %ToolName%WidgetModel.cs 54 | 55 | powershell -Command "(gc ShareableComponentBoilerplate.csproj) -replace 'CompanyName.ShareableComponentBoilerplate.Kentico.MVC', '%Prefix%%ToolName%%PostFix%' | Out-File -encoding ASCII ShareableComponentBoilerplate.csproj" 56 | powershell -Command "(gc ShareableComponentBoilerplate.csproj) -replace 'CompanyName.', '%Prefix%' | Out-File -encoding ASCII ShareableComponentBoilerplate.csproj" 57 | powershell -Command "(gc ShareableComponentBoilerplate.csproj) -replace 'ShareableComponentBoilerplate', '%ToolName%' | Out-File -encoding ASCII ShareableComponentBoilerplate.csproj" 58 | rename ShareableComponentBoilerplate.csproj %ToolName%.csproj 59 | 60 | powershell -Command "(gc ShareableComponentBoilerplate.nuspec) -replace 'CompanyName.ShareableComponentBoilerplate', '%Prefix%%ToolName%' | Out-File -encoding ASCII ShareableComponentBoilerplate.nuspec" 61 | powershell -Command "(gc ShareableComponentBoilerplate.nuspec) -replace '--TagsHere--', '%Tags%' | Out-File -encoding ASCII ShareableComponentBoilerplate.nuspec" 62 | powershell -Command "(gc ShareableComponentBoilerplate.nuspec) -replace 'http://url', '%GitHubUrl%' | Out-File -encoding ASCII ShareableComponentBoilerplate.nuspec" 63 | powershell -Command "(gc ShareableComponentBoilerplate.nuspec) -replace '--CopyrightHere--', '%AssemblyCopyright%' | Out-File -encoding ASCII ShareableComponentBoilerplate.nuspec" 64 | rename ShareableComponentBoilerplate.nuspec %ToolName%.nuspec 65 | 66 | cd Properties 67 | powershell -Command "(gc AssemblyInfo.cs) -replace 'AssemblyTitleHere', '%AssemblyTitle%' | Out-File -encoding ASCII AssemblyInfo.cs" 68 | powershell -Command "(gc AssemblyInfo.cs) -replace 'AssemblyDescriptionHere', '%AssemblyDescription%' | Out-File -encoding ASCII AssemblyInfo.cs" 69 | powershell -Command "(gc AssemblyInfo.cs) -replace 'AssemblyCompanyHere', '%AssemblyCompany%' | Out-File -encoding ASCII AssemblyInfo.cs" 70 | powershell -Command "(gc AssemblyInfo.cs) -replace 'AssemblyProductHere', '%AssemblyProduct%' | Out-File -encoding ASCII AssemblyInfo.cs" 71 | powershell -Command "(gc AssemblyInfo.cs) -replace 'AssemblyCopyrightHere', '%AssemblyCopyright%' | Out-File -encoding ASCII AssemblyInfo.cs" 72 | powershell -Command "(gc AssemblyInfo.cs) -replace '60d45549-bb62-4999-8f29-987b58b33127', [GUID]::NewGuid() | Out-File -encoding ASCII AssemblyInfo.cs" 73 | cd.. 74 | 75 | cd.. 76 | 77 | rename ShareableComponentBoilerplate %ToolName% 78 | 79 | powershell -Command "(gc ShareableComponentBoilerplate.sln) -replace 'ShareableComponentBoilerplate', '%ToolName%' | Out-File -encoding ASCII ShareableComponentBoilerplate.sln" 80 | rename ShareableComponentBoilerplate.sln %ToolName%.sln 81 | 82 | cd targets 83 | powershell -Command "(gc Kentico.EmbeddedViews.targets) -replace 'ShareableComponentBoilerplate', '%ToolName%' | Out-File -encoding ASCII Kentico.EmbeddedViews.targets" 84 | cd.. 85 | pause -------------------------------------------------------------------------------- /Targets/Kentico.EmbeddedViews.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | [assembly: Kentico.Web.Mvc.EmbeddedViewAssembly(%40"@(ViewFiles->'%(relativedir)%(filename)%(extension)','", 10 | %40"')")] 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Targets/Kentico.Resources.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /URLGenerator.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KenticoDevTrev/DynamicRouting/b352bee0ccd67a3d28e5de6dbcced522657664c4/URLGenerator.zip --------------------------------------------------------------------------------