INDIVIRTUAL - TECHNISCH PARTNER IN DIGITALE DIENSTVERLENING

Utilize LinkedIn for filling in form fields in EPiServer

February 26, 2019

Utilize LinkedIn for filling in form fields in EPiServer

Filling in a from can be simplified for your users! Basic contact information, such as name and email address, can be filled in automatically by utilizing the LinkedIn API.

Implementing the code

First, make sure that you have installed the following NuGet packages:

  • EPiServer.Forms
  • Sparkle.LinkedInNET

Also, make sure that you have a reference to the Microsoft.Net.Http namespace.

Now, let’s add the code.

    public class LinkedInAuthorizationController : Controller
    {
        private const string LinkedInApiKey = "Client ID";
        private const string LinkedInApiSecretKey = "Client Secret";

        private readonly UrlHelper _urlHelper;
        private readonly LinkedInApi _api;
        private readonly string _redirectUrl;
        
        public LinkedInAuthorizationController(UrlHelper urlHelper, ISiteDefinitionRepository siteDefinitionRepository)
        {
            _urlHelper = urlHelper;
            _api = new LinkedInApi(new LinkedInApiConfiguration(LinkedInApiKey, LinkedInApiSecretKey));
            _redirectUrl = $"{siteDefinitionRepository.List().First(x => x.StartPage == ContentReference.StartPage).SiteUrl}LinkedInAuthorization";
        }

        public ActionResult Index(int pageId = 0)
        {
            if (pageId != 0)
            {
                Response.Cookies.Add(new HttpCookie("CurrentPage", _urlHelper.ContentUrl(new ContentReference(pageId))));

                return Redirect(_api.OAuth2.GetAuthorizationUrl(
                                    AuthorizationScope.ReadBasicProfile | AuthorizationScope.ReadEmailAddress,
                                    Guid.NewGuid().ToString(),
                                    _redirectUrl).OriginalString);
            }

            if (Request.QueryString["code"] == null)
            {
                return Redirect(_urlHelper.ContentUrl(ContentReference.StartPage));
            }

            var currentpage = Request.Cookies["CurrentPage"]?.Value;
            if (string.IsNullOrEmpty(currentpage))
            {
                return Redirect(_urlHelper.ContentUrl(ContentReference.StartPage));
            }

            Response.Cookies.Add(new HttpCookie("CurrentPage", null)
            {
                Expires = DateTime.Now.AddDays(-1)
            });

            var userToken = _api.OAuth2.GetAccessToken(Request.QueryString["code"], _redirectUrl);

            var user = new UserAuthorization(userToken.AccessToken);

            var fields = FieldSelector.For<Person>()
                .WithFirstName()
                .WithLastName()
                .WithEmailAddress()
                .WithPositionsCompany()
                .WithPositionsTitle();

            var profile = _api.Profiles.GetMyProfile(user, new[] { "en-us" }, fields);

            Response.Cookies.Add(new HttpCookie("FirstName", profile?.Firstname));
            Response.Cookies.Add(new HttpCookie("LastName", profile?.Lastname));
            Response.Cookies.Add(new HttpCookie("Email", profile?.EmailAddress));
            Response.Cookies.Add(new HttpCookie("CompanyName", profile?.Positions?.Position?.FirstOrDefault()?.Company?.Name));
            Response.Cookies.Add(new HttpCookie("JobTitle", profile?.Positions?.Position?.FirstOrDefault()?.Title));

            return Redirect(currentpage);
        }
    }

A custom Form Element Block is used for mapping the values from LinkedIn to our textboxes.

    [ContentType(DisplayName = "LinkedIn Form Block", GUID = ".....", Description = "")]
    public class LinkedInFormElementBlock : ElementBlockBase
    {
        [Display(
            Name = "First name field",
            GroupName = SystemTabNames.Content,
            Order = 10)]
        [CultureSpecific]
        [SelectOne(SelectionFactoryType = typeof(LinkedInOptionsSelectionFactory))]
        public virtual string FirstNameField { get; set; }

        [Display(
            Name = "Last name field",
            GroupName = SystemTabNames.Content,
            Order = 20)]
        [CultureSpecific]
        [SelectOne(SelectionFactoryType = typeof(LinkedInOptionsSelectionFactory))]
        public virtual string LastNameField { get; set; }

        [Display(
            Name = "Email field",
            GroupName = SystemTabNames.Content,
            Order = 30)]
        [CultureSpecific]
        [SelectOne(SelectionFactoryType = typeof(LinkedInOptionsSelectionFactory))]
        public virtual string EmailField { get; set; }

        [Display(
            Name = "Company name field",
            GroupName = SystemTabNames.Content,
            Order = 40)]
        [CultureSpecific]
        [SelectOne(SelectionFactoryType = typeof(LinkedInOptionsSelectionFactory))]
        public virtual string CompanyNameField { get; set; }

        [Display(
            Name = "Job title field",
            GroupName = SystemTabNames.Content,
            Order = 50)]
        [CultureSpecific]
        [SelectOne(SelectionFactoryType = typeof(LinkedInOptionsSelectionFactory))]
        public virtual string JobTitleField { get; set; }

        [ScaffoldColumn(false)]
        public new virtual string Label { get; set; }

        [ScaffoldColumn(false)]
        public new virtual string Description { get; set; }
    }

