DotNetCurry Logo

Build a Blogging Site in ASP.NET Core with Clean Reusable Front-end code

Posted by: Daniel Jimenez Garcia , on 11/25/2016, in Category ASP.NET
Views: 26650
Abstract: This article demonstrates how to keep ASP.NET front-end code clean, maintainable and reusable. This article builds a simple blogging site as an example.

When it came to organizing your frontend code in ASP.NET MVC 5, you had several tools like HTML Helpers, Partial Views and Child Actions. ASP.NET Core introduces Tag Helpers and replaces Child Actions with View Components.

While techniques like partial views are still relevant, new features like Tag Helpers and View Components in ASP.NET Core makes it even easier to achieve these objectives.

This article is published from the DNC Magazine for Developers and Architects. Download or Subscribe to this Free magazine [PDF] to access all previous, current and upcoming editions.

ASP.NET Core – Simple Blogging Site Example

In this article, we will build a simple blogging site that integrates with Google accounts, and demonstrates how you can write readable, reusable and maintainable frontend code using these new features. I hope you enjoy it!

Setting up a new Project with Google Authentication

Create the ASP.NET Core Web Project

Create a new ASP.NET Core Web Application project in Visual Studio 2015. Select the Web Application template, and finally select Individual User Accounts as the authentication method.

Once created, compile and launch the website and take note of your application url, for example http://localhost:64917. We will need this url later when configuring Google credentials. (You could also check the launchSettings.json file inside the Properties folder)

Now open a command line at the project’s root folder, and execute the following command:

>dotnet ef database update

This command initializes the default database and applies the initial migrations, setting up all the tables required by the ASP.NET Core Identity framework.

Configuring the credentials

Before we even begin writing any code, we need to configure the credentials in the Google Developer Console. These are the credentials that our ASP.NET Core application will use when trying to get access to Google APIs.

To begin with, sign in with your google account in the Google Developer Console site https://console.developers.google.com. Then create a new project, give it a name and accept the terms:

aspnet-core-google-project

Figure 1: New project in the Google Developer Console

Once the project has been created, we need to enable the APIs that our web application will require. Navigate to the API Manager using the hamburger menu, then select the Google+ link found within the Library of Google APIs, and finally click the enable button.

Next we need to setup the client credentials that will be used by our application to access these Google APIs. Go to the Credentials section using the left side menu. Click on the Create credentials button and select OAuth client ID:

new-google-credentials

Figure 2: Beginning the creation of the OAuth credentials

Now configure the consent screen, the one shown to users asking them to grant our application access to their private data. At the bare minimum, you need to enter the name of your application.

With the consent screen configured, you can now select the application type of the credentials. Select the Web Application type and it will immediately ask you for the authorised origin urls and the redirect url:

configuring-credentials

Figure 3: Configuring the credentials for our web application

Remember when I asked to take note of your application url? This is where you need it. Enter the base url as an authorised origin, for example http://localhost:64917. This is the origin from where these credentials will be used.

Now enter your base url followed by the path signin-google as the redirect uri, for example http://localhost:64917/signin-google. This is the url where Google will redirect users after they have logged in with their accounts. That particular url being the default callback of the Google Authentication package used in our ASP.NET Core application. (You could use a different redirect uri as long as you also configure it in your app Startup options)

Once you click Create, you will be presented with your Client ID and Client Secret. They can be viewed any time by clicking edit in your credentials:

credentials-secret

Figure 4: The Client ID and secret of the credentials

The Client ID and secret are quite important, as the Google Authentication middleware of our web application needs to use them.

This completes the setup in Google and we can go back to Visual Studio.

Enabling Google Authentication in the ASP.NET Core project

First let’s add the middleware required for Google Authentication. In your project.json, add the following dependency:

"Microsoft.AspNetCore.Authentication.Google": "1.0.0"

Make sure you save the file so Visual Studio restores the project dependencies, or manually runs dotnet restore in the command line.

