Adding Social Sharing to a Web Site
How I built a social sharing component for my own web site and added a secured geo-located audit trail. Step by step, let’s analyse technologies and source code for developing this component.
This is the first of two articles about adding social sharing capability to a web site, and auditing its usage. In this first article I’ll focus on building the sharing functionality; in the second article I’ll present a secured geo-located audit trail.
There are many available components for adding social sharing capability to a web site, that is the possibility to share a specific page on the like of Facebook, Twitter, LinkedIn, etc. However, I decided to implement my own component for my blog, in order to achieve a specific look & feel that I wanted, and obviously to gain more experience on how to deal with the several social network providers.
At time of writing this article, I have implemented the sharing functionality on the following social network sites:
· Google+
I have also decided to add a “Share by Email” option, for those like me who like to send emails to themselves with URLs of web pages to read (one day when I will have spare time…). In addition, when sharing the page, an audit trail is generated, capturing information about the client. A significant piece of information is the IP address of the referrer, i.e. the client browser. From this IP address, I can locate the country of origin of the request, for purely statistical pleasure J
I took a top-down approach in designing this component, or as I prefer to say, a UXF (User Experience First) approach. In a nutshell:
· I designed the component using a mix of Bootstrap for responsive design, and have added an Entypo font for the social glyphicons.
· I added the component to an MVC view and connected it to an MVC controller with an Ajax call for the auditing part; I could have used a WebAPI, but I preferred to stick to simplicity of execution in this case. The auditing action is secured using a Triple DES-encrypted security token.
· Once the audit information is stored in the database (Entity Framework takes care of the data access layer), I used a third-party service for resolving the geolocation of the client request, based on its IP address.
The end result visible to the user looks like this (and you can see on this web page too – feel free to use it!):
As said, the component is implemented in a MVC view, specifically a Razor partial view.
From a look & feel perspective, I like minimalist layouts, well-spaced and with simple lines. All the sharing buttons are implemented as Bootstrap pills. In my case they are displayed horizontally and justified within the parent container, but this can be easily changed to accommodate different layouts.
But before getting into the implementation details of the component itself, we need to look at the page that hosts the sharing button. For the sake of this article, all we need is a simplified Article model, implemented in a POCO class as follows:
public class Article
{
public int Id { get; set; }
[StringLength(200)]
[Required, Index(IsUnique = true)]
public string Title { get; set; }
[DataType(DataType.MultilineText)]
public string Abstract { get; set; }
}
The Article model.
An article is uniquely identified by the Id property. This is convenient for Entity Framework to save the article into a database table, having the Id as identity. There is a required title, which is also indexed to be unique (that is, no two articles can have the same title), and an optional abstract. There should be additional properties for content, format, creation date, publish date, etc. but this is off topic for the purpose of the sharing buttons component.
The solution attached to this article has an Articles controller that is generated with the default scaffolding of an MVC 5 controller with EF CRUD operations: Index, Details, Edit, Create and Delete. I won’t go into the details of this controller, this is purely auto-generated code in Visual Studio. All I need this controller for is to provide a backend mechanism for entering articles in the application.
The actual display of an article is done by the Default controller, which is the controller that is executed when running the application. The Index action represents the “home page” of the application, which is based on a shared layout (_Layout.cshtml) and content defined in the Index.cshtml view. This is all very standard ASP.NET MVC. I am mentioning this as an opportunity to define my library dependencies, all managed via NuGet, obviously. The list of referenced libraries, server and client side, is in the following picture:
List of referenced libraries in the project.
All these libraries are automatically added to the solution when created the first time, and Entity Framework added when a controller with EF actions is generated, except for Microsoft jQuery Unobtrusive Ajax. This is a plugin for jQuery that is used by the MVC Ajax helper for invoking actions on a controller via Ajax, that is without post back of the entire page to the server. Convenient, as we will see later, for the audit trail tracking.
Back to our home page, the Index action in the Default controller simply retrieves a list of available articles ordered alphabetically by title, and passes it to the Index view.
public ActionResult Index()
{
using (var db = new DatabaseContext())
{
return View(db.Articles.OrderBy(a => a.Title).ToList());
}
}
The Index action in the Default controller.
The view accepts an enumerable of Article and displays them in an unordered list:
<ul>
@foreach (var item in Model)
{
<li>@Html.ActionLink(item.Title, "Open", new { id = item.Id })</li>
}
</ul>
List of articles in the Index view.
Not particularly fancy and sophisticated UI, I know J but it gets to the point immediately!
As you notice in the above code, each article is displayed in a list item as a link (<a> tag generated by the Html.ActionLink helper). This is a link to the Open action of the same Default controller, which, as you may guess from the name, simply “opens” the article, i.e. displays its content as a web page.
The Open action is defined as follows:
public ActionResult Open(int id)
{
using (var db = new DatabaseContext())
{
return View(db.Articles.Find(id));
}
}
The Open action in the Default controller.
This action finds the specific article by Id in the database context, and passes it on to the view. The view, in turn, displays the article as a web page.
<h2>@Model.Title</h2>
<p>
@Model.Abstract
</p>
<hr />
@Html.Action("ShareButtons", "Social", new { id = Model.Id })
The very important line of code in this extremely simple view is the last one, the Html.Action call to the ShareButtons action in the Social controller.
This is one of the way, and probably my preferred one, to include reusable components in a web page. By creating self-contained partial views with all the resources included (HTML, CSS and potentially JavaScript code) in a single separate file, it is extremely easy to embed that partial view into a parent view. Html.Action fits exactly the purpose, allowing to call an action on a controller, getting HTML code in return, and then displaying that code inline within the container page.
The share buttons are implemented in the ShareButtons action and view. I have introduced a dedicated MVC controller for the purpose, to keep action isolated. The Social controller contains the actions for displaying the view and auditing the use of the share buttons.
[ChildActionOnly]
public ActionResult ShareButtons(int id)
{
using (var db = new DatabaseContext())
{
return PartialView(new
{
Article = db.Articles.Find(id),
SecurityToken = Request.Url.Host.Encrypt(EncryptionKey, EncryptionIV)
}.ToDynamic());
}
}
The ShareButtons action in the Social controller.
This action performs a few interesting things:
1. It takes the article Id as input parameter and finds the article in the database context.
2. It generates a security token to pass to the view for validating subsequent Ajax calls for auditing purpose.
3. It puts together the Article and the Security Token into an anonymous object, but because anonymous objects cannot be passed to an MVC view, it uses the ToDynamic() extension to convert the anonymous object into a dynamic object (more precisely, into an ExpandoObject).
Let’s expand on the need for a dynamic model. I’m discussing the securing auditing in the second part of this article.
The ToDynamic extensions used in the ShareButtons action converts an anonymous object to a dynamic object, more precisely an ExpandoObject. This is necessary because it is not possible to pass an anonymous object directly to a view, so I need to pass through a dynamic object… but hold on, why do we need an anonymous object at all? Right, good question! J
I need this data in the component view:
· Id of the article
· Title of the article
· URL of the web page
· The security token to secure auditing
Id and Title of the article are used for auditing and sharing, respectively. The best way to pass this is in the Article object itself, as they are already there. So my model, at the moment, would likely be the Article class.
However, I also need the URL of the article page, and the security token. The URL is also used for sharing by the buttons (that’s exactly what we are sharing, the URL of the article!). But this is easy to obtain from the client request: Request.Url.AbsoluteUri.
The security token would also need to be passed to the view. Now, there are different ways of passing values from a controller to a view, there are different schools of thought about each way, and all of them are valid and legitimate. So I simply have to pick one!
1. Using ViewBag: I could pass the Article as the model of the view, and then transfer the security token via the ViewBag.
2. Using a View Model: I could create a new class to contain exactly what I need, an Article and a SecurityToken property; this would be a bespoke class strongly tied to the view with very little reusability, but perfectly valid for the purpose.
3. I have decided to instantiate a new anonymous object containing two properties, Article and SecurityToken; this object is then converted to an ExpandoObject for the mentioned reasons, and then passed to the view.
This last approach has a better object-oriented flavour, for my like, as I could add other properties directly to the anonymous object as needed, and without modifying a separate class, as it would be in case of the approach with a View Model. As for the ViewBag, that’s perfectly fine but properties are not strongly typed, so when using it in the view to access the article Id and Title, I’d need to cast to the Article class first. This creates an unnecessary dependency between the view and the model.
So anonymous approach it is! Let’s see how to convert from anonymous to dynamic:
1. Create an instance of an ExpandoObject.
2. For each public property defined at instance level in the anonymous object, add the property name and value combination to the expando object as key and value; this is possible because the ExpandoObject class implements the IDictionary<string, object> interface.
The source code is:
public static ExpandoObject ToDynamic(this object data)
{
dynamic expando = new ExpandoObject();
foreach (PropertyInfo property in data.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance))
{
if (property.CanRead)
{
(expando as IDictionary<string, object>).Add(property.Name, property.GetValue(data, null));
}
}
return expando;
}
Extension to convert from anonymous object to dynamic.
The ShareButtons view is where the layout of the component is implemented, and the code for sharing on the social networks and auditing the action. It is technically a partial view, which means that no layout is used, as the component is embedded in the hosting page.
There are a lot of elements in this page… so let’s go in order. First of all, like any good MVC view, it should have a strongly typed model associated. As discussed previously, our model is a dynamic object that contains two properties, Article and SecurityToken. So the very first line of code of the ShareButton.cshtml view is indeed the declaration of the model.
@model dynamic
Then I need to encode the article title and the page URL to pass this information to each share button, as we will see soon. So let me allocate two local string variables with the URL-friendly title and URL of the article.
@{
string encodedTitle = Uri.EscapeDataString(Model.Article.Title);
string encodedUrl = Uri.EscapeDataString(Request.Url.AbsoluteUri);
}
Now the UI of the component. I am using Bootstrap pills for the buttons, the Entypo Social font for the icons, a bespoke CSS stylesheet, jQuery for event handling, Bootstrap modal for email sharing, Ajax for auditing… lot of stuff! Ok, in order then, the buttons are “pills”, the code below is for the “Share on Facebook” button. The other buttons are all similar, except for the “Share by Email” as we will see in a moment.
<ul class="nav nav-pills nav-justified" data-id="@Model.Article.Id">
<li>
<a class="social social-facebook"
data-social="Facebook" data-src= "https://www.facebook.com/sharer/sharer.php?u=@(encodedUrl)&t=@(encodedTitle)&display=popup"
title="Share on Facebook">
<span class="entypo-social facebook"></span>
</a>
</li>
. . .
</ul>
The Razor code is self-explanatory, I’m passing the encoded URL and the encoded title to the sharer service of Facebook, in this case. Each social network site has its own syntax for sharing, but they all look very similar. A particular mention needs the assignment of the article id in the data-id attribute of the navigation list. This will be used later for the auditing action. There is large use of “data” attributes because unobtrusive JavaScript is bound to the “social” class to respond to the “click” event.
$(function () {
$(".social").on("click", function () {
var src = $(this).data("src");
if (social != "Email") {
window.open(src, "SocialSharing", "<windows options>", true);
}
});
});
Share button On Click event handler
A new window is opened with the indicated URL taken from the data-src attribute, but only if the pressed button is not for sharing by email, because it that case the functionality is slightly different.
The icons for each share buttons are glyphicons. For the social network site, we use the Entypo Social font, whereas for email sharing, the Bootstrap glyphicon-envelope will suffice.
In order to use the Entypo Social font, the following font types should be added to the project and referenced in the Social.css stylesheet.
Entypo Social fonts along with the Bootstrap Glyphicons.
The CSS file defines a new font-face directive, and a CSS class “entypo-social” that references it. For each of the social network sites used in this sharing component, a sub-class is defined to be used in the HTML code for each character of the font representing the icon of the social network site.
@font-face {
font-family: 'EntypoSocialRegular';
src: url('../fonts/entypo-social.eot');
src: url('../fonts/entypo-social.eot?#iefix') format('embedded-opentype'),
url('../fonts/entypo-social.woff') format('woff'),
url('../fonts/entypo-social.ttf') format('truetype'),
url('../fonts/entypo-social.svg#EntypoRegular') format('svg');
font-weight: normal;
font-style: normal;
}
.entypo-social {
font-family: 'EntypoSocialRegular';
font-size: 2em;
font-weight: normal;
line-height: 0;
}
.entypo-social.twitter:before { content:'\F309'; }
.entypo-social.facebook:before { content:'\F30C'; }
.entypo-social.googleplus:before { content:'\F30F'; }
.entypo-social.pinterest:before { content:'\F312'; }
.entypo-social.linkedin:before { content:'\F318'; }
CSS for the Entypo Social font.
For sharing by email, a Bootstrap modal is used, instead. The body of the modal contains a form for entering email address and body of the email. The body is prefilled with the article’s title and URL, but it can be edited.
Share by Email form.
When sending the email, the form is submitted back to the ShareByEmail action in the Social controller. Submission is actually done via Ajax and the form is declared using the Ajax.BeginForm helper, which requires the Microsoft jQuery Unobtrusive Ajax library referenced via NuGet.
@using (Ajax.BeginForm("ShareByEmail", "Social", new AjaxOptions { HttpMethod = "POST", ... }))
The ShareByEmail action responds only to POST requests, validates the anti-forgery token, and accepts input parameters as a form collection. Then, it sends the email using MailMessage and SmtpClient in the System.Net.Mail namespace.
[HttpPost]
[ValidateAntiForgeryToken]
public ContentResult ShareByEmail(FormCollection values)
{
string emailAddress = values["email_address"];
string emailSubject = values["email_subject"];
string emailBody = values["email_body"];
. . .
Portion of the ShareByEmail action. Full source code in the project online.
SMTP details are configured in the appSettings section of the Web.config file:
<appSettings>
<add key="SmtpServer" value="[SMTP SERVER]"/>
<add key="SmtpUser" value="[SMTP USER]"/>
<add key="SmtpPassword" value="[SMTP PASSWORD]"/>
</appSettings>
And referenced by a strongly typed constants using the new C# 6 syntax for properties:
public static string SmtpServer => System.Configuration.ConfigurationManager.AppSettings["SmtpServer"];
The sharing buttons are all developed and fully functional, you can find the full source code of the solution on CodePlex. Feel free to use it, it’s completely open source!
In the next article I will describe the implementation of a secured geo-located auditing capability, to track usage of the sharing buttons.
Project Name: SocialSharing