DotNetNuke and ApiExplorer

Exposing Web API endpoints from DotNetNuke is very easy to do and is extremely useful for accessing the internals of DNN. For example, if I want to get a list of all of the custom roles that are available for a particular DNN site, I can easily do so by creating a custom endpoint as illustrated at http://www.dnnsoftware.com/community-blog/cid/144400/webapi-tips.

There are a myriad of reasons you may want to create your own Web API endpoint within a custom DNN module, such as:

  • Exposing data to 3rd party systems
  • Accessing content from a companion mobile app
  • Automating tasks within your DNN site
  • Synchronizing data between DNN and an external system
  • Allowing different DNN modules to interact
  • Allow javascript on a module’s views to save and retrieve custom data

On a project I was working on, there was a need to expose an administrative api for a set of modules that were being developed.  As part of this effort, we wanted the api to be easily documented.  Luckily, DNN 8 now supports MVC modules, so it was a cinch to get started creating a new DNN MVC module project and then installing the Microsoft ASP.NET Web API Help Page package:

PM > Install-Package Microsoft.AspNet.WebApi.HelpPage

Note: If you don’t have a Visual Studio project template for a DNN MVC Module for Visual Studio, you can find one here https://github.com/ChrisHammond/DNNTemplates.  The one used in this example is a simplified and modified version of Chris Hammond’s template.

If you compile at this point and get an error such as:

CS0656 – Missing compiler required member 'Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo.Create'

you will need to add a reference to Microsoft.CSharp.dll.

Now, let’s take a look at what happened when you installed Microsoft’s Web API help page package.  The first thing you’ll notice is that the package creates an Areas folder and stuffs all of its content within a custom Area.  While this is useful when building a normal MVC or Web API project, it causes problems with DNN since DNN does not support Areas due to its custom routing.  To fix this, we simply need to perform the following steps

Move files and folders to the root of the module project

1) Move the /Areas/HelpPage/Controllers/HelpController.cs to the /Controllers/DnnApiHelpPageController.cs

The HelpController needs to be renamed to the controller name specified in the DNN manifest file.  In the case of this example, it is the DnnApiHelpPageController.cs file.

As part of this, the class name needs to be updated in code and the class needs to inherit from DnnController.  If you choose to use a different name, make sure you update the controller name in the DNN manifest file.

You may also want to update the namespace to correspond to the new location, but that’s not technically needed.

2) Move the /Areas/HelpPage/Views/HelpPage folder to the /Views/DnnApiHelpPage folder

As with all MVC controllers, the controller we moved above needs to have its corresponding views placed in the proper project folders.  Move all of the Views in the /Areas/HelpPage/Views folder to the /Views folder.  In order to make it easier, I renamed the existing Views/DnnApiHelpPage folder to a temporary name, copied the /Areas/HelpPage/Views/HelpPage folder to the /Views folder, and then renamed it to DnnApiHelpPage.

3) Delete the Areas/HelpPage/Controllers and Areas/HelpPage/Views folders

You shouldn’t need these folders anymore and they can safely be deleted.  You can verify that your views folder structure is correct by looking at the DnnApiHelpPageController.cs file in Visual Studio and confirming that calls to View() do not have a compilation error indicating a missing view.  The exception to this is the calls to View(ErrorViewName); since a view was not packaged as part of the nuget package.  You could easily create your own view for the error page, however.

4) Move all of the remaining files and folders to the root of the project except App_Start

The App_Start folder contains a configuration file that is normally called on the start of the web application.  Since we don’t have access to the web start event, we will handle this differently.  This is address later in this post when discussing a custom IApiExplorer implementation.

apihelppagearea_vsnet2015_aftermove.png

Quick Summary

At this point, all of the files should be moved over to the root of the project with the exception of the HelpPageConfig.cs file in the Areas/HelpPage/App_Start folder.  I recommend going through renaming all of the namespaces so that there is no confusion, but this should be an optional step.  If you were to add an instance of this module to a page on your DNN site, it should function and enumerate all of the exposed apis.

Side Note

If you were to build and deploy the module to your DNN installation at this point, you might have a failure due to not being able to load System.Web.Mvc.dll.  This is because DNN is using an older version of the System.Web.Mvc.dll (5.1.0.0) and the help page package references 5.2.3.  You can attempt to downgrade the version of System.Web.Mvc.dll by specifying the version of the package you want PM> Update-Package Microsoft.AspNet.Mvc -Version 5.1.0. Be aware that this will reinstall the help page nuget package and you may need to delete files/folders in the Areas/HelpPage folder again.  Another way to fix this issue is updating the binding redirect in the DNN sites web.config to

Additionally, you may get a similar error complaining about System.Net.Http.  You may need to modify your web.config for the DNN site in two places.  Adding another binding redirect as follows:

And adding an entry in the compilation section of the web.config

Fixing Other Issues

1) Fix Url.Action call in ApiGroup.cshtml

Right now, if you click on a link to drill into the details of an api, you will get an exception indicating that Default.aspx was not found or does not implement IController.  Obviously, this error message is not exactly accurate.

Because we moved/renamed the controller, we also need to update the call to  @Url.Action() in the
/Views/DnnApiHelpPage/DisplayTemplates/ApiGroup.cshtml view.  The correct line of code should change the second parameter to the name of the controller.  In this case, it is “DnnApiHelpPage” instead of “Help”

2) Update the @model directive on CSHTML pages

DNN handles MVC pages slightly different.  You need to setup the views so that DNN can process them appropriately.

If you look at the Api.cshtml file, you’ll see several issues which are quite straight forward to fix.  First, you should add the following:

The above uses the DnnWebViewPage<> as its base class for the view.  In addition, the generic type parameter indicates the type of the model passed to the view (the @model directive is no longer needed at this point).  Next, we use the ClientResourceManager.RegisterStyleSheet to register the appropriate stylesheet.  Lastly, the @using DotNetNuke.Web.Mvc.Helpers statement needs to be added since DNN supplies its own helper functions, @Html.DisplayFor() for example.

Note: In my environment, the IDE complained about the System.Net.Http.dll not being referenced even though it was.  After setting Copy Local to true on the reference, the IDE was happy again.

Similar updates should be applied to all of the views in the DisplayTemplates folder, as well as Index.cshtml and ResourceModel.cshtml.  Since there are quite a few, please see the attached project for reference.

3) Exception when you have multiple models with the same name

In many cases, you will have the same model name in different namespaces.  This will cause the help page generation to fail due to duplicate types detected.  This is because the help page generation uses the type name instead of the full type name.  There is a simple fix for this which is explained herehttp://www.c-sharpcorner.com/UploadFile/d132a2/workaround-in-Asp-Net-webapi-help-page/

In a nutshell, in the ModelNameHelper.cs file, update two lines of code to get the type’s full name.

Also, to make sure everything is complete, do the same in the HelpPageSampleGenerator.WriteSampleObjectUsingFormatter method:

At this point, you will have a fully functioning api browser that is always up to date since it examines the loaded assemblies to determine what endpoints are exposed.

There are a few strange issue in how the standard ApiExplore  object works with DNN.  I will explore these in a future blog post.

  1. DNN lists all actions for all routes: I’ve worked around this by subclassing ApiExplorer and creating my own filter in the ShouldExploreController method.
  2. DNN displays the route incorrectly: If you look at the screenshot above, you’ll see that the URL for the endpoint is listed incorrectly.  DNN displays the full name of the controller type in the URL instead of just the controller name.  To fix this, I added code in my custom ApiExplorer  constructor that will modify the RelativePath  property of each ApiDescription  object in ApiExplorer.ApiDescriptions.