Products

Solutions

Resources

Partners

Community

About

New Community Website

Ordinarily, you'd be at the right spot, but we've recently launched a brand new community website... For the community, by the community.

Yay... Take Me to the Community!

The Community Blog is a personal opinion of community members and by no means the official standpoint of DNN Corp or DNN Platform. This is a place to express personal thoughts about DNNPlatform, the community and its ecosystem. Do you have useful information that you would like to share with the DNN Community in a featured article or blog? If so, please contact .

The use of the Community Blog is covered by our Community Blog Guidelines - please read before commenting or posting.


Services Framework with Meat

This post describes a complete module built on Services Framework, HTML, and Javascript. It is intended to serve as a Services Framework example with more meat. It also shows how easy Services Framework makes it to step away from traditional code-behind style development. Detailed reference material on Services Framework can be found in the Wiki.

The entire source for this module can be found on github. The module was originally written on 6.2/MVC as part of my Day Of DNN Charlotte 2012 presentation. For those of you interested in converting from MVC to WebAPI you should look at my previous blog post on the topic. I have also maintained an MVC_62 branch in the github repo so you can compare the MVC and WebAPI versions, as well as review the check-ins that I made in the conversion process.

The example module is called ExploreSettings and allows users to view and edit host and portal level settings of a DNN site. Here are the core requirements that the module must meet:

  • Allows host users to view and change host and portal settings
  • Allows users with view module permission to view portal settings
  • Allows users with edit module permission to edit portal settings

In this post I am going to jump over the basics and head straight for the meat. If you need a refresher on the basics of Services Framework, check out my earlier blog post.

Controller

First a look at the controller and it's methods

[SupportedModules("ExploreSettings")]
public class SettingsController : DnnApiController
{....}

[SupportedModules("ExploreSettings")] locks this controller to the ExploreSettings module. Any request that does not provide a tab and module id that matches an ExploreSettings module will be refused. This ensures that all the DnnModuleAuthorize attributes used on the methods will in fact be authorized against an instance of ExploreSettings, and not any random module instance.

[HttpGet]
public HttpResponseMessage HostSettings()
{
    return Request.CreateResponse(HttpStatusCode.OK, HostController.Instance.GetSettingsDictionary());
}

The HostSettings method supports getting a list of all the host settings in the system. There are no authorization attributes applied to this method because the default behavior is to require host level access which is exactly what we want for this method. We could explicitly apply the RequiresHost attribute and it would make no difference. The RequiresHost attribute is primarily meant for situations where a lower level of authorization is applied to the controller, and a few specific methods need to be elevated back to requiring host level access.

The code inside the method is straight forward. We simply fetch a dictionary containing all the host settings for the system, and let the WebAPI content negotiation take care of converting the dictionary to JSON or XML or whatever format is appropriate for the client.

[HttpGet]
[DnnModuleAuthorize(AccessLevel = SecurityAccessLevel.View)]
public HttpResponseMessage CurrentPortalSettings()
{
    return Request.CreateResponse(HttpStatusCode.OK, TestablePortalController.Instance.GetPortalSettingsDictionary(PortalSettings.PortalId));
}

The CurrentPortalSettings method supports getting a list of all the portal settings in the current portal. Current was prefixed to the name because DnnApiController already has a PortalSettings property.

[DnnModuleAuthorize(AccessLevel = SecurityAccessLevel.View)] ensures that only users with view permissions to a module can call this method. DnnModuleAuthorize should always be used in concert with SupportedModules. Without SupportedModules it would be possible to authorize against view permissions for any module instance. Since most sites will have at least one page visible to anonymous users, it would be possible for a hacker to call this function without any user credentials at all.

public class UpdateSettingDTO
{
    public string Key { get; set; }
    public string Value { get; set; }
}

[HttpPost]
[ValidateAntiForgeryToken]
public HttpResponseMessage UpdateHostSetting(UpdateSettingDTO submitted)
{
    HostController.Instance.Update(submitted.Key, submitted.Value);
    return Request.CreateResponse(HttpStatusCode.OK);
}

UpdateHostSetting supports the ability for hosts to edit host settings. Again there is no Authorize attribute so this method defaults to requiring a host user.

