Posts tagged ‘MVC’

July 2, 2012

List recent WordPress Blog Posts in ASP.NET MVC

Recently I was tasked with adding a list of recent blog posts from our WordPress site inside of an ASP.NET MVC web site. The initial strategy was going to involve fetching from the RSS feed, but after discovering the WordPress supported XML-RPC, I decided to explore that route first.

Using XML-RPC was supposed to be easy considering there is an available .NET library for it created by Charles Cook. The task should have been further simplified because there are several C# WordPress wrappers available.

After spending time with a couple of the WordPress wrappers available, it quickly became clear that the process of updating the wrappers to comply with the latest version of WordPress would be far more work than it was worth; especially considering the scope of this task.

So it was back to the original strategy of creating an RSS reader specifically for WordPress. After digging around a bit, I thankfully stumbled across the Syndication Feed class that is available in .Net 4.0 and above.

The Syndication Feed class basically provides the quick and easy solution that I was looking for while exploring the XML-RPC route. It really turned out to be a lot easier than I thought it could ever be.

So let’s get started. For this, I will assume that you are using an existing MVC application.

The first step is to add a reference to System.ServiceModel to your project as this is where the Syndication Feed class resides.

The second step is to create a View Model that will define a List of SyndicationItem’s:

using System.Collections.Generic;
using System.ServiceModel.Syndication;

namespace MvcApplication1.Models
{
    public class FeedViewModel
    {
        public FeedViewModel()
        {
            Posts = new List<SyndicationItem>();
        }

        public List<SyndicationItem> Posts { get; set; } 
    }
}

And in the Controller, we will need to populate the list:

using System.Linq;
using System.Web.Mvc;
using System.ServiceModel.Syndication;
using System.Xml;
using MvcApplication1.Models;

namespace MvcApplication1.Controllers
{
    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            var model = new FeedViewModel();

            using (var reader = XmlReader.Create("https://blog.yojimbocorp.com/feed/"))
            {
                var feed = SyndicationFeed.Load(reader);
                foreach (var post in feed.Items.Take(10))
                {
                    model.Posts.Add(post);
                }
            }

            return View(model);
        }
    }
}

You will of course want to customize the reader URL to be your own and most likely will want to create a configuration entry for it.

And finally, display the data in your view:

@model MvcApplication1.Models.FeedViewModel
           
@foreach (var post in Model.Posts)
{
    <td><a href="@post.Id" target="_blank">@Html.DisplayFor(model => post.Title.Text)</a></td>
    <br />
    
}

I highly recommend reviewing the SyndicationItem documentation when creating your view.

Advertisement
June 26, 2011

Facebook Connect Authentication injected into ASP.NET MVC 3 Forms Authentication

I was recently admiring the simplicity of the PHP example on how to implement Server-side Authentication using OAuth 2.0 on facebook.com. You can read the full document here: http://developers.facebook.com/docs/authentication/

The code below assumes that you’ve read the entire “Server-side Flow” section and have made some attempt to understand it.

I used Visual Studio 2010 and generated a new project of type “ASP.NET MVC 3 Web Application”, on the next step I choose Internet Application (once you’ve mastered the code below you’ll also be able to do this starting with Empty).

Next you can do some cleanup:

Delete Controllers/AccountController.cs

Delete Views/Account

In web.config you can delete the sections <membership>, <profile> and<roleManager>. You can also delete the <connectionStrings> section (or at least the one with the name “ApplicationServices”).

The reason for all this is that we won’t be using any of the built-in profile/membership/roles providers or the database connection string they rely upon.

Update the <authentication> node to look like this:

<authentication mode="Forms">
<!-- we don't want the expiration to slide, because we respect the Facebook token expiration settings -->
<forms loginUrl="~/Facebook/Login" slidingExpiration="false" />
</authentication>

Create a fake domain in your C:\Windows\System32\drivers\etc\hosts file (make sure you use this domain when you setup your Facebook application). For the sake of discussion I am going to use dev.somefakedomain.com. All you have to do is add this line to the file (you can edit it via Notepad – make sure you launch Notepad with Administrator privileges):

127.0.0.1 dev.somefakedomain.com

Modify your project settings to look like this image:

project settings