Then update the Configure method of your Startup class, adding the Google Authentication middleware. The default project template contains a comment stating: Add external authentication middleware below. That’s because the order in which the middleware is registered is critical, and this type of middleware should be registered after UseIdentity and before UseMVC.

app.UseGoogleAuthentication(new GoogleOptions
{
    ClientId = "YourClientId",
    ClientSecret = "YourClientSecret",
    SaveTokens = true        
});

You could just copy and paste your Client ID and secret. However I strongly suggest you to use the Secret Manager tool and to keep these secrets outside your source code, as explained here.

That’s it, start your project and navigate to the Log in page. You will see a section for external providers with Google as the only option listed:

login-options

Figure 5: Log in now provides the option of using your Google account

If you click on that button, you will be taken to a page on Google’s server where you can authenticate with your account and give consent to your web application (This is the consent screen configured earlier in the Google Developer Console):

consent-screen

Figure 6: the Google consent screen

Once you click the Allow button, you will be sent back to the application where you will now be authenticated. During the first time, you also need to associate your Google account with a local account.

Storing the user profile picture and name

Our web application will retrieve the user name and profile picture from the user’s Google account, and store them in the ApplicationUser class.

To begin with, extend ApplicationUser with these new properties:

public class ApplicationUser : IdentityUser
{
    public string FullName { get; set; }
    public string PictureUrl { get; set; }
}

Then add a new migration and update the database by running these commands:

>dotnet ef migrations add user-profile
>dotnet ef database update