A big difference between MVC and WebAPI is in how they process the bodies of requests. MVC would analyze the body and map it to individual parameters in the method signature. WebAPI treats all bodies as a single object and will return create a single POCO from the request body. The UpdateSettingDTO gives WebAPI a POCO to map the request body too.

[ValidateAntiForgeryToken] This attribute helps to prevent CSRF attacks on services by setting an encrypted cookie and hidden field when a web page is requested. When submitted the cookie and a header are submitted and validated to ensure that they match. Since a 3rd party site will not be able to access the cookie value they will not be able to set ann appropriate header in a CSRF style attack. Use of ValidateAntiForgeryToken on all services intended for AJAX use is recommended.

[HttpPost]
[ValidateAntiForgeryToken]
[DnnModuleAuthorize(AccessLevel = SecurityAccessLevel.Edit)]
public HttpResponseMessage UpdatePortalSetting(UpdateSettingDTO submitted)
{
    TestablePortalController.Instance.UpdatePortalSetting(PortalSettings.PortalId, submitted.Key, submitted.Value);
    return Request.CreateResponse(HttpStatusCode.OK);
}

UpdatePortalSetting supports the requirement for users with edit rights to the ExploreSettings module to be able to update a portal setting. There is nothing new that here that was not already covered on a previous controller method.

That's it for the service controller. It's pretty straight forward, now I'll move on to the client.

Code Behind

public partial class ExploreSettings : PortalModuleBase
{
    protected override void OnLoad(EventArgs e)
    {
        base.OnLoad(e);
        ServicesFramework.Instance.RequestAjaxAntiForgerySupport();
    }
}

The above code is the entire code behind for this module. I smile every time I see it again. :)

ServicesFramework.Instance.RequestAjaxAntiForgerySupport() This call enables anti-forgery support on any page that the module is included in. Be sure to call this method whenever you are using services that have the ValidateAntiForgeryToken attribute. It also registers the services framework AJAX helper script and jQuery.

In the Browser

The bulk of the code for the ExploreSettings module is straight forward HTML and Javascript. All of the HTML and Javascript for this module lives inside the ExploreSettings.ascx file. I will only highlight pieces unique to working with the Services Framework.