The SelectionFactory is used for mapping the EPiServer Form Elements to the data that is received from the LinkedIn API.

    public class LinkedInOptionsSelectionFactory : ISelectionFactory
    {
        public IEnumerable<ISelectItem> GetSelections(ExtendedMetadata metadata)
        {
            var contentLoader = ServiceLocator.Current.GetInstance<IContentLoader>();
            var formContainer = ((LinkedInFormElementBlock)metadata.FindOwnerContent()).FindOwnerForm();
            var items = new List<ISelectItem>
            {
                new SelectItem { Text = "Empty", Value = "" }
            };

            if (formContainer?.ElementsArea?.FilteredItems != null && formContainer.ElementsArea.FilteredItems.Any())
            {
                foreach (var item in formContainer.ElementsArea.FilteredItems)
                {
                    var elementBlock = contentLoader.Get<ElementBlockBase>(item.ContentLink);
                    if (!(elementBlock is LinkedInFormElementBlock))
                    {
                        items.Add(new SelectItem
                        {
                            Text = ((IContent)elementBlock).Name,
                            Value = elementBlock.FormElement.Guid
                        });
                    }
                }
            }

            return items;
        }
    }
    public class LinkedInFormElementBlockController : BlockController<LinkedInFormElementBlock>
    {
        private readonly IPageRouteHelper _pageRouteHelper;

        public LinkedInFormElementBlockController(IPageRouteHelper pageRouteHelper)
        {
            _pageRouteHelper = pageRouteHelper;
        }

        public override ActionResult Index(LinkedInFormElementBlock currentBlock)
        {
            var viewModel = new LinkedInFormElementBlockViewModel
            {
                CurrentBlock = currentBlock,
                PageId = _pageRouteHelper.Page.ContentLink.ID,
                FirstName = GetCookieValue("FirstName"),
                LastName = GetCookieValue("LastName"),
                Email = GetCookieValue("Email"),
                CompanyName = GetCookieValue("CompanyName"),
                JobTitle = GetCookieValue("JobTitle")
            };

            return PartialView("~/Views/Shared/ElementBlocks/LinkedInFormElementBlock.cshtml", viewModel);
        }

        private string GetCookieValue(string name)
        {
            var cookie = Request.Cookies[name];
            if (cookie == null)
            {
                return string.Empty;
            }

            var value = cookie.Value;
            Response.Cookies.Add(new HttpCookie(name, null)
            {
                Expires = DateTime.Now.AddDays(-1)
            });

            return value;
        }
    }
    public class LinkedInFormElementBlockViewModel
    {
        public LinkedInFormElementBlock CurrentBlock { get; set; }
        public int PageId { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string Email { get; set; }
        public string CompanyName { get; set; }
        public string JobTitle { get; set; }
    }

Finally, the LinkedInFormElementBlock.cshtml needs to be added to the /Views/Shared/ElementBlocks/ directory.

@model LinkedInFormElementBlockViewModel

<a id="linkedinAuthorization">Fill in with LinkedIn</a>

<script>
    document.getElementById("linkedinAuthorization").addEventListener("click", function() {
        window.location.href = '/LinkedInAuthorization/@Model.PageId';
    });

    document.addEventListener("DOMContentLoaded", function() {
    @if (!string.IsNullOrEmpty(Model.CurrentBlock.FirstNameField) && !string.IsNullOrWhiteSpace(Model.FirstName))
    {
    <text>
        document.getElementById("@Model.CurrentBlock.FirstNameField").value = '@Model.FirstName';
    </text>
    }

    @if (!string.IsNullOrEmpty(Model.CurrentBlock.LastNameField) && !string.IsNullOrWhiteSpace(Model.LastName))
    {
    <text>
        document.getElementById("@Model.CurrentBlock.LastNameField").value = '@Model.LastName';
    </text>
    }

    @if (!string.IsNullOrEmpty(Model.CurrentBlock.EmailField) && !string.IsNullOrWhiteSpace(Model.Email))
    {
    <text>
        document.getElementById("@Model.CurrentBlock.EmailField").value = '@Model.Email';
    </text>
    }

    @if (!string.IsNullOrEmpty(Model.CurrentBlock.CompanyNameField) && !string.IsNullOrWhiteSpace(Model.CompanyName))
    {
    <text>
        document.getElementById("@Model.CurrentBlock.CompanyNameField").value = '@Model.CompanyName';
    </text>
    }

    @if (!string.IsNullOrEmpty(Model.CurrentBlock.JobTitleField) && !string.IsNullOrWhiteSpace(Model.JobTitle))
    {
    <text>
        document.getElementById("@Model.CurrentBlock.JobTitleField").value = '@Model.JobTitle';
    </text>
    }
});
</script>

Create the API key and secret

Create an app on the LinkedIn Developers site: https://www.linkedin.com/developers/apps.
Select the newly created app and switch to the “Auth” tab.
Edit the “OAuth 2.0 settings” and add the following URL: {your hostname}/LinkedInAuthorization.
This is the URL that LinkedIn will redirect to, after the user grants his/her permission.

Copy the “Client ID” and “Client Secret” to the “LinkedInApiKey” and “LinkedInApiSecretKey” fields in the LinkedInAuthorizationController class.

Make sure that you add proper routing to redirect the “/LinkedInAuthorization/{pageId}” URL to the LinkedInAuthorizationController’s Index method.

Configuration

I have created a form that contains the following form elements. The first element is the custom “LinkedInFormElementBlock”.

Inside the custom “LinkedInFormElementBlock” form element, we need to map the data we get from the LinkedIn API to our own EPiServer Form Elements. The SelectionFactory provides us with the names of the other form elements inside the Form Container.

The result

After adding the code and applying the necessary configuration, you will get the following result:

Oscar de Vaal

Oscar de Vaal

.NET Developer