Extension URL Providers are a new feature of the DNN Platform, released with DNN Version 7.1
Purpose of DNN Extension URL Providers
Extension URL Providers are another extensibility point of the DNN Platform. They allow the implementation of customised URL Schemes that allow for formats and values that would not normally be easy or possible within the standard DNN URL APIs.
An Extension URL Provider is a compiled extension that installs into DNN through the Host->Extensions page. They are normally associated with a specific extension already installed, and intercept the URLs for that extension, allowing custom patterns and logic to be implemented.
An Extension URL Provider can be bundled with a DNN extension, or they can be developed separately to an existing extension, even if the developer has no internal knowledge of how that extension works.
Examples of DNN Extension URL Providers can be found at the
Codeplex Extension URL Providers project page.
A Brief Introduction to DNN URLs, URL Rewriting and Friendly URL Generation
A DNN URL can be broken down into 4 basic pieces - though not all URLs contain all 4.
http://example.com/Page-Name/ItemId/34?parameter=value└──── 1 ─────┘└── 2 ──┘└─ 3 ──┘└─── 4 ─────┘
The path segments highlighted are as follows:
- Site Alias : Usually the domain name, but can also contain part of the path such as for a 'child' alias
- Page Path : The portion of the URL path that identifies the DNN page to load
- Parameters : Key/Value pairs that are used by modules on the page to vary the content that is displayed (Optional)
- Querystring : Key/Value pairs in a traditional Querystring appended to the end of the 'path' section of the URL
The Parameters within DNN URLs are used to vary the content - in the case of 'Edit' URLs the pattern will often be /ctl/Edit/ModuleId/345, which is translated by DNN to mean 'Load the Edit page for Module Id 345.
In practice, there is no difference between the Querystring values on the end of a URL and the path segments within the Parameters. This is because the URL Rewriter in DNN rewrites all DNN URLs to a URL that is relative to the underlying page of 'Default.aspx'. The Default.aspx page is actually the only 'physical' file that exists in a DNN application - this is sometimes a surprise to many people who go looking for a 'home.aspx' file within a new DNN installation.
URL Rewriting
The process of URL Rewriting in DNN would turn the above URL into something like this:
http://example.com/default.aspx?TabId=56&language=en-US&ItemId=34¶meter=value
This rewritten URL is never shown to the site visitor, but the underlying code in DNN reads these values from the URL and generates the output HTML accordingly.
Friendly URL Generation
Extension developers in DNN do not 'hard code' URLs like the one above. This is because, when developing an Extension, the developer doesn't know in advance what the domain name is going to be, what page a module might be on, and cannot know things like an Item Id in advance. Thus, the vast majority of DNN URLs are generated dynamically. This is done within modules with calls to the DotNetNuke.Common.Globals.NavigateURL() call, which is the primary API to generating URLs within DNN.
The NavigateURL() call essentially assembles the pieces of the URL as per the segments 1-4 as listed above. Parts 1 and 2 are mandatory, and anything passed in as parameters gets transformed into the /key/value pairs shown - which can be easily translated back into the &itemId=34 format when rewritten.
Modifying the standard DNN URL Pattern with an Extension URL Provider
Many developers and site administrators want to use different URL patterns for a variety of reasons (usually SEO, but there are others). There is no way to easily work around the DNN Friendly Url Provider / URL Rewriter. You can 'hack' the process by avoiding use of the NavigateURL() call to generate the URL (or modifying the results of the NavigateURL() call), and you can implement additional URL Rewriting to catch and translate those specific URLs. The problems with this type of approach are (1) It makes for difficult install/uninstall of Extensions and (2) it leaves code brittle to changes within the DNN Core platform.
Extension URL Providers are the key to solving this problem. An Extension URL Provider is a simple piece of code that implements 3 basic procedures required by the underlying DNN URL functionality:
- Rewriting : Taking a customised URL and identifying the correct and unique data from it
- Replacement : Allowing the redefinition of a standard DNN Friendly URL and replacing segments from it
- Redirecting : Handling the cases that are required when a standard URL scheme gets replaced with a customised version.
Extension URL Provider Example
The following examples are for a
- raw DNN URL of example.com/default.aspx?tabid=56&language=en-US&ItemId=34
- standard friendly Url of example.com/Page-Name/ItemId/34
- improved friendly Url of example.com/My-Item
Rewriting
The process of rewriting takes the friendly URL and rewrites it back to the raw URL format.
In the case of our improved Friendly URL (/My-Item) we would normally get a 404 error because there is no matching DNN Page path to the first segment of the URL (/My-Item).
When an Extension URL Provider is installed, instead of returning a 404 Error, the Extension URL Provider will be given the Friendly URL and asked to return a valid rewritten URL. If there are more than one Extension Provider installed, each provider will be called in turn, until either a result is returned by a Provider, or there are no more Providers.
The provider call is done with the segment of the URL that does not match a DNN page URL. In this case, it is the 'My-Item' segment because the URL does not have any DNN Page URL segment in it.
This is handled by the provider implementation of the
TransformFriendlyUrlToQueryString method.
This method must be implemented by all providers, and has the following parameters:
- urlParms : a string array of the URL segments (/segment1/segment2) in the URL "after" the site alias & page path have been matched and removed from the entire URL. In this example, it would be an array of 1 position with 'My-Item' in it.
- tabId : the ID of the matched tab. If no tab could be matched, it will be -1
- portalId : the ID of the matched portal, which is identified by the site alias
- options : An object which contains properties relating to the URL options of the current portal, such as the configured page extension, space replacement characters and other URL generation options.
- cultureCode : any identified culture for the URL, or the default culture code of the portal
- portalAlias : the Site Alias that was used to request the URL
- messages : a by-reference list of string messages which can be used to add debug/informational messages. These are returned in the Test Url Rewriting function, and also during runtime if the 'debug code' is enabled.
- out status : a return parameter to return the http status of the request after the provider has modified it
- out location : if a redirect (301) has been returned in the status, then the location parameter must supply the redirect location.
The return value of the method must return null or empty string if no rewrite is performed by the Provider. The logic within the method should determine whether to 'leave' the particular request or provide a querystring in response. Due to the way that providers are configured, not all requests will be relevant to the particular provider.
If a querystring is returned by the Provider, this querystring will be appended to the end of the Rewritten URL. The provider itself does not perform the rewriting process.
In some cases (such as the example URL above) the Page Url is not known for the requested URL. Because the page URL is not known, at the time of calling the provider method, the tabId will not be known either. In these cases, the provider must return the valid TabId for the request. This means the logic within the provider must have a way of determining which requests belong to which page. That is likely to be different for each different implementation.
If the TabId is known because the Page could be identified by the request, then the TabId should not be supplied in the resulting querystring.
The pseudo code for the example URL would be as follows:
TransformFriendlyUrl()
{
result = null
url=urlParms.join
if (url matches basic filter of expected urls)
index = GetIndexOfValidUrlsFromCache()
if index.contains(url)
result = "tabid=" + definedPageForModule + "&" + index(url)
end if
end if
return result
}
This would transform 'My-Item' into tabid=56&itemId=34 and return that as the rewrite result.
Replacing
The process of Replacing takes the standard-format URL and replaces it with a customised URL that the Provider can specify.
This modifies the URLs that are generated through the NavigateURL() function.
The process of calling an Extension URL Provider for replacing URLs is similar to the process of Rewriting, but is called during the process of the DNN Core and other extensions like Menu providers and Modules requesting NavigateURL() to build links for the page that will be sent back to the visitor.
A provider will be called for a page if it is enabled, Providers in general are enabled at the installation and site level, and the underlying provider configuration specifies it matches with the tab specified in the NavigateURL() call.
Using the example, if the following URL is requested through the NavigateURL() call, and no Provider is configured, then the result would be : example.com/Page-Name/ItemId/34
Assuming there is a Provider configured such that requests for Tab Id 56 ('Page Name') are processed through the Provider, the "ChangeFriendlyUrl" method will be called.
This method must be implemented by a provider, and takes the following parameters:
- tab : The current TabInfo object for the tab the URL is being generated for
- friendlyUrlPath : the path excluding the Tab requested (/itemId/34)
- options : An object which contains properties relating to the URL options of the current portal, such as the configured page extension, space replacement characters and other URL generation options.
- cultureCode : any identified culture for the URL, or the default culture code of the portal
- endingPageName : a by reference value which equates to the 'pagename' method of the underlying FriendlyURLProvider call. This is normally 'default.aspx' (it refers to the underlying physical page, rather than the DNN page). This can be overridden by the provider if a different value is required to be suffixed on the end of the generated URL. If 'default.aspx' is kept, then the value is not in the resulting URL.
- useDNNPagePath : an out boolean parameter. When true, the resulting URL will "not" include the DNN Page path (in our example, this means that 'Page-Name' would not be in the resulting URL.
- messages : a list of debug messages that can be added to as the operation proceeds. Do not re-initialise the list, but append new messages. These can be seen in the Test Url screen within the Admin section.
The purpose of this method is to use the supplied data and implement specific logic to take the supplied parameters and change them into whatever format the URL should take.
Some developers might question why this step is necessary, given that the module is generally responsible for what goes into the NavigateURL call that is related to the particular module that the URL is related to. The answer is in three parts:
1) Extension URL Providers give the ability to modify URLs for code that the Provider developer cannot change
2) The Extension URL Provider may or may not be enabled for the module, so the existence of the provider to rewrite the URLs cannot be guaranteed
3) Developers must use the NavigateURL() call to generate URLs for DNN pages, and Extension URL Providers are the only method available in order to modify the entire URL from site alias forwards.
Pseudocode for developing the example URL is as follows:
ChangeFriendlyUrl()
{
result = null
if (friendlyUrlPath matches filter of Urls which should be modified)
urlIndex = GetFriendlyUrlsFromCache()
if (urlIndex.contains(friendlyUrlPath)
result = urlIndex(friendlyUrlPath)
end if
end if
if (providerOptions.DoNotUseDNNPagePath is true)
useDNNPagePath = false
return result
}
In the example case used on this page, the 'friendlyUrlPath' would be /itemId/34 and the result would be 'My-Item' with the "out" parameter of useDnnPagePath set to "true".
The ChangeFriendlyUrl() request has the ability to be called a lot of times during the build process for a page, whereas the 'TransformFriendlyUrl' method is typically called once per request. It is more important that the ChangeFriendlyUrl() method performs exceptionally than any other method in a provider. This means using filters and exclusions to only run 'expensive' code when necesasry. It means ensuring that there are no database lookups per-case and leveraging the DNN cache for any stored indexes.
Note that this example uses an index based lookup - this is typical of something like an e-commerce product catalog or a blog module. However, it is possible to use regex-based logic to change the URL if the transformation is relatively simple. The key to understanding Extension URL Providers is in the freedom that a compiled, coded solution brings - developers are free to create whatever they need.
Redirecting
Part of the job of an Extension URL Provider is to provide a migration path from older forms of URL patterns to newer forms. This may not be important if the underlying extension is a new offering, and there are no 'legacy' URLs around. But in many cases the functionality will be added to an existing extension. In these cases the Provider should allow for older styles of URLs to be requested and redirected to whatever the current, correct URL is.
In the same way as the Rewrite and Replace functions, there is a standard method that each Provider needs to implement. If a provider has no need to redirect any URLs, it must be implemented, but can safely return an empty result.
The Redirect method for the Provider will be called under the following conditions:
- The Provider is configured as Active at the Installation and Portal level
- The Provider is configured to respond to the requested DNN page "or" the Provider is configured to check all requests for redirects
- No Providers returned a valid result for the TransformFriendlyURL() call.
Developers will note that the TransformFriendlyURL call had the ability to return a redirect status code and redirect location, so this method may seem superfluous. This is the reason why the Redirect is not called if the TransformFriendlyURL call returned a result. However, the TransformFriendlyURL method is more tightly controlled in terms of how often it is called, and does not include the raw requested URL. There is an overlap between the two methods, but they serve slightly different purposes.
If all possible redirect scenarios can be identified within the logic of the TransformFriendlyURL method, then the redirect functionality should be implemented within that. For all other redirect requirements (such as redirects off querystrings, for different site aliases, URLs that were never used for a DNN install, etc) the Redirect method should be used.
The Redirect method is called "CheckForRedirect" and is provided with the following parameters:
- tabId : The identified tabId for the request
- portalId : The identified portalId for the identied Portal
- httpAlias : The identified site Alias (portalAlias) for the request
- requestUri : The raw URI object for the request, which includes the full URL, the domain, querystring and scheme
- queryStringCol : The querystring in NameValueCollection form, which allows for QueryString("key") usage
- options : An object which contains properties relating to the URL options of the current portal, such as the configured page extension, space replacement characters and other URL generation options.
- redirectLocation : an out parameter which specifies the fully qualified URL for where a redirect should be redirected to
- messages : a by-reference list of string messages which can be used to add debug/informational messages. These are returned in the Test Url Rewriting function, and also during runtime if the 'debug code' is enabled.
The return value of the method is a boolean true/false - true for 'do the redirect'; false for 'no redirect to do'
The implementation of redirect logic within this method is a free to be implemented however a developer needs. The full, original requestURI value is provided to manipulate and use any particular part of the originally requested URL.
The basic pseudocode is:
CheckForRedirect()
{
result = false
If (requestUri matches some type of redirect filter)
location = GetTheRedirectURL()
return result
}
It is advised to use Regular Expression filters or other logic to narrow down the scope of redirected URLs to prevent unwanted redirects, which are a common problem when implementing logic like this. Developers should avoid the redirection of URLs unrelated to the requests being targeted.
The provision of the httpAlias and portalId into the method allows the calling of the NavigateURL() function with a specific portalSettings object. Because of the early location in the request pipeline where this method is called, calls to NavigateURL() may fail with a null reference error related to the portalSettings object. It is advised to create a portalSettings object and supply it to the NavigateURL() call if the redirect logic requires the construction of a new DNN URL.
Provider Settings & Settings UI
Each Provider has separate settings storage, where key/value pairs specific for the Extension URL Provider per portal can be saved. These are supplied upon instantiation of the Provider and can then be used to alter the behaviour of the Provider for different configurations.
The Settings are available as a Dictionary object from the instantiated provider.
In order to provide end-users with the ability to customise the functionality of the settings through a dedicated UI. This allows developers to specify their own screen controls to give end-users to ability to specify options on how the Urls should appear.
The Provider Settings UI works in a similar way to the way that the Module Settings UI works - it provides a LoadSettings() and UpdateSettings() overload. All the developer needs to do is read and display the settings to the UI, and then fetch the settings from the UI and populate the settings dictionary, and these will be saved in the database.
References and Further Reading
Introducing DNN Extension URL ProvidersDNN Manifest File Definitions