The port can be anything you like, as long as it’s not used by another application on your machine (I use whatever port Visual Studio assigned).

Add a new controller. I called it Facebook (if you don’t call it “Facebook”, don’t forget to change the loginUrl part of the <forms> tag in web.config).

Replace the entire body of your controller with this code:

using System;
using System.Collections.Generic;
using System.Web.Mvc;
using System.Security.Cryptography;
using System.Net;
using System.IO;
using System.Web.Script.Serialization;
using System.Web.Security;

namespace FacebookLogin.Controllers
{
    public class FacebookController : Controller
    {
        // these really should be in a config file:
        private string appId = "getyourown!";
        private string appSecret = "lookup your own!";
        /*
         * This is important. Using the default 'localhost' for debugging will fail,
         * because of problems with writing cookies and because Facebook requires a real
         * domain when you setup your application. What you have to do is create a 'fake'
         * domain by editing your C:\Windows\System32\drivers\etc\hosts file.
         * Google how to do this */
        private string authorizeUrl = "http://dev.somefakedomain.com:2064/Facebook/Login";

        public ActionResult Login()
        {
            try
            {
                string error = Request["error"];
                if (!String.IsNullOrEmpty(error))
                {
                    ViewBag.ErrorMessage = Request["error_description"];
                }
                else
                {
                    string code = Request["code"];
                    // when we get redirected here by Forms Authentication we'll
                    // have a ReturnUrl indicating the page we came from
                    string returnUrl = Request.QueryString["ReturnUrl"];

                    if (String.IsNullOrEmpty(code))
                    {
                        // CSRF protection
                        var hashBytes = new MD5CryptoServiceProvider().ComputeHash(Guid.NewGuid().ToByteArray());
                        string state = Convert.ToBase64String(hashBytes);
                        Session["state"] = state;

                        // add the return Url to the state parameter so Facebook can send it all back to us
                        if (!String.IsNullOrEmpty(returnUrl))
                        {
                            state += returnUrl;
                        }

                        // good programmers encode strings before passing them to a query string
                        state = Url.Encode(state);

                        // don't forget to change the "scope" values listed below
                        // to something appropriate for your facebook application
                        // facebook warns not to get greedy as users may deny access
                        string redirectUrl = "http://www.facebook.com/dialog/oauth?client_id=" + appId +
                                             "&redirect_uri=" +
                                             Url.Encode(authorizeUrl) +
                                             "&scope=publish_stream&state=" +
                                             state;
                        return Redirect(redirectUrl);
                    }

                    string sessionState = Convert.ToString(Session["state"]);
                    string requestState = Request.QueryString["state"];

                    if (!String.IsNullOrEmpty(requestState) &&
                        !String.IsNullOrEmpty(sessionState) &&
                        requestState.Length >= sessionState.Length &&
                        requestState.Substring(0, sessionState.Length) == sessionState)
                    {
                        string tokenUrl = "https://graph.facebook.com/oauth/access_token?client_id=" + appId +
                                          "&redirect_uri=" + Url.Encode(authorizeUrl) +
                                          "&client_secret=" + appSecret +
                                          "&code=" + code;
                        string response = GetPageContent(tokenUrl);

                        var responseDictionary = ParseQueryString(response);
                        if (responseDictionary.ContainsKey("access_token"))
                        {
                            // note: you don't HAVE to respect this, as long as you
                            // get a new token before trying to use the FB API
                            double facebookTokenExpirationSeconds =
                                Convert.ToDouble(responseDictionary["expires"]);

                            // Note: you may want to store responseDictionary["access_token"] and
                            // facebookTokenExpirationSeconds somewhere so you can use it for other FB API requests
                            string graphUrl = "https://graph.facebook.com/me?access_token=" +
                                              responseDictionary["access_token"];

                            var serializer = new JavaScriptSerializer();
                            dynamic facebookUser = serializer.DeserializeObject(GetPageContent(graphUrl));

                            // grab facebook name and Id to use as our forms authentication ticket
                            string facebookName = facebookUser["name"];
                            long facebookUserId = Convert.ToInt64(facebookUser["id"]);

                            // get the cookie the way forms authentication would put it together.
                            //Note: I am using the facebookUserId as the "username" as it's guaranteed to be unique
                            var authCookie = FormsAuthentication.GetAuthCookie(facebookUserId.ToString(), true);
                            var ticket = FormsAuthentication.Decrypt(authCookie.Value);
                            // we want to change the expiration of our forms authentication cookie
                            // to match the token expiration date, but you can also use your own expiration
                            DateTime expiration = ticket.IssueDate.AddSeconds(facebookTokenExpirationSeconds);
                            var newTicket = new FormsAuthenticationTicket(ticket.Version, ticket.Name,
                                ticket.IssueDate, expiration, ticket.IsPersistent, facebookName);

                            // encrypt the cookie again
                            authCookie.Value = FormsAuthentication.Encrypt(newTicket);

                            // manually set it (instead of calling FormsAuthentication.SetAuthCookie)
                            Response.Cookies.Add(authCookie);

                            // If we added the Redirect Url to our 'state', grab it and redirect
                            if (requestState.Length > sessionState.Length)
                            {
                                returnUrl = requestState.Substring(sessionState.Length);
                                if (!String.IsNullOrEmpty(returnUrl))
                                {
                                    return Redirect(returnUrl);
                                }
                            }

                            // otherwise redirect back to Home or whatever your default page is
                            return RedirectToAction("Index", "Home");
                        }
                        else
                        {
                            ViewBag.ErrorMessage = "Facebook authorization replied with this invalid response: " +
                                response;
                        }

                    }
                    else
                    {
                        ViewBag.ErrorMessage =
                            "There is a problem with the redirect from Facebook. You may be a victim of CSRF.";
                    }

                }
            }
            catch (Exception ex)
            {
                ViewBag.ErrorMessage = "Login failed with this exception: " + ex.Message;
            }

            return View();
        }

