This post is intended for advanced developers who are looking to dig deeply into the guts of Services Framework, with an eye to extending the core features of Services Framework. I will be pointing out the main classes involved in each feature of the Services Framework, and mentioning any unobvious details. I will not be mentioning every class, or expalaining in detail the function of every piece of Services Framework. This document should quickly point you to the relevant piece of code. From that point the code is truly the best documentation.
The original motivation for writing this was to share my knowledge of the Services Framework internals with the rest of the DNN Corp. engineering team. In the process of putting this together I realized that it may be useful to some folks outside of DNN Corp. and decided to publish it as a blog. Much of what is discussed here is either marked internal or included in Internal namespaces, and is subject to change at any time. Dragons live here, tread carefully.
Initializing Services Framework
Startup
The DotNetNuke application startup is centered around DotNetNuke.Common.Initialize.InitializeApp()
. There is a call to DotNetNuke.Common.Internal.ServicesRoutingManager.RegisterServiceRoutes()
. This method merely calls DotNetNuke.Web.Api.Internal.ServicesRoutingManager.RegisterRoutes()
via reflection. The reflection is neccesary to break what would otherwise by a cyclic dependancy between DotNetNuke.Library and DotNetNuke.Web.
DotNetNuke.Web.Api.Internal.ServicesRoutingManager.RegisterRoutes()
performs two important functions. It sets up all the GlobalConfiguration of the WebAPI portion of Services Framework, and then it runs the process of reflecting for all the route mappers and calling them to register routes for all the installed services. Once all the services have been registered the Services Framework is ready to process requests.
HttpModule
Requests to services will not "naturally" start the DotNetNuke application. If a website shuts down and the first request received is for a service, we need to ensure that the Services Framework is initialized. This is done by an HttpModule implemented in DotNetNuke.HttpModules.Services.ServicesModule
.
Routes
Services Framework follows a simple format for all routes:
DesktopModules/<ModuleFolderName>/API/
This format is straight forward and documented in several places. What is not immediately obvious is that depending on the sites and aliases configured in a DNN installation, it may be neccesary to include prefixes on that path. If an installation only has an alias for foo.com, then a single route starting at /DesktopModules... is sufficient. But, if an installation also includes a child portal like foo.com/child it will be neccesary to map every route twice, once for /DesktopModules... and once for {prefix}/DesktopModules... . All the logic for mapping the correct routes can be easily found by following the flow of the RegisterRoutes method.
Adding an alias
When a new portal alias is added to the system, it is possible that the routes will need to be updated to start supporting a new level of prefix on the route. PortalController.CreatePortal
and PortalAliasController.AddPortalAlias
both call DotNetNuke.Common.Internal.ServicesRoutingManager.ReRegisterServiceRoutesWhileSiteIsRunning
to take care of this.
Re-Registering routes while the site is running actually clears a cache key, the cache provider eventually makes the calls to re-register the routes. This hack is done to take advantage of the web farm cache provider and propogate the route changes to all servers within a web farm.
Processing a Request
Services Framework makes several additions to the standard flow of a WebAPI request in order to integrate properly with DNN, and to add features not available in standard WebAPI. Much of the additional processing is done in Http Mesage Handlers, you should be familiar with how they work, here is a good starting point.
Dnn Context
The first Services Framework specific step is establshing the DNN context of the request. The context is needed before attempting authentication. This includes locating the correct portal settings, setting the culture for the request, validating tab and module ids,and determining the active module if any. This is all handled by the DnnContextMessageHandler
.
ITabAndModuleInfoProvider
Anyone who needs to override the way that Tab and Module info is passed to a service call can implement their own ITabAndModuleInfoProvider
, and register it using the extension methods in HttpConfigurationExtensions
. This approach is neccesary because the context needs to be determined from a raw request before we know what controller will handle the request.
Authentication
Immediately after the context is determined the various authentication message handlers run. If the request is already authenticated the subsequent handlers each short circuit and do nothiing. If a valid web forms cookie is present in the request then ASP.Net will have already authenticated the request before any of the WebAPI handlers runs.
The order of the handlers is not particularly important as long as the WebFormsAuthMessageHandler always runs last. - ASP.Net authenticates forms cookie (not a message handler)
- BasicAuthMessageHandler
- DigestAuthMessageHandler
- WebFormsAuthMessageHandler
- This messaage handler does not actually perform the authentication, rather it completes the authentication within the DNN framework. There is no short circuit for this handler and it will always run.
Controller Selection
WebAPI no longer supports filtering controllers that apply to a route by namespace. Services Framework restores this functionality by implementing it's own IHttpControllerSelector. The Services Frameowrk implementation is in DnnHttpControllerSelector. Most of the code was copied from the DefaultControllerSelector in WebAPI, unfortunately while the WebAPI class does provide virtual methods, the way the code works makes it very difficult to effectively override those methods.
Dynamic Authorization Filters
WebAPI defaults to anonymous access to all web methods. Services Framework defaults to requiring host permissions to access a method. This is implemented by dynamically inserting a RequiresHost auth filter in the list of action filters run on a method. Services Framework implements it's own IFilterProvider in DnnActionFilterProvider to manage the dynamic insertion of the RequiresHost auth filter.
Optional Features
There are a whole series of features that can be applied to services as required.
Auth Attributes
Services Framework implements serveral auth filters that can be applied to classes and methods as needed. Each of the Auth filters is quite straight forward and well documented in the wiki and my other blog posts.
- DnnAuthorize
- DnnModuleAuthorize
- RequireHost
- SupportedModules
- DnnPageEditor
- This is an internal attribute introduced at the last second in 7.0. It will likely be replaced by a comprehensive and public approach to authorizing based on page permissions.
- ValidateAntiForgeryToken
IOverrideDefaultAuthLevel
Any auth attribute that wants to remove the default authorization level of RequiresHost must implement IOverrideDefaultAuthLevel
interface. Most of the above attributes implement this inteface but for example the SupportedModules does not as passing this auth filter says nothing about personal permissions.
StringPassThroughMediaTypeFormatter
StringPassThroughMediaTypeFormatter
adds support for formatting strings as text/html or text/plain to the default set of media type formatters provided by WebAPI. It is also easy to support additional media types from strings by adding a new instance of StringPassThroughMediaTypeFormatter
to the configuration.
Tracing
The TraceWriter
class implements the ITraceWriter interface and writes all trace output to the the DNN log4net logs. There is an app setting named EnableServicesFrameworkTracing that controls the logging.
UrlHelper
Because DNN may use more than one physical route for each logical route defined by a service's route mapper, it is not easy to know the true name of a route. WebAPI's UrlHelper requires the name of a route. The UrlHelperExtensions
class provides helper methods that wrap the standard UrlHelper methods and determine the correct route name from the context of the request.
Registering Features in a Page
The DotNetNuke.Framework.ServicesFramework
class provides methods that modules, skins etc. can use to ensure that ajax script support and/or anti-forgery support is included in a page.
jQuery plugin
The /js/dnn.servicesframework.js
file includes a jQuery plugin with various javascript helper functions to assist in making AJAX calls to Services Framework services. The methods themselves are all documented in the Wiki.