Featured

Customizing social login buttons in Asp.Net MVC 5

Introduction

In Asp.Net Mvc 5, support for Oauth 2.0  comes in bundled into most of the default web application project types in Visual Studio via Microsoft Owin. Adding Oauth 2.0 support to a new project, is as good as just editing the Startup.Ath.cs file located in the App_Start folder of your project. You do this by adding your own clientId and clientSecret which you get when you register your application with the different Oauth providers available. The most used of these providers are Facebook, Google and Twitter.

When all of the setup in the Start.Auth.cs is done and finished your login page will look something like the image below depending on how many providers you have added.

Default Buttons
Default Buttons

This will work fine and your users will have the option to be able to login into your application using their social network account of choice. But if you survey a lot of websites on the internet nowadays which support login using Oauth, you will find that their layout looks much nicer and are well presented. My favourite, being a soccer fan, is the English Premier League fantasy premier league website. It has support for logging in using Facebook, Twitter and Google.

Fantasy PL login page
Fantasy PL login page

Compare this to the default layout you get from the Visual Studio project template above. I am no expert in UX and graphic design but I think the fantasy football one looks way much better.

In this article I am going to demonstrate how you can achieve a similar layout by customizing the Login and Register pages in a basic MVC5 web application project. Lets begin.

Create a new Asp.Net MVC5 project

In Visual Studio 2015, create a web application with Individual accounts authentication option selected.

 

New Project
New Project
Individual User Accounts
Individual user accounts selected

Once Visual Studio has finished creating the project, head over to the App_Start folder and find the Start.Auth.cs  file. Edit the file to include the Oauth providers you have registered with. For testing purposes, test123 is enough for the social buttons to show.

 app.UseMicrosoftAccountAuthentication(
clientId: "test123",
clientSecret: "test123");

app.UseTwitterAuthentication(
consumerKey: "test123",
consumerSecret: "test123");

app.UseFacebookAuthentication(
appId: "test123",
appSecret: "test123");

app.UseGoogleAuthentication(new GoogleOAuth2AuthenticationOptions()
{
ClientId = "test123",
ClientSecret = "test123"
});

After adding your appliation OAuth clientIds’ and secrets’, add font-awesome, we will need it for the social icons. In Package Manager Console:
install-package fontawesome
Also do not forget to add the font-awesome.css file to styles bundle. In the App_Start folder, edit the styles bundle to include Font Awesome:

            bundles.Add(new StyleBundle("~/Content/css").Include(
                      "~/Content/bootstrap.css",
                      "~/Content/font-awesome.css",
                      "~/Content/site.css"));

Editing the views

Now that we have added Oauth support to our application it is time modify the layout and make it better. Head over to the Views folder, in the Account child folder find the _ExternalLoginsListPartial.cshtml partial view. Modify the html so that it looks like the following:


@using Microsoft.Owin.Security
@model custom_social_buttons.Models.ExternalLoginListViewModel

<div class="text-center split-div">
    <hr class="split-hr"/>
    <span class="split-span">or sign up with</span>
</div>
@{
    var loginProviders = Context.GetOwinContext().Authentication.GetExternalAuthenticationTypes();
    if (loginProviders.Count() != 0)
    {
        using (Html.BeginForm("ExternalLogin", "Account", new {Model.ReturnUrl}))
        {
            @Html.AntiForgeryToken()
            <div id="socialLoginList">
                <p>
                    @foreach (var p in loginProviders)
                    {
                        if (p.AuthenticationType == "Google")
                        {
                            <div class="col-md-4">
                                <button type="submit" class="btn btn-block btn-google" id="@p.AuthenticationType" name="provider">

                                    <i class="fa fa-google-plus fa-align-left social-icon" style=""></i>Google
                                </button>
                            </div>
                        }
                        else if (p.AuthenticationType == "Facebook")
                        {
                            <div class="col-md-4">
                                <button class="btn-block btn btn-facebook" type="submit" id="@p.AuthenticationType" name="provider">

                                    <i class="fa fa-facebook fa-align-left social-icon"></i>Facebook
                                </button>
                            </div>
                        }
                        else if (p.AuthenticationType == "Twitter")
                        {
                            <div class="col-md-4">
                                <button class="btn-block btn btn-twitter" type="submit" id="@p.AuthenticationType" name="provider">

                                    <i class="fa fa-twitter fa-align-left social-icon"></i>Twitter
                                </button>
                            </div>
                        }
                        else
                        {
                            <div class="col-md-4">
                                <button class="btn-block btn btn-linkedin" type="submit" id="@p.AuthenticationType" name="provider">

                                    <i class="fa fa-life-buoy fa-align-left social-icon"></i>
                                    Sign in with @p.Caption

                                </button>
                            </div>
                        }
                    }
                </p>

            </div>
        }
    }
}