$(document).ready(function () {
var moduleScope = $('#<%=ScopeWrapper.ClientID %>'),
    self = moduleScope,
    sf = $.ServicesFramework(<%=ModuleId %>);

The moduleScope technique used in this module is not Services Framework specific, but it is a good technique to ensure that your module will work properly when there is more than one instance of the module on a page. It also helps prevent accidental conflicts with 3rd party modules that may share the same page.

sf = $.ServicesFramework(<%=ModuleId %>) This line initializes the ServicesFramework plugin and prepares it to work with the current module instance.

self.updateKey = function() {
    var postData = { key: $("#key", moduleScope).text(), value: $("#value", moduleScope).val() };
    var action = self.getUpdateAction();

    $.ajax({
        type: "POST",
        url: sf.getServiceRoot('ExploreSettings') + "Settings/" + action,
        data: postData,
        beforeSend: sf.setModuleHeaders
    }).done(function() {
        self.loadSettings();
    }).fail(function (xhr, result, status) {
        alert("Uh-oh, something broke: " + status);
    });
};

updateKey is a pretty typical method for posting data via jQuery. There are only two lines specific to Services Framework.

url: sf.getServiceRoot('ExploreSettings') + "Settings/" + action The getServiceRoot method will generate the correct URL to use for calling a service in the current portal up to the /API/. The developer can then easily append the remainder of the the url, typically it is for the {controller}/{action} portion when using RPC style services.

beforeSend: sf.setModuleHeaders setModuleHeaders takes care of several things for you. It sets the tab and module id headers which is what enables DnnModuleAuthorize and the DnnApiController.ActiveModule property. It also sets the anti-forgery header to support the ValidateAntiForgeryToken attribute.

That is really all there is to it. By writing a small service with the Services Framework, and a tiny bit of boilerplate code behind, it is suddenly easy to write modern DNN modules entirely in HTML and Javascript. You are not limited to plain javascript and jQuery either, you can mix in whatever javascript library you prefer. Many people have been using Knockout with Services Framework recently. I find the whole thing an exciting breath of fresh air.

Comments

Jordan
hello scott, thanks for the article.

I have a quick question. The following line:
sf = $.ServicesFramework(<%=ModuleId %>);
does not work when it is placed inside an external js file that is linked to the ascx file. This is because .js files are not processed by the server and therefore the inline tag <%=ModuleId %> is not resolved and will break the js file. I used a hidden field to hold the moduleid with a unique css id and then use Jquery to get that value and pass it to the servicesframework initialization:

EXAMPLE:
in ascx file:


in js file:
var mid = $("#KS_moduleid").data("mid");

Putting this code in an eternal file is really ideal if you are using Angular.js, Knockout.js or some other type of javascript front end framework.

I am wondering how you pass your module id an external js file?
Jordan Tuesday, October 21, 2014 2:46 PM (link)
Bruce Chapman
@Jordan, in your .ascx, register the moduleId (and portalId, normally) in some type of correctly scoped javascript variable of the page. You can then use this as an input to the ServicesFramework calls.
Bruce Chapman Tuesday, May 12, 2015 8:50 AM (link)

Comment Form

Only registered users may post comments.

NewsArchives


Aderson Oliveira (22)
Alec Whittington (11)
Alessandra Daniels (3)
Alex Shirley (10)
Andrew Hoefling (3)
Andrew Nurse (30)
Andy Tryba (1)
Anthony Glenwright (5)
Antonio Chagoury (28)
Ash Prasad (37)
Ben Schmidt (1)
Benjamin Hermann (25)
Benoit Sarton (9)
Beth Firebaugh (12)
Bill Walker (36)
Bob Kruger (5)
Bogdan Litescu (1)
Brian Dukes (2)
Brice Snow (1)
Bruce Chapman (20)
Bryan Andrews (1)
cathal connolly (55)
Charles Nurse (163)
Chris Hammond (213)
Chris Paterra (55)
Clint Patterson (108)
Cuong Dang (21)
Daniel Bartholomew (2)
Daniel Mettler (181)
Daniel Valadas (48)
Dave Buckner (2)
David Poindexter (12)
David Rodriguez (3)
Dennis Shiao (1)
Doug Howell (11)
Erik van Ballegoij (30)
Ernst Peter Tamminga (80)
Francisco Perez Andres (17)
Geoff Barlow (12)
George Alatrash (12)
Gifford Watkins (3)
Gilles Le Pigocher (3)
Ian Robinson (7)
Israel Martinez (17)
Jan Blomquist (2)
Jan Jonas (3)
Jaspreet Bhatia (1)
Jenni Merrifield (6)
Joe Brinkman (274)
John Mitchell (1)
Jon Henning (14)
Jonathan Sheely (4)
Jordan Coopersmith (1)
Joseph Craig (2)
Kan Ma (1)
Keivan Beigi (3)
Kelly Ford (4)
Ken Grierson (10)
Kevin Schreiner (6)
Leigh Pointer (31)
Lorraine Young (60)
Malik Khan (1)
Matt Rutledge (2)
Matthias Schlomann (16)
Mauricio Márquez (5)
Michael Doxsey (7)
Michael Tobisch (3)
Michael Washington (202)
Miguel Gatmaytan (3)
Mike Horton (19)
Mitchel Sellers (40)
Nathan Rover (3)
Navin V Nagiah (14)
Néstor Sánchez (31)
Nik Kalyani (14)
Oliver Hine (1)
Patricio F. Salinas (1)
Patrick Ryan (1)
Peter Donker (54)
Philip Beadle (135)
Philipp Becker (4)
Richard Dumas (22)
Robert J Collins (5)
Roger Selwyn (8)
Ruben Lopez (1)
Ryan Martinez (1)
Sacha Trauwaen (1)
Salar Golestanian (4)
Sanjay Mehrotra (9)
Scott McCulloch (1)
Scott Schlesier (11)
Scott Wilkinson (3)
Scott Willhite (97)
Sebastian Leupold (80)
Shaun Walker (237)
Shawn Mehaffie (17)
Stefan Cullmann (12)
Stefan Kamphuis (12)
Steve Fabian (31)
Steven Fisher (1)
Tony Henrich (3)
Torsten Weggen (3)
Tycho de Waard (4)
Vicenç Masanas (27)
Vincent Nguyen (3)
Vitaly Kozadayev (6)
Will Morgenweck (40)
Will Strohl (180)
William Severance (5)
What is Liquid Content?
Find Out
What is Liquid Content?
Find Out
What is Liquid Content?
Find Out