The user’s full name is already available in the Identity Claims of the ExternalLoginInfo object after a successful Google authentication. (The Google middleware adds the email address and full name amongst others as the default scopes. You can request additional properties by adding them to the Scopes collection of the Google Options object, where the list of available scopes for the Google APIs is located at https://developers.google.com/identity/protocols/googlescopes)

We will however need to write some code to retrieve the profile picture. Remember the setting SaveTokens=true within the GoogleOptions in your Startup class? This will allow us to grab the access token and use it to retrieve a json with the basic profile information of the user.

This json contains a picture property with the url of the user’s profile picture, and is retrieved from the following url: https://www.googleapis.com/oauth2/v2/userinfo?access_token=theAccessToken

Now that we know what to do, we can for example create a new service GooglePictureLocator with a method that takes an ExternalLoginInfo, and returns the profile picture:

public async Task< string > GetProfilePictureAsync(ExternalLoginInfo info)
{
    var token = info.AuthenticationTokens
                          .SingleOrDefault(t => t.Name == "access_token");
    var apiRequestUri = 
          new Uri("https://www.googleapis.com/oauth2/v2/userinfo?access_token=" +
                  token.Value);
    using (var client = new HttpClient())
    {
        var stringResponse = await client.GetStringAsync(apiRequestUri);
        dynamic profile = JsonConvert.DeserializeObject(stringResponse);
        return profile.picture;
    }
}

Remember to register the service within the DI container in the Startup class. This way, the code that needs to use the service just needs a constructor parameter of the service type, so an instance gets injected.

This utility service will be used when storing the user’s full name and profile picture after:

  • A new user successfully logs in with his Google account and associates it with a local account. This happens in the ExternalLoginConfirmation method of the AccountController.
  • An existing user with a local account adds an external Google account. This happens in the LinkLoginCallback method of the ManageController.

In the ExternalLoginConfirmation, populate the FullName and PictureUrl properties before the user is created:

var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
user.FullName = info.Principal.FindFirstValue(ClaimTypes.Name);              
user.PictureUrl = await _pictureLocator.GetProfilePictureAsync(info);
var result = await _userManager.CreateAsync(user);

Do something similar in LinkLoginCallback, updating the user profile right before the redirect at the end of the method. (Save the updated properties to the db by calling

_userManager.UpdateAsync(user))

Make sure you register a new account once the changes are in place to retrieve the profile information from Google, so your account profile contains the retrieved information.

Show profile pictures in the navigation bar

Let’s update the main navigation bar to display the user’s name and picture:

nav-google-accounts

Figure 7: Updated navigation bar

While doing so, we will create a Tag Helper that renders the HTML of the profile picture. Tag Helpers let you encapsulate somewhat complex HTML rendering logic behind specific tags recognized by Razor that can be used from any of your views.

Create a Tag Helper for rendering profile pictures

Start by adding a new TagHelpers folder to your project, and a new class ProfilePictureTagHelper inheriting from the TagHelper abstract class.

Any class implementing ITagHelper (like any class that inherits from TagHelper) will be recognized as a Tag Helper, regardless of how they are named, and where they are located. The default convention for matching tags with classes is based on the name, so ProfilePictureTagHelper matches the tag:

 <profile-picture> </profile-picture>

When the view is rendered by Razor, the tag will be replaced with the result of the Process method of the ProfilePictureTagHelper class. As usual you can customize the convention; there are attributes for specifying the target tag element, required attributes and more.

The only additional requirement is to inform Razor about any assembly that contains Tag Helpers. Update _ViewImports.cshtml adding the following line (Where you add the assembly name, not the namespace!):

@addTagHelper *, BlogPlayground

Continue by adding the following code to the Tag Helper:

public ApplicationUser Profile { get; set; }
public int? SizePx { get; set; }

public override void Process(TagHelperContext context, TagHelperOutput output)
{
    //Render nothing if there is no profile or profile doesn't have a picture url
    if (this.Profile == null || String.IsNullOrWhiteSpace(this.Profile.PictureUrl))
    {
        output.SuppressOutput();
        return;
    }

    //To be completed
}

This means our tag helper accepts 2 parameters and renders nothing when there is no profile or picture. (The later happening with users that use local accounts instead of google accounts)

We will complete it later. First, switch to _LoginPartial.cshtml and where the user is authenticated, retrieve its profile and use the profile picture tag helper:

@if (SignInManager.IsSignedIn(User))
{         
    var userProfile = await UserManager.GetUserAsync(User);    
    <form …>
        <ul class="nav navbar-nav pull-right">
            …            
            <li>
                <profile-picture profile="@userProfile" size-px="40" />
            </li>
        </ul>
    </form>
}

We just need to complete the Process method of the Tag Helper so it renders a span with an inner element, like:

<span class="profile-picture">
    <img src="https://lh6.googleusercontent.com/...">
</span>

This is achieved with the following code:

output.TagName = "span";
output.TagMode = TagMode.StartTagAndEndTag;
output.Attributes.SetAttribute("class", "profile-picture");

var img = new TagBuilder("img");
img.Attributes.Add("src", this.GetPictureUrl());
output.Content.SetHtmlContent(img);

· The GetPictureUrl function will not just provide the url. We need to control the size of the picture, so our site looks as expected. Luckily for us the Google picture accepts a query string like ?sz=40 that specifies the size in pixels:

private string GetPictureUrl()
{
    var imgUriBuilder = new UriBuilder(this.Profile.PictureUrl);
    if (this.SizePx.HasValue)
    {
        var query = QueryString.FromUriComponent(imgUriBuilder.Query);
        query = query.Add("sz", this.SizePx.Value.ToString());
        imgUriBuilder.Query = query.ToString();
    }
    return imgUriBuilder.Uri.ToString();
} 

· You can also add the user’s name as the title and alt attributes of the img element.

Finally, add some css rules to site.css so it looks similar to Figure 7:

.navbar .profile-picture {
    line-height: 50px;
}
.profile-picture img{
    vertical-align: middle;
    border-radius: 50%;
}

Provide a default image for local accounts

Right now if someone is using local accounts, the navigation bar will show no profile picture as we only have that information for users authenticating with Google accounts:

nav-local-accounts

Figure 8: Local accounts have no profile picture

We can easily update the ProfilePictureTagHelper so it also renders a default placeholder for local accounts. Just google for a suitable default profile picture, and save it to the wwwroot/images folder.

The full url for this image can be generated using an IUrlHelper in the tag helper as Content("~/images/placeholder.png"), but we need to get access to an IUrlHelper instance first!

Add a constructor to the TagHelper which takes instances of IUrlHelperFactory and IActionContextAccessor.(You will also need to register IActionContextAccessor within the DI services in the Startup class)

Then create the following properties:

private bool IsDefaultPicture => 
this.Profile == null 
|| String.IsNullOrWhiteSpace(this.Profile.PictureUrl);
private IUrlHelper UrlHelper =>
      this.urlHelperFactory.GetUrlHelper(this.actionAccessor.ActionContext);

Update the GetPictureUrl method so it returns the placeholder when required:

if (this.IsDefaultPicture)
{
    return this.UrlHelper.Content("~/images/placeholder.png");
}

Then update the Process method so it doesn’t suppress the output anymore when there is no PictureUrl. Instead, let it proceed and just update the code so it forces the width and height in pixels for the default profile picture. (This is the simplest solution to ensure the single default placeholder picture has the dimensions required)

if (this.IsDefaultPicture && this.SizePx.HasValue) {
    img.Attributes.Add("style",
      $"height:{this.SizePx.Value}px;width:{this.SizePx.Value}px");
}

That’s it, now local accounts will show the default placeholder:

nav-default-placeholder

Figure 9: Placeholder profile picture for local accounts

Building the blogging site

The objective of the article is to build a simple blogging site, so we need pages where users create articles, for display. Let’s just use scaffolding to quickly generate the required controller and views.

I will stay focused on the frontend code, if you have any issues with the rest of the code please check the article code in GitHub.

Create new Articles controller and views

Create a new Article class inside the Models folder:

public class Article
{
    public int ArticleId { get; set; }
    [Required]
    public string Title { get; set; }
    [Required]
    public string Abstract { get; set; }
    [Required]
    public string Contents { get; set; }
    public DateTime CreatedDate { get; set; }
    public string AuthorId { get; set; }
    public virtual ApplicationUser Author { get; set; }
}

This is a POCO model that Entity Framework will be able to store and whose relationship with the ApplicationUser model will be automatically managed. (Read more about relationships here.)

Since it has a relationship with the ApplicationUser model, it needs to be added as a new DbSet in the default ApplicationDbContext class:

public DbSet<Article> Article { get; set; }

At this point, you probably want to add a new migration, so go ahead and do so. Once you are ready, we will scaffold everything we need. (If you are already using the secret manager for storing your Google ClientId and Secret, check this issue in github)

Right click the Controllers folder and select Add > Controller. Then select MVC Controller with views, using Entity Framework. Select the Article class as the model and the existing ApplicationDbContext as your data context, so Articles and ApplicationUsers live in the same context.

aspnet-core-scaffolding

Figure 10: scaffolding the articles

Once scaffolding is completed, let’s apply some small changes:

· I will ignore the edit view, feel free to remove it along with corresponding controller actions.

· Add the [Authorize] attribute to the Create and Delete actions, so only logged in users can add/remove them.

· Leave just the Title, Abstract and Content in the Create view. Use the textarea tag helper for both the Abstract and Content, for example:

< textarea asp-for="Abstract" class="form-control" rows="5">

· Set the AuthorId and CreatedDate in the Create method of the controller. (You will need to inject an UserManager in the controller constructor)

article.AuthorId = _userManager.GetUserId(this.User);
article.CreatedDate = DateTime.Now;

· You will see that scaffolding already created a query for the Index page that includes the Author information within a single query. However you will manually need to do the same in the Details action, so it also loads the Author within the same single database query:

var article = await _context.Article
            .Include(a => a.Author)
            .SingleOrDefaultAsync(m => m.ArticleId == id);

You should now have a functional site where users can write articles which are then displayed inside the index view.

Polish the views using partials and tag helpers

We will update the article views so that they show the author details and look nicer (within the boundaries of my limited design skills) while its code stays clean. As Bootstrap is included by default, I will base my design on it.

Let’s start with the Index view. Change it so we now have a title followed by a container div. Inside the container, create a main left side column and a smaller right side column.

  • The right side contains a link for creating a new article, styled as a main button.
  • The left main side is where articles will be rendered. Use a list with an item for every article. Each item will be also divided on the left and side columns.
  • Display the author profile picture (using our tag helper), name and creation date on the smaller left side column.
  • Finally display the title and abstract on the right side column. Don’t forget to render the title as a link for the details page of that article.

Once done with the html and css changes, the UI should look similar to the following screenshot:

article-index

Figure 11: Updated articles index view

Continue by extracting the razor code rendering each article into an old fashioned partial view. We will later reuse it on other views, keeping their code clean and simple. This partial view should be close to mine, depending on how closely you have followed my design:

@model BlogPlayground.Models.Article
<div class="row article-summary">
    <div class="col-md-4">
        <profile-picture profile="@Model.Author" size-px="40" />
        <p class="text-center author-name">
            @(Model.Author.FullName ?? Model.Author.UserName)
        </p>
        <p class="text-center">
            @Model.CreatedDate.ToString("dd MMM yyyy")
        </p>
    </div>
    <div class="col-md-8">
        <h4>
            <a asp-action="Details" asp-route-id="@Model.ArticleId">
                @Model.Title
            </a>
        </h4>
        <blockquote>
            <p>@Model.Abstract</p>
        </blockquote>
    </div>
</div>

With the partial created, the Index view should now be quite simple and clean:

@model IEnumerable<BlogPlayground.Models.Article>
<h3>This is what we have been writing...</h3>
<div class="container">
    <ul class="col-md-8 list-unstyled article-list">
    @foreach (var article in Model)
    {
        <li>
            @Html.Partial("_ArticleSummary", article)
        </li>
    }
    </ul>

    <div class="col-md-4">
        @*create new article button*@
    </div>
</div>

Now it’s the turn of the Details view. Follow the same approach with a container divided into a main left side showing the article details, and a smaller right side showing the links.

· In the main left side, render the _ArticleSummary partial followed by the article contents.

· In the right side, use a Bootstrap button group and provide links to write a new article, delete the current one or go back to the list.

@model BlogPlayground.Models.Article
<div class="article-page container">
    <div class="col-md-8 article-details">
        @Html.Partial("_ArticleSummary", Model)
        <hr />
        <p>@Model.Contents</p>
    </div>

    <div class="col-md-4">
        @* button group *@
    </div>
</div>

Once finished, the details page will look similar to this screenshot:

article-details

Figure 12: Updated article details page

As you can see, partials are still useful for keeping views clean and reusing code!

Render markdown contents

Rendering the article contents as an unformatted block of text in a blogging site is not very useful.

An easy way of improving this situation would be using markdown.

· Markdown is a simple markup language that can be included in plain text and is later formatted into HTML by tools. Stack Overflow and the readme.md files in git repos are just 2 very common examples where it is used.

The article contents will continue to be entered and stored as plain text, which can contain markup syntax. We will then create a new Tag Helper that will render any string as the markdown processed HTML.

Add one of the several nuget packages that can convert a string with markdown syntax into HTML, for example:

"Markdown": "2.2.0",

Add a new MarkdownTagHelper class to the TagHelpers folder. This one will be quite simple; it will accept a source string and render the markdown processed version of that string:

public class MarkdownTagHelper : TagHelper
{
    public string Source { get; set; }
    public override void Process(TagHelperContext context, TagHelperOutput output)
    {
        output.TagName = "div";
        output.TagMode = TagMode.StartTagAndEndTag;
        output.Attributes.SetAttribute("class", "markdown-contents");
        var markdown = new Markdown();           
        output.Content.SetHtmlContent(markdown.Transform(this.Source));
    }        
}

Notice the usage of Content.SetHtmlContent when adding the processed string to the output. This is required as otherwise the HTML tags in the transformed string would be HTML escaped!

Once the Tag Helper is ready, use it in the Details view for rendering the article contents:

<div class="col-md-8 article-details">
    @Html.Partial("_ArticleSummary", Model)
    <hr />
    <markdown source="@Model.Contents"></markdown>
</div>

Now the Details page looks way more interesting than it did:

markdown-article-details

Figure 13: article details with processed markdown

Adding a Latest Articles View Component

In ASP.NET Core, View Components have replaced Child Actions as the way of encapsulating components into reusable pieces of code.

  • The component logic won’t be part of a controller anymore; instead it will be placed inside the View Component class. The views are also placed in component specific folders. This encapsulates the logic and view in a better way than the Child Action approach with partial views and controller actions.
  • View Components are also lighter. They are invoked directly and parameters are directly passed into them, without involving the same pipeline than controller actions. (Like filters or model binding)
  • They are not accessible by default over HTML. (If you need to, it’s possible to write a controller action that returns a View Component)
  • They support async code, which was not possible with Child Actions.

A View Component is the right approach for building a reusable widget that renders the latest articles in our blogging site. As you will see, it will also be straightforward to build on top of our current code.

Add a new ViewComponents folder to the project and create a new class LatestArticlesViewComponent.

· Any class deriving from ViewComponent, decorated with [ViewComponent] or whose name ends in ViewComponent will be recognized by ASP.NET Core as a view component.

Just inherit from the ViewComponent abstract class and implement the InvokeAsync method. This method will receive the number of articles to retrieve, fetch them from the database, and finally render them:

public class LatestArticlesViewComponent: ViewComponent
{
    private readonly ApplicationDbContext _context;
    public LastArticlesViewComponent(ApplicationDbContext context)
    {
        _context = context;
    }
    public async Task<IViewComponentResult> InvokeAsync(int howMany = 3)
    {
        var articles = await _context.Article
                        .OrderByDescending(a => a.CreatedDate)
                        .Take(howMany)
                        .ToListAsync();
        return View(articles);
    }
}

The default convention expects the view located in the folder Views\Shared\Components\LatestArticles. Since we are not specifying any view name, the convention is that the view must be named Default.cshtml.

The razor code inside that view will simply render a list with the latest articles. Again we use the _ArticleSummary partial in order to render each item:

@model BlogPlayground.Models.Article
<div class="last-articles">
    <h4>Latest articles:</h4>
    <ul class="list-unstyled last-articles-list">
    @foreach (var article in Model)
    {
        <li>
            @Html.Partial("_ArticleSummary", article)
        </li>
    }
    </ul>
</div>

Once the View Component is finished, we can include it in the sidebar of both the Index and Details views. For example in the Index view, right below the Write a new Article button:

<div class="row">
    @await Component.InvokeAsync("LatestArticles", new { howMany = 2 })
</div>

article-index-widget

Figure 14: Index page including latest articles widget

article-details-latest-articles-widget

Figure 15: Details page including latest articles widget

Conclusion

We have seen how we can build non-trivial sites while keeping our frontend code clean, maintainable and reusable. Previous techniques like partial views are still relevant, but the addition of Tag Helpers and View Components in ASP.NET Core makes it even easier to achieve those objectives.

  • Tag Helpers are great for encapsulating blocks of HTML code that you might need in several places and for rendering concerns like img attributes or markdown strings. The profile picture and markdown Tag Helpers are good examples.
  • View Components let you encapsulate together business logic and views in a powerful and simple way. The latest articles widget is the perfect example of what can be easily achieved.

The site built in this article wraps everything into a simple but functional blogging site. Although interesting, bear in mind this is only meant as an example. Due to time and space constraints, I have happily overlooked many issues you would face in a real project like searching, pagination, different social providers like Facebook or Twitter, profile management and many more!

Download the entire source code of this article (Github)

Was this article worth reading? Share it with fellow developers too. Thanks!
Share on LinkedIn
Share on Google+
Further Reading - Articles You May Like!
Author
Daniel Jimenez Garcia is a passionate software developer with 10+ years of experience. He started as a Microsoft developer and learned to love C# in general and ASP.NET MVC in particular. In the latter half of his career he worked on a broader set of technologies and platforms while these days he is particularly interested in .Net Core and Node.js. He is always looking for better practices and can be seen answering questions on Stack Overflow.


Page copy protected against web site content infringement 	by Copyscape




Feedback - Leave us some adulation, criticism and everything in between!