Whilst you are still in the Account folder edit the Login.cshtml view so the html looks like below.

@using custom_social_buttons.Models
@model custom_social_buttons.Models.LoginViewModel
@{
    ViewBag.Title = "Log in";
}
<div class="row">
    <div class="col-md-6 col-md-offset-3">
        <h3 class="text-center">@ViewBag.Title.</h3>
        <section id="loginForm">
            @using (Html.BeginForm("Login", "Account", new {ViewBag.ReturnUrl}, FormMethod.Post, new {@class = "form-horizontal", role = "form"}))
            {
                @Html.AntiForgeryToken()
                <h4 class="text-center">Use a local account to log in.</h4>
                <hr/>
                @Html.ValidationSummary(true, "", new {@class = "text-danger"})
                <div class="form-group">
                    @Html.LabelFor(m => m.Email, new {@class = "control-label"})
                    @Html.TextBoxFor(m => m.Email, new {@class = "form-control"})
                    @Html.ValidationMessageFor(m => m.Email, "", new {@class = "text-danger"})
                </div>
                <div class="form-group">
                    @Html.LabelFor(m => m.Password, new {@class = "control-label"})
                    @Html.PasswordFor(m => m.Password, new {@class = "form-control"})
                    @Html.ValidationMessageFor(m => m.Password, "", new {@class = "text-danger"})
                </div>

                <div class="col-xs-12 col-md-6 text-left remember-me">
                    <label class="checkbox">
                        @Html.CheckBoxFor(m => m.RememberMe)
                        @Html.LabelFor(m => m.RememberMe)
                    </label>
                </div>

                <div class="form-group">
                    <input type="submit" value="Log in" class="btn btn-block btn-primary"/>
                </div>
                <div class="form-group">
                    @Html.ActionLink("Register as a new user", "Register", null, new {@class = "btn  btn-block btn-default"})
                </div>
            }
        </section>
        <section id="socialLoginForm">
            @Html.Partial("_ExternalLoginsListPartial", new ExternalLoginListViewModel {ReturnUrl = ViewBag.ReturnUrl})
        </section>
    </div>
</div>

@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
}

And optionally the Register.cshtml view to the following:

@using custom_social_buttons.Models
@model custom_social_buttons.Models.RegisterViewModel
@{
    ViewBag.Title = "Register";
}

<div class="row">
    <div class="col-md-offset-3 col-md-6">
        <h3 class="text-center">@ViewBag.Title.</h3>
        <section id="registerForm">
            @using (Html.BeginForm("Register", "Account", FormMethod.Post, new { @class = "form-horizontal", role = "form" }))
            {
                @Html.AntiForgeryToken()
                <h4 class="text-center">Create a new account.</h4>
                <hr />
                @Html.ValidationSummary("", new { @class = "text-danger" })
                <div class="form-group">
                    @Html.LabelFor(m => m.Email, new { @class = "control-label" })
                    @Html.TextBoxFor(m => m.Email, new { @class = "form-control" })
                </div>
                <div class="form-group">
                    @Html.LabelFor(m => m.Password, new { @class = "control-label" })
                    @Html.PasswordFor(m => m.Password, new { @class = "form-control" })
                </div>
                <div class="form-group">
                    @Html.LabelFor(m => m.ConfirmPassword, new { @class = "control-label" })
                    @Html.PasswordFor(m => m.ConfirmPassword, new { @class = "form-control" })
                </div>
                <div class="form-group">
                    <input type="submit" class="btn btn-block btn-primary" value="Register" />
                </div>
            }
        </section>
        <section id="socialLoginForm">
            @Html.Partial("_ExternalLoginsListPartial", new ExternalLoginListViewModel { ReturnUrl = ViewBag.ReturnUrl })
        </section>
        
    </div>
</div>

@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
}

The CSS

Finally we would add the css magic that enhance the look and feel of our account pages. Go to Content folder and replace all its contents with the following:


body {
    padding-bottom: 20px;
    padding-top: 50px;
}

/* Set padding to keep content from hitting the edges */

.body-content {
    padding-left: 15px;
    padding-right: 15px;
}

