Hi Erik
The project template does certainly seem like a good quick start for a Canvas Application. (I'll admit I mainly used and looked around the Nuget package of the code and Codeplex, rather than the VS installer.) Some general feedback is below.
- the typical model objects on the Canvas Application home route are Signed Request, Request Ids, Facebook Source if the native facebook flow is used; this seems a little hidden
- would normally associate Canvas homepage route with an AccountController type control logic, accessing user and friends list from an FacebookService rather than model binders
- we use an attribute IframeCookieSupportAttribute to allow forms authentication cookies cross browser within an iframe
- nearly all apps have a companion Tab Application; and adding a MobileController would create a good placeholder
- no common access to Signed Request and Access Token from within a controller; shame adding access token in ViewBag rather than an IPrincipal
- canvas authentication redirect results is very userful so a seperate ActionResult would be good for reuse
- the template should add Terms and Privacy actions to remined developers that these are required for Canvas Applications
- there is quite a variety in recent App Settings naming conventions in frameworks "facebook:AppSecret" would be my preference, as with Razor
- wonder whether getting full friends list is a good sample, as this can be a very long list; would be more explicit using async controllers
- worry that most apps now use javascript Facebook Auth Dialog, as Canvas homepage normally has to promote and invite new users
Pseudo code
public class CanvasController : Controller {
[FacebookAuthorize(Permissions="email")]
[IframeCookieSupport]
public async Task<ActionResult> Index(FacebookSignedRequest signedRequest, FacebookSource facebookSource, string[] requestIds) {
if(!string.IsNullOrEmpty(signedRequest.UserId)) {
FacebookAuthentication.SetAuthCookie(signedRequest.UserId, signedRequest.AccessToken);
}
var me = FacebookService.GetMeAsync(signedRequest.AccessToken);
var appFriends = FacebookService.GetAppFriendsAsync(signedRequest.AccessToken);
await Task.WhenAll(me, appFriends);
ViewBag.Me = me.Result;
ViewBag.AppFields = appFriends.Result;
return View();
}
public ActionResult TermsAndConditions() {
return View();
}
public ActionResult PrivacyPolicy() {
return View();
}
}
public class TabController : Controller {
public ActionResult Index(FacebookSignedRequest signedRequest) {
if(!signedRequest.Page.Liked) {
return View("LikeGate");
} else {
return View();
}
}
}
public class MobileController : Controller {
[FacebookAuthorize(Permissions="email")]
public ActionResult Index(string[] requestIds) {
return View();
}
}
public class FacebookAuthorizeRedirectResult : ActionResult {
....
}
public class FacebookUser : IPrincipal {
public string FacebookId { get; }
public string AccessToken { get; }
....
}
public static class FacebookPrincipalExtension {
public static FacebookPrincipal AsFacebookUser(this IPrincipal user) {
....
}
}
public class IframeCookieSupportAttribute : ActionFilterAttribute
{
public override void OnResultExecuted(ResultExecutedContext filterContext)
{
var response = filterContext.HttpContext.Response;
if (!filterContext.IsChildAction && !response.IsRequestBeingRedirected)
{
response.AddHeader("p3p", "CP=\"CAO PSA OUR\"");
}
}
}
Cheers.
Andy