        public ActionResult Logout()
        {
            FormsAuthentication.SignOut();
            return RedirectToAction("Index", "Home");
        }

        private static string GetPageContent(string url)
        {
            var request = WebRequest.Create(url);
            var reader = new StreamReader(request.GetResponse().GetResponseStream());
            return reader.ReadToEnd();
        }

        private static IDictionary ParseQueryString(string query)
        {
            var result = new Dictionary();

            // if string is null, empty or whitespace
            if (string.IsNullOrEmpty(query) || query.Trim().Length == 0)
            {
                return result;
            }

            foreach (var pair in query.Split("&".ToCharArray()))
            {
                if (!String.IsNullOrEmpty(pair))
                {
                    var pairParts = pair.Split("=".ToCharArray());
                    if (pairParts.Length == 2)
                    {
                        result.Add(pairParts[0], pairParts[1]);
                    }
                }
            }

            return result;
        }
    }
}

Add a Login view and replace it with code:

@{
    ViewBag.Title = "Facebook Login Error";
}

@if (!String.IsNullOrEmpty(ViewBag.ErrorMessage))
{

Error: @ViewBag.ErrorMessage

}
else
{

Facebook login encountered an error without a description.

}

Replace the contents of _LogOnPartial.cshtml with this code:

@if (Request.IsAuthenticated)
{
    string facebookName = "BLANK";
    FormsIdentity ident = User.Identity as FormsIdentity;

    if (ident != null)
    {
        FormsAuthenticationTicket ticket = ident.Ticket;
        facebookName = ticket.UserData;
    }

    Welcome <strong>@facebookName</strong>!
    [ @Html.ActionLink("Logout", "Logout", "Facebook") ]
}
else
{
    @:[ @Html.ActionLink("Login", "Login", "Facebook") ]
}

Now that you’ve done this you can use the built-in [Authorize] tag on any controller method and the user will get sent to your Facebook/Login controller. The sky is the limit from here.

You could also use the Facebook SDK on codeplex, which provides a richer programming model and more functionality, but if you want to understand what’s happening behing the scenes, this gives you a good idea of the handshake required for successful Facebook authentication.

Note that this example uses the built-in Session object, which will fail on a web server farm. You have a few options:

1) You can change the code that uses Session to rely on cookies instead

2) You can use a session provider that relies on a database or a separate app server

3) You can get rid of that code if you don’t care about CSRF

If there is interest, I will post a working project example.