/* Override the default bootstrap behavior where horizontal description lists 
   will truncate terms that are too long to fit in the left column 
*/

.dl-horizontal dt {
    white-space: normal;
}

/*
    Login Form
*/
.btn-facebook {
    background: #305c99;
    color: #fff;
}

.btn-google:hover {
    background: #b22222;
    color: #fff;
}

.btn-facebook:hover {
    background: #2b4db1;
    color: #fff;
}

.btn-twitter:hover {
    background: #007bb6;
    color: #fff;
}

.btn-twitter {
    background: #00cdff;
    color: #fff;
}

.btn-google {
    background: #d24228;
    color: #fff;
}

.btn-linkedin {
    background: #007bb6;
    color: #fff;
}

.btn-block {
    margin: 3px 0 !important;
}

.form-group {
    margin-left: 15px !important;
    margin-right: 15px !important;
}

#loginForm {
    align-items: center;
}

.remember-me {
    margin-bottom: 8px;
}

.split-div {
    font-size: 1.5em;
    margin-bottom: 1em;
    margin-top: 1em;
    padding-bottom: 0.5em;
    padding-top: 0.5em;
    position: relative;
}

.split-span {
    background-color: white;
    display: block;
    left: 40%;
    margin-left: -1.5em;
    position: absolute;
    text-align: center;
    top: -0.3em;
    width: 8em;
}

.split-hr {
    margin-bottom: 0;
    margin-top: 0;
}

.social-icon {
    margin-right: 15px;
}

The Result

If you have followed all of the above steps correctly you should have a layout that almost look like the image below.

The Result
The Result

It is not perfect but you can agree me that this is much better than the default layout

Conclusion

We have taken a basic Asp.Net MVC5 project, changed the login page and register pages and added style enhancements to the layout to achieve a more professional and well presented look and feel.

All the code for the article can be downloaded from Github.

Thank you.

Featured

Insert images into Tiny MCE editor using Asp.Net Web Api 2.2

Introduction

TinyMce is a nice WYSIWYG rich text editor for use in web applications and websites. It is small and very easy to setup. All you need to do is just download the javascript from the Tiny MCE downloads page and include in your project and you are ready to go. Basic setup and usage can be found on www.tinymce.com/documentation.

On one of our projects, which has a blog type of interface where users come in and post content on the platform, we decided to use Tiny-Mce as a rich text editor of choice. Our decision was motivated by the fact that all the text formatting and other options worked as we wanted. But when it comes to image handling , we encountered problems with the built in image plugin. It only works for images that already available on the web with a known location url. It does not allow you to upload your own images by default unless you set the editor to encode them as base64.

I literally spent hours going through the tiny-mce documentation just trying to figure out how to do this but without any success. If there is anyone who has successfully implemented this functionality in a different way, you can share it by posting your solution in the comments section below.

Basically in many of these scenarios, you would want your users to click the insert image button in the toolbar, browse to the image, in the pop up that shows, upload the image to a server and then insert the image into the content being edited. From there onward users can then use the image tools plugin to resize the image, crop the image and so on.

The solution I am putting forward here, is more like a hack than a conventional way of doing things. There are other universally accepted options you can use like Roxy Fileman, Plupload etc to achieve the same goal. But if you do not want the extra stuff (junk) that comes with these others, please continue reading. Below are steps you can follow to implement this functionality using Asp.Net Web Api for the server side uploading of images.

Server side setup

Start by creating a new Web Api project in Visual Studio, call the project tinymce-imageupload with no authentication as shown in the images below.

New Project
New Porject
Web Api Project
Web Api Project

Create web api controller

Once you are finished creating the web api project, add an empty Images web api controller and call it ImagesController.

Add web api controller
Add web api controller

In your images controller, add an action call it UploadImage() with a route attribute to api/images/upload and with the following code as the body. See code listing below.



      [Route("api/images/upload")]
        public async Task<IHttpActionResult> UploadImage()
        {
            var workingFolder = HttpContext.Current.Server.MapPath("~/images/" + DateTime.Today.ToString("ddMMyyyy"));
            if (!Directory.Exists(workingFolder))
            {
                Directory.CreateDirectory(workingFolder);
            }
            // Check if the request contains multipart/form-data.
            if (!Request.Content.IsMimeMultipartContent("form-data"))
            {
                return BadRequest("Unsupported media type");
            }
            try
            {
                var provider = new CustomMultipartFormDataStreamProvider(workingFolder);
                await Request.Content.ReadAsMultipartAsync(provider);

                var file = provider.FileData.First();
                var fileInfo = new FileInfo(file.LocalFileName);

                return Ok(new {location = $"/images/{DateTime.Today.ToString("ddMMyyyy")}/{fileInfo.Name}"});
            }
            catch (Exception ex)
            {
                return BadRequest(ex.GetBaseException().Message);
            }
        }

For the workingFolder, you can choose whatever location you want to use as your destination for the uploaded images. Here I am using a folder named with today’s date inside the images folder. This is merely an example on how you can structure a similar setup.

The first step in the action, checks whether the incoming request is from multi-part form, and if so, the section below it does most of the uploading work asynchronously using a MultipartFormDataStreamProvider, a custom one in this case.

Once the image is uploaded the action returns an Ok result with the location of the uploaded image. We will use this location value in the next section when we configure the client.

Client side setup

Since this is just a demo project, we will use the Index.cshtml view of the home controller for all our setup. Find the Index.cshtml view, in the View/Home folder and replace all the razor html in there with the following.


<h2>TinyMCE Image Upload with Asp.net Web Api</h2>

<div class="edit-container">
    <textarea id="tinymceContent" class="use-tinymce"></textarea>
    <button class="btn btn-success save">Save</button>
</div>

<input name="image" type="file" id="upload" onchange="" style="display: none;">

Basically we have a div with a textarea with id tinymceContent and a save button. We use the tinymceContent textarea as a our target for Tiny-Mce via the selector option. The save button does not do anything, it is just for display. Just below the div, there is a hidden file input control, which we will use for selecting files for upload.

Now to set up the tiny-mce editor, add a reference to the tiny mce javascript file and add the initial setup code below from the tiny-mce website as shown below.


@section scripts
{
    <script src="~/Scripts/tinymce/tinymce.js"></script>
    <script type="text/javascript">
        tinymce.init({
            selector: '#tinymceContent',
       
            plugins: [
                'advlist autolink lists link image charmap print preview anchor',
                'searchreplace visualblocks code fullscreen',
                'insertdatetime media table contextmenu paste code'
            ],
            toolbar:
                'insertfile undo redo | styleselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link image',
            resize: true,
            statusbar: true,
            extended_valid_elements: "span[!class]",
            visualblocks_default_state: true,
            file_picker_callback: function(callback, value, meta) {
                // Provide image and alt text for the image dialog
                if (meta.filetype === 'image') {
                    $('#upload').trigger('click');
                    $('#upload')
                        .on('change',
                            function() {
                                var file = this.files[0];
                                if (file) {
                                    var data = new FormData();
                                    data.append('file', file);
                                    $.ajax({
                                            url: '/api/images/upload',
                                            data: data,
                                            processData: false,
                                            contentType: false,
                                            async: false,
                                            type: 'POST'
                                        })
                                        .done(function(response) {
                                            callback(response.location);
                                        })
                                        .fail(function(jqXhr, textStatus) {
                                            console.log("Request failed: " + jqXhr.responseText + " --- " + textStatus);
                                            alert("Request failed: " + jqXhr.responseText + " --- " + textStatus);
                                        });
                                }
                            });
                }
            },
            file_picker_types: 'image',
            image_title: true
        });
    </script>
}

In this scripts section we add a reference to the Tiny-Mce library and below it we add the code to wire tiny-mce with the html. We provides basic options to the tiny-mce init function to get it up and running. All these options are standard except for the file_picker_callback callback function. This is where we do our magic.

We check if the incoming file type is an image and then we trigger a click event on our file input using JQuery. When the user selects a file, we upload the file to the server, via ajax, using a JQuery post method. Once the upload is finished and returns a response, we pass the response’s location value to our callback function. The callback function then sets value of the opened modal, source text box, to be this returned location value.

When the user clicks Ok on the opened modal/popup, the Tiny-Mce infrastructure will take over and then inserts the image into our content. This finishes the setup process and you can test it by inserting your own images into the editor and see if it works. If not download this demo project from Github and test again to make sure you understand the logic.

You can view the html generated by the editor, by going to the Tools toolbar button click it and then click view source.

Conclusion

In this article we have explored another way you can insert images into a tiny-mce WYSIWYG editor using Asp.net Web Api on the server side. You can also implement same type of functionality for other file types like videos (mp4, mpg, etc) and documents (pdfs, docs, etc). Code for the project can be found on Github