C# Corner Author Posts Analytics With Angular 8

Introduction

 
C# Corner provides RSS feeds (maximum 100 posts only) with various data about their authors. We will use one of these RSS feeds to fetch the author's post details and scrape data such as article category, number of views, and number of likes for each post and save to a SQL Server database. In our application, user can give an author id and fetch the post data for that author and save to the database. Once we populate the data, we can fetch the data with different LINQ queries using Entity Framework and show the data in Angular application as Chart or some other formats.
 

Motivation for this app

 
I have published more than 70 articles so far on C# Corner and just wanted to analyze my posts' reach to the audience. I have searched various ways to fetch data from C# Corner site and luckily got RSS feeds. But RSS feeds do not contain the data like the number of views or number of likes for a post. Again, I was searching for a way to get this kind of data and finally, decided to scrape each post and get individual data. Though it is a time-consuming process, I have achieved my goal to get the desired data from the C# Corner site. I believe this will be useful to other authors also. Hence, I am sharing the source code of the app attached to this article.
 

Create a Web API project in Visual Studio

 
We need to create a Web API project for populating the data to SQL server database and fetch various data for our Angular application.
 
Firstly, let us create an “ArticleMatrices” table in SQL Server database using the below SQL script.
  1. CREATE TABLE [dbo].[ArticleMatrices](  
  2.     [Id] [int] IDENTITY(1,1) NOT NULL,  
  3.     [AuthorId] [nvarchar](50) NULL,  
  4.     [Author] [nvarchar](50) NULL,  
  5.     [Link] [nvarchar](250) NULL,  
  6.     [Title] [nvarchar](250) NULL,  
  7.     [Type] [nvarchar](50) NULL,  
  8.     [Category] [nvarchar](50) NULL,  
  9.     [Views] [nvarchar](50) NULL,  
  10.     [ViewsCount] [decimal](18, 0) NULL,  
  11.     [Likes] [intNULL,  
  12.     [PubDate] [dateNULL,  
  13.  CONSTRAINT [PK_ArticleMatrices] PRIMARY KEY CLUSTERED   
  14. (  
  15.     [Id] ASC  
  16. ))  

Create a Web API project in Visual Studio

 
Open Visual Studio and create a new web application with “ASP.NET Web Application” template. You can also choose the “Web API” option.
 
C# Corner Author Posts Analytics With Angular 8
 
After clicking the OK button, our project will be created with default dependencies.
 
We can install “HtmlAgilityPack” NuGet library to scrape the data.
 
C# Corner Author Posts Analytics With Angular 8
 
We must enable CORS in this Web API project to access services from Angular 8 application. Hence, we install the "Microsoft.AspNet.WebApi.Cors“ library also.
 
C# Corner Author Posts Analytics With Angular 8 
 
Please note, this library will install other three related libraries as well.
 
We now need to enable CORS in “WebApiConfig” file. I have enabled CORS for all domains. In real applications, you can restrict it with a specific domain.
 
WebApiConfig.cs
  1. using System.Web.Http;  
  2. using System.Web.Http.Cors;  
  3.   
  4. namespace AnalyticsWebAPI  
  5. {  
  6.     public static class WebApiConfig  
  7.     {  
  8.         public static void Register(HttpConfiguration config)  
  9.         {  
  10.             // Web API configuration and services  
  11.             var cors = new EnableCorsAttribute("*""*""*");  
  12.             config.EnableCors(cors);  
  13.   
  14.             // Web API routes  
  15.             config.MapHttpAttributeRoutes();  
  16.   
  17.             config.Routes.MapHttpRoute(  
  18.                 name: "DefaultApi",  
  19.                 routeTemplate: "api/{controller}/{id}",  
  20.                 defaults: new { id = RouteParameter.Optional }  
  21.             );  
  22.         }  
  23.     }  
  24. }  
We need some model classes to fetch data from SQL server and populate C# Corner author information. For simplicity, I will create a single class file “Models” and create all classes inside this file.
 
Models.cs
  1. using System;  
  2.   
  3. namespace AnalyticsWebAPI.Models  
  4. {  
  5.     public class ArticleMatrix  
  6.     {  
  7.         public int Id { getset; }  
  8.         public string AuthorId { getset; }  
  9.         public string Author { getset; }  
  10.         public string Link { getset; }  
  11.         public string Title { getset; }  
  12.         public string Type { getset; }  
  13.         public string Category { getset; }  
  14.         public string Views { getset; }  
  15.         public decimal? ViewsCount { getset; }  
  16.         public int Likes { getset; }  
  17.         public DateTime PubDate { getset; }  
  18.     }  
  19.     public class Feed  
  20.     {  
  21.         public string Link { getset; }  
  22.         public string Title { getset; }  
  23.         public string FeedType { getset; }  
  24.         public string Author { getset; }  
  25.         public string Content { getset; }  
  26.         public DateTime PubDate { getset; }  
  27.   
  28.         public Feed()  
  29.         {  
  30.             Link = "";  
  31.             Title = "";  
  32.             FeedType = "";  
  33.             Author = "";  
  34.             Content = "";  
  35.             PubDate = DateTime.Today;  
  36.         }  
  37.     }  
  38.     public class Authors  
  39.     {  
  40.         public string AuthorId { getset; }  
  41.         public string Author { getset; }  
  42.         public int Count { getset; }  
  43.     }  
  44.   
  45.     public class Category  
  46.     {  
  47.         public string Name { getset; }  
  48.         public int Count { getset; }  
  49.     }  
  50. }  
I have created “ArticleMatrix”,“Feed”,” Authors”, “Category” model classes. Each class has its own significance in our application.
We can create a Web API controller using scaffolding.
 
C# Corner Author Posts Analytics With Angular 8
 
Choose “Web API 2 Controller with actions, using Entity Framework” option and choose a model class from the drop-down list to create a new controller.
 
We have already created “ArticleMatrices” table in SQL server and we have the same set of properties inside “ArticleMatrix” model class.
 
We will choose this class to create a controller. Entity Framework automatically chooses the mapping between the SQL table and model class.
 
C# Corner Author Posts Analytics With Angular 8
 
It will automatically create a “SqlDbContext” db context file along with the controller class file. Also, note that in Web.Config, a connection string is also created with default values. You can modify this connection string with your SQL Server and database details.
 
C# Corner Author Posts Analytics With Angular 8
 
We can add the methods inside the Web API controller.
 
CsharpCornerController.cs
  1. using AnalyticsWebAPI.Models;  
  2. using HtmlAgilityPack;  
  3. using System;  
  4. using System.Collections.Generic;  
  5. using System.Data;  
  6. using System.Globalization;  
  7. using System.IO;  
  8. using System.Linq;  
  9. using System.Net;  
  10. using System.Text;  
  11. using System.Web.Http;  
  12. using System.Xml.Linq;  
  13.   
  14. namespace AnalyticsWebAPI.Controllers  
  15. {  
  16.     [RoutePrefix("api/CsharpCorner")]  
  17.     public class CsharpCornerController : ApiController  
  18.     {  
  19.         private SqlDbContext db = new SqlDbContext();  
  20.         readonly CultureInfo culture = new CultureInfo("en-US");  
  21.   
  22.         [HttpGet]  
  23.         [Route("CreatePosts/{authorId}")]  
  24.         public bool CreatePosts(string authorId)  
  25.         {  
  26.             try  
  27.             {  
  28.                 int count = 0;  
  29.                 XDocument doc = XDocument.Load("https://www.c-sharpcorner.com/members/" + authorId + "/rss");  
  30.                 var entries = from item in doc.Root.Descendants().First(i => i.Name.LocalName == "channel").Elements().Where(i => i.Name.LocalName == "item")  
  31.                               select new Feed  
  32.                               {  
  33.                                   Content = item.Elements().First(i => i.Name.LocalName == "description").Value,  
  34.                                   Link = (item.Elements().First(i => i.Name.LocalName == "link").Value).StartsWith("/") ? "https://www.c-sharpcorner.com" + item.Elements().First(i => i.Name.LocalName == "link").Value : item.Elements().First(i => i.Name.LocalName == "link").Value,  
  35.                                   PubDate = Convert.ToDateTime(item.Elements().First(i => i.Name.LocalName == "pubDate").Value, culture),  
  36.                                   Title = item.Elements().First(i => i.Name.LocalName == "title").Value,  
  37.                                   FeedType = (item.Elements().First(i => i.Name.LocalName == "link").Value).ToLowerInvariant().Contains("blog") ? "Blog" : (item.Elements().First(i => i.Name.LocalName == "link").Value).ToLowerInvariant().Contains("news") ? "News" : "Article",  
  38.                                   Author = item.Elements().First(i => i.Name.LocalName == "author").Value  
  39.                               };  
  40.   
  41.                 List<Feed> feeds = entries.OrderByDescending(o => o.PubDate).ToList();  
  42.                 string urlAddress = string.Empty;  
  43.                 List<ArticleMatrix> articleMatrices = new List<ArticleMatrix>();  
  44.   
  45.                 foreach (Feed feed in feeds)  
  46.                 {  
  47.                     count++;  
  48.                     if (count > 100) break;  
  49.                     urlAddress = feed.Link;  
  50.   
  51.                     HttpWebRequest request = (HttpWebRequest)WebRequest.Create(urlAddress);  
  52.                     HttpWebResponse response = (HttpWebResponse)request.GetResponse();  
  53.                     string strData = "";  
  54.   
  55.                     if (response.StatusCode == HttpStatusCode.OK)  
  56.                     {  
  57.                         Stream receiveStream = response.GetResponseStream();  
  58.                         StreamReader readStream = null;  
  59.   
  60.                         if (response.CharacterSet == null)  
  61.                         {  
  62.                             readStream = new StreamReader(receiveStream);  
  63.                         }  
  64.                         else  
  65.                         {  
  66.                             readStream = new StreamReader(receiveStream, Encoding.GetEncoding(response.CharacterSet));  
  67.                         }  
  68.   
  69.                         strData = readStream.ReadToEnd();  
  70.   
  71.                         response.Close();  
  72.                         readStream.Close();  
  73.   
  74.                         HtmlDocument htmlDocument = new HtmlDocument();  
  75.                         htmlDocument.LoadHtml(strData);  
  76.   
  77.                         ArticleMatrix articleMatrix = new ArticleMatrix  
  78.                         {  
  79.                             AuthorId = authorId,  
  80.                             Author = feed.Author,  
  81.                             Type = feed.FeedType,  
  82.                             Link = feed.Link,  
  83.                             Title = feed.Title,  
  84.                             PubDate = feed.PubDate  
  85.                         };  
  86.   
  87.                         string category = htmlDocument.GetElementbyId("ImgCategory").GetAttributeValue("title""");  
  88.   
  89.                         articleMatrix.Category = category;  
  90.   
  91.                         var view = htmlDocument.DocumentNode.SelectSingleNode("//span[@id='ViewCounts']");  
  92.                         if (view != null)  
  93.                         {  
  94.                             articleMatrix.Views = view.InnerText;  
  95.   
  96.                             if (articleMatrix.Views.Contains("m"))  
  97.                             {  
  98.                                 articleMatrix.ViewsCount = decimal.Parse(articleMatrix.Views.Substring(0, articleMatrix.Views.Length - 1)) * 1000000;  
  99.                             }  
  100.                             else if (articleMatrix.Views.Contains("k"))  
  101.                             {  
  102.                                 articleMatrix.ViewsCount = decimal.Parse(articleMatrix.Views.Substring(0, articleMatrix.Views.Length - 1)) * 1000;  
  103.                             }  
  104.                             else  
  105.                             {  
  106.                                 decimal.TryParse(articleMatrix.Views, out decimal viewCount);  
  107.                                 articleMatrix.ViewsCount = viewCount;  
  108.                             }  
  109.                         }  
  110.                         else  
  111.                         {  
  112.                             articleMatrix.ViewsCount = 0;  
  113.                         }  
  114.                         var like = htmlDocument.DocumentNode.SelectSingleNode("//span[@id='LabelLikeCount']");  
  115.                         if (like != null)  
  116.                         {  
  117.                             int.TryParse(like.InnerText, out int likes);  
  118.                             articleMatrix.Likes = likes;  
  119.                         }  
  120.                         articleMatrices.Add(articleMatrix);  
  121.                     }  
  122.                 }  
  123.   
  124.                 count = 0;  
  125.   
  126.                 db.ArticleMatrices.RemoveRange(db.ArticleMatrices.Where(x => x.AuthorId == authorId));  
  127.   
  128.                 foreach (ArticleMatrix articleMatrix in articleMatrices)  
  129.                 {  
  130.                     count++;  
  131.                     db.ArticleMatrices.Add(articleMatrix);  
  132.                 }  
  133.   
  134.                 db.SaveChanges();  
  135.                 return true;  
  136.             }  
  137.             catch  
  138.             {  
  139.                 return false;  
  140.             }  
  141.   
  142.         }  
  143.   
  144.         [HttpGet]  
  145.         [Route("GetAll/{authorId}")]  
  146.         public IQueryable<ArticleMatrix> GetAll(string authorId)  
  147.         {  
  148.             return db.ArticleMatrices.Where(x => x.AuthorId == authorId).OrderByDescending(x => x.PubDate);  
  149.         }  
  150.   
  151.         [HttpGet]  
  152.         [Route("GetAuthors")]  
  153.         public IQueryable<Authors> GetAuthors()  
  154.         {  
  155.             return from x in db.ArticleMatrices.GroupBy(x => x.AuthorId)  
  156.                    select new Authors  
  157.                    {  
  158.                        AuthorId = x.FirstOrDefault().AuthorId,  
  159.                        Author = x.FirstOrDefault().Author,  
  160.                        Count = x.Count()  
  161.                    };  
  162.         }  
  163.   
  164.         [HttpGet]  
  165.         [Route("GetCategory/{authorId}")]  
  166.         public IQueryable<Category> GetCategory(string authorId)  
  167.         {  
  168.             return from x in db.ArticleMatrices.Where(x => x.AuthorId == authorId).GroupBy(x => x.Category)  
  169.                    select new Category  
  170.                    {  
  171.                        Name = x.FirstOrDefault().Category,  
  172.                        Count = x.Count()  
  173.                    };  
  174.         }  
  175.   
  176.         [HttpGet]  
  177.         [Route("GetPosts/{authorId}/{category}/{orderBy}")]  
  178.         public IQueryable<ArticleMatrix> GetPosts(string authorId, string category, string orderBy)  
  179.         {  
  180.             var newCategory = category.Replace("~~~"".").Replace("```""&").Replace("!!!""#");  
  181.             if (newCategory == "all")  
  182.             {  
  183.                 switch (orderBy)  
  184.                 {  
  185.                     case "likes":  
  186.                         return db.ArticleMatrices.Where(x => x.AuthorId == authorId).OrderByDescending(x => x.Likes);  
  187.                     case "views":  
  188.                         return db.ArticleMatrices.Where(x => x.AuthorId == authorId).OrderByDescending(x => x.ViewsCount);  
  189.                     case "category":  
  190.                         return db.ArticleMatrices.Where(x => x.AuthorId == authorId).OrderBy(x => x.Category);  
  191.                     case "type":  
  192.                         return db.ArticleMatrices.Where(x => x.AuthorId == authorId).OrderBy(x => x.Type);  
  193.                     default:  
  194.                         return db.ArticleMatrices.Where(x => x.AuthorId == authorId).OrderByDescending(x => x.PubDate);  
  195.                 }  
  196.             }  
  197.             else  
  198.             {  
  199.                 switch (orderBy)  
  200.                 {  
  201.                     case "likes":  
  202.                         return db.ArticleMatrices.Where(x => x.AuthorId == authorId && x.Category == newCategory).OrderByDescending(x => x.Likes);  
  203.                     case "views":  
  204.                         return db.ArticleMatrices.Where(x => x.AuthorId == authorId && x.Category == newCategory).OrderByDescending(x => x.ViewsCount);  
  205.                     case "category":  
  206.                         return db.ArticleMatrices.Where(x => x.AuthorId == authorId && x.Category == newCategory).OrderBy(x => x.Category);  
  207.                     case "type":  
  208.                         return db.ArticleMatrices.Where(x => x.AuthorId == authorId && x.Category == newCategory).OrderBy(x => x.Type);  
  209.                     default:  
  210.                         return db.ArticleMatrices.Where(x => x.AuthorId == authorId && x.Category == newCategory).OrderByDescending(x => x.PubDate);  
  211.                 }  
  212.             }  
  213.         }  
  214.   
  215.         protected override void Dispose(bool disposing)  
  216.         {  
  217.             if (disposing)  
  218.             {  
  219.                 db.Dispose();  
  220.             }  
  221.             base.Dispose(disposing);  
  222.         }  
  223.     }  
  224. }  
For code simplicity, I have made all the HTTP methods as GET methods only. I have created “CreatePosts”, “GetAll”, “GetAuthors”, “GetCategory”, “GetPosts” methods inside controller class.
 
The “CreatePosts” method is the most important method and it will populate the data for an author to the database. I have used many HttpAgilityPack properties inside this method. All other methods are self-explanatory.
 
We have successfully completed the Web API project. We can run the project and check methods inside the controller. Since I have created all the methods as HTTP GET, you can simply check it with any browser itself.
 

Create an Angular 8 Project using CLI

 
We can create the Angular 8 project using below command.
 
ng new AnalyticsAngular8
 
It will take some time to create all the node packages. We can install Chart.js package in our project using the below command.
 
npm install chart.js --save
 
We are all set to start coding in the Angular project.
 
We also have to install the “bootstrap” library to project.
 
Import bootstrap class inside the “style.css” file for future usage.
 
style.css
  1. /* You can add global styles to this file, and also import other style files */
  2. @import "~bootstrap/dist/css/bootstrap.css";
Le us import “HttpClientModule”, “FormsModule” and “ReactiveFormsModule” inside the app.module file.
 
app.module.ts
  1. import { BrowserModule } from '@angular/platform-browser';  
  2. import { NgModule } from '@angular/core';  
  3.   
  4. import { AppRoutingModule } from './app-routing.module';  
  5. import { AppComponent } from './app.component';  
  6.   
  7. import { HttpClientModule } from '@angular/common/http';    
  8. import { ReactiveFormsModule, FormsModule } from '@angular/forms';   
  9.   
  10. @NgModule({  
  11.   declarations: [  
  12.     AppComponent  
  13.   ],  
  14.   imports: [  
  15.     BrowserModule,  
  16.     AppRoutingModule,  
  17.     HttpClientModule,  
  18.     FormsModule,  
  19.     ReactiveFormsModule  
  20.   ],  
  21.   providers: [],  
  22.   bootstrap: [AppComponent]  
  23. })  
  24. export class AppModule { }  
Modify the “app.component” component file with the below code.
 
app.component.ts
  1. import { Component, OnInit } from '@angular/core';  
  2. import { HttpClient } from '@angular/common/http';  
  3. import { Chart } from 'chart.js';  
  4. import { FormGroup, FormBuilder } from '@angular/forms';  
  5.   
  6. @Component({  
  7.   selector: 'app-root',  
  8.   templateUrl: './app.component.html',  
  9.   styleUrls: ['./app.component.css']  
  10. })  
  11. export class AppComponent implements OnInit {  
  12.   constructor(private http: HttpClient, private fb: FormBuilder) { }  
  13.   
  14.   authors: Author[] = [];  
  15.   posts: Post[] = [];  
  16.   authorForm: FormGroup;  
  17.   chartClicked: boolean;  
  18.   showDetails: boolean;  
  19.   showLoader: boolean;  
  20.   
  21.   categories: string[] = [];  
  22.   counts: number[] = [];  
  23.   chart1: Chart;  
  24.   backColor: string[] = [];  
  25.   totalPosts: number;  
  26.   
  27.   selectedCategory: string;  
  28.   selectedAuthor: string;  
  29.   selectedCount: number;  
  30.   selectedAuthorId: string;  
  31.   
  32.   private baseUrl = 'http://localhost:4000/api/csharpcorner';  
  33.   
  34.   ngOnInit(): void {  
  35.     this.authorForm = this.fb.group({  
  36.       authorId: '',  
  37.       chartType: 'pie',  
  38.       author: '',  
  39.       category: '',  
  40.       orderBy: 'pubDate'  
  41.     });  
  42.     this.showDetails = false;  
  43.     this.showLoader = false;  
  44.     this.showAuthors();  
  45.   }  
  46.   
  47.   showAuthors() {  
  48.     this.http.get<Author[]>(this.baseUrl + '/getauthors').subscribe(result => {  
  49.       this.authors = result;  
  50.     }, error => console.error(error));  
  51.   }  
  52.   
  53.   fillData() {  
  54.     this.fillCategory();  
  55.   }  
  56.   
  57.   fillCategory() {  
  58.     if (this.chart1) this.chart1.destroy();  
  59.     this.showDetails = false;  
  60.     this.categories = [];  
  61.     this.counts = [];  
  62.     this.chartClicked = true;  
  63.     this.authorForm.patchValue({  
  64.       category: ''  
  65.     });  
  66.     this.totalPosts = 0;  
  67.     this.selectedAuthorId = this.authorForm.value.author.AuthorId;  
  68.     this.http.get<Categroy[]>(this.baseUrl + '/getcategory/' + this.authorForm.value.author.AuthorId).subscribe(result => {  
  69.       result.forEach(x => {  
  70.         this.totalPosts += x.Count;  
  71.         this.categories.push(x.Name);  
  72.         this.counts.push(x.Count);  
  73.         this.backColor.push(this.getRandomColor());  
  74.       });  
  75.       if (result.length == 0 || this.selectedAuthorId == undefined) return;  
  76.       this.chart1 = new Chart('canvas1', {  
  77.         type: this.authorForm.value.chartType,  
  78.         data: {  
  79.           labels: this.categories,  
  80.           datasets: [  
  81.             {  
  82.               data: this.counts,  
  83.               borderColor: '#3cba9f',  
  84.               backgroundColor: this.backColor,  
  85.               fill: true  
  86.             }  
  87.           ]  
  88.         },  
  89.         options: {  
  90.           legend: {  
  91.             display: false  
  92.           },  
  93.           scales: {  
  94.             xAxes: [{  
  95.               display: false  
  96.             }],  
  97.             yAxes: [{  
  98.               display: false  
  99.             }],  
  100.           }  
  101.         }  
  102.       });  
  103.   
  104.     }, error => console.error(error));  
  105.   }  
  106.   
  107.   clickChart(event: any) {  
  108.     var evt = this.chart1.chart.getElementAtEvent(event);  
  109.     if (evt.length == 0) return;  
  110.     this.chartClicked = true;  
  111.     this.authorForm.patchValue({  
  112.       category: this.categories[evt[0]._index]  
  113.     });  
  114.     this.fillDetails();  
  115.   }  
  116.   
  117.   populateData() {  
  118.     if (this.authorForm.value.authorId == '' || this.authorForm.value.authorId == undefined) {  
  119.       alert('Please give a valid Author Id');  
  120.       return;  
  121.     }  
  122.     this.showLoader = true;  
  123.     if (this.chart1) this.chart1.destroy();  
  124.     this.chartClicked = true;  
  125.     this.authorForm.patchValue({  
  126.       chartType: 'pie',  
  127.       author: ''  
  128.     });  
  129.     this.showDetails = false;  
  130.     this.http.get(this.baseUrl + '/CreatePosts/' + this.authorForm.value.authorId).subscribe(result => {  
  131.       this.showAuthors();  
  132.       this.showLoader = false;  
  133.       if (result == true) {  
  134.         alert('Author data successfully populated!');  
  135.       }  
  136.       else {  
  137.         alert('Invalid Author Id');  
  138.       }  
  139.       this.authorForm.patchValue({  
  140.         authorId: ''  
  141.       });  
  142.     }, error => console.error(error));  
  143.   
  144.   }  
  145.   
  146.   categorySelected() {  
  147.     if (this.chartClicked) {  
  148.       this.chartClicked = false;  
  149.       return;  
  150.     }  
  151.     this.fillDetails();  
  152.   }  
  153.   
  154.   fillDetails() {  
  155.     var category = this.authorForm.value.category;  
  156.     var newCategory = category.replace('.'"~~~").replace('&''```').replace('#''!!!');  
  157.     this.http.get<Post[]>(this.baseUrl + '/getposts/' + this.authorForm.value.author.AuthorId + '/' + newCategory + '/' + this.authorForm.value.orderBy).subscribe(result => {  
  158.       this.posts = result;  
  159.       this.selectedCategory = (category == 'all') ? 'All' : category;  
  160.       this.selectedCount = result.length;  
  161.       thisthis.selectedAuthor = this.authorForm.value.author.Author;  
  162.       this.showDetails = true;  
  163.     }, error => console.error(error));  
  164.   }  
  165.   
  166.   getRandomColor() {  
  167.     var letters = '0123456789ABCDEF';  
  168.     var color = '#';  
  169.     for (var i = 0; i < 6; i++) {  
  170.       color += letters[Math.floor(Math.random() * 16)];  
  171.     }  
  172.     return color;  
  173.   }  
  174.   
  175.   private delay(ms: number) {  
  176.     return new Promise(resolve => setTimeout(resolve, ms));  
  177.   }  
  178. }  
  179.   
  180. interface Author {  
  181.   AuthorId: string;  
  182.   Author: string;  
  183.   Count: number;  
  184. }  
  185.   
  186. interface Categroy {  
  187.   Name: string;  
  188.   Count: number;  
  189. }  
  190.   
  191. interface Post {  
  192.   Link: string;  
  193.   Title: string;  
  194.   Type: string;  
  195.   Category: string;  
  196.   Views: string;  
  197.   ViewsCount: number;  
  198.   Likes: number;  
  199.   PubDate: Date;  
  200. }  
Also, modify the corresponding HTML and CSS files with the below codes.
 
app.component.html
  1. <div style="text-align:center">  
  2.   <h2>  
  3.     <img width="50" src="../assets/csharpcornerlogo.png" alt="C# Corner Logo">  
  4.     C# Corner author posts analytics with  
  5.     <img width="50" alt="Angular Logo"  
  6.       src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTAgMjUwIj4KICAgIDxwYXRoIGZpbGw9IiNERDAwMzEiIGQ9Ik0xMjUgMzBMMzEuOSA2My4ybDE0LjIgMTIzLjFMMTI1IDIzMGw3OC45LTQzLjcgMTQuMi0xMjMuMXoiIC8+CiAgICA8cGF0aCBmaWxsPSIjQzMwMDJGIiBkPSJNMTI1IDMwdjIyLjItLjFWMjMwbDc4LjktNDMuNyAxNC4yLTEyMy4xTDEyNSAzMHoiIC8+CiAgICA8cGF0aCAgZmlsbD0iI0ZGRkZGRiIgZD0iTTEyNSA1Mi4xTDY2LjggMTgyLjZoMjEuN2wxMS43LTI5LjJoNDkuNGwxMS43IDI5LjJIMTgzTDEyNSA1Mi4xem0xNyA4My4zaC0zNGwxNy00MC45IDE3IDQwLjl6IiAvPgogIDwvc3ZnPg==">  
  7.   </h2>  
  8. </div>  
  9. <form novalidate [formGroup]="authorForm">  
  10.   <div class="card row" style="margin: 25px; height:425px;">  
  11.     <div class="card-header">  
  12.       Author Analytics  
  13.     </div>  
  14.   
  15.     <div class="card-body">  
  16.   
  17.   
  18.       <div class="row">  
  19.         <div class="col-md-6">  
  20.           <div class="form-group row mb-4">  
  21.             <label class="col-md-3 col-form-label" for="authorId">Author Id</label>  
  22.             <div class="col-md-4">  
  23.               <input class="form-control" id="authorId" formControlName="authorId" type="text"  
  24.                 placeholder="Eg: sarath-lal7" />  
  25.             </div>  
  26.             <div class="col-md-5">  
  27.               <button class="btn btn-primary mr-3" (click)="populateData()">  
  28.                 Populate Author Data  
  29.               </button>  
  30.             </div>  
  31.           </div>  
  32.   
  33.           <div class="form-group row mb-4">  
  34.             <label class="col-md-3 col-form-label" for="authorId">Author Name</label>  
  35.             <div class="col-md-4">  
  36.               <select formControlName="author" (ngModelChange)="fillData()" class="form-control" id="authorId">  
  37.                 <option value="" disabled>Select an Author</option>  
  38.                 <option *ngFor="let myauthor of authors" [ngValue]="myauthor">{{myauthor.Author}} </option>  
  39.               </select>  
  40.             </div>  
  41.             <label class="col-md-2 col-form-label" for="chartType">Chart Type</label>  
  42.             <div class="col-md-3">  
  43.               <select id="chartType" class="form-control" formControlName="chartType" (ngModelChange)="fillData()">  
  44.                 <option value="pie">Pie</option>  
  45.                 <option value="doughnut">Doughnut</option>  
  46.                 <option value="polarArea">Polar Area</option>  
  47.               </select>  
  48.             </div>  
  49.           </div>  
  50.           <div class="row mb-4">  
  51.             <div class="col-md-3">  
  52.             </div>  
  53.             <div class="card col-md-6" style="margin: 15px; height:100px;">  
  54.               <div class="card-header">  
  55.                 Choose Category  
  56.               </div>  
  57.               <div>  
  58.                 <select formControlName="category" (ngModelChange)="categorySelected()" class="form-control"  
  59.                   id="categoryId">  
  60.                   <option value="" disabled>Select a Category</option>  
  61.                   <option value="all">(All)</option>  
  62.                   <option *ngFor="let mycategory of categories" [ngValue]="mycategory">{{mycategory}} </option>  
  63.                 </select>  
  64.               </div>  
  65.             </div>  
  66.           </div>  
  67.           <div *ngIf="categories.length>0">  
  68.             <b> Total Categories : {{ categories.length}}     Total Posts : {{totalPosts}}</b>  
  69.           </div>  
  70.         </div>  
  71.         <div class="col-md-6">  
  72.           <div class="chart-container" style="position: relative; height:25vh; width:45vw" (click)="clickChart($event)">  
  73.             <canvas id="canvas1"></canvas>  
  74.           </div>  
  75.           <div class="file-loader" *ngIf="showLoader">  
  76.             <div class="upload-loader">  
  77.               <div class="loader"></div>  
  78.             </div>  
  79.           </div>  
  80.         </div>  
  81.   
  82.       </div>  
  83.     </div>  
  84.   
  85.   </div>  
  86.   
  87.   <div class="card row" style="margin: 25px; height:450px;" *ngIf="showDetails && totalPosts>0">  
  88.     <div class="card-header">  
  89.       Author Name : <b>{{selectedAuthor}}</b>      
  90.       Category : <b>{{selectedCategory}}</b>      
  91.       Post Count : <b>{{selectedCount}}</b>     
  92.       Order By :      
  93.       <select id="orderBy" formControlName="orderBy" (ngModelChange)="fillDetails()">  
  94.         <option value="pubDate">Publish Date</option>  
  95.         <option value="views">Views</option>  
  96.         <option value="likes">Likes</option>  
  97.         <option value="category">Category</option>  
  98.         <option value="type">Post Type</option>  
  99.       </select>  
  100.     </div>  
  101.   
  102.     <div class="card-body">  
  103.       <div class="table-responsive" style="max-height:350px; font-size: 12px">  
  104.         <table class="table mb-0" *ngIf="posts && posts.length>0">  
  105.           <thead>  
  106.             <tr>  
  107.               <th>Sl.No.</th>  
  108.               <th>Post Type</th>  
  109.               <th>Category</th>  
  110.               <th>Title</th>  
  111.               <th>Views</th>  
  112.               <th>Likes</th>  
  113.               <th>Published Date</th>  
  114.             </tr>  
  115.           </thead>  
  116.           <tbody>  
  117.             <tr *ngFor="let post of posts; let i=index">  
  118.               <td>{{i+1}}</td>  
  119.               <td>{{post.Type}}</td>  
  120.               <td>{{post.Category}}</td>  
  121.               <td><a href="{{post.Link}}" target="_blank">{{post.Title}}</a></td>  
  122.               <td>{{post.Views}}</td>  
  123.               <td>{{post.Likes}}</td>  
  124.               <td>{{post.PubDate | date: 'dd-MMM-yyyy'}}</td>  
  125.             </tr>  
  126.           </tbody>  
  127.         </table>  
  128.       </div>  
  129.     </div>  
  130.   </div>  
  131. </form>  
  132.   
  133. <router-outlet></router-outlet>  
app.component.css
  1. /* Spin Start*/  
  2.   
  3. .file-loader {  
  4.   background-color: rgba(000, .5);  
  5.   overflowhidden;  
  6.   positionfixed;  
  7.   top: 0;  
  8.   left: 0;  
  9.   right: 0;  
  10.   bottom: 0;  
  11.   z-index100000 !important;  
  12. }  
  13.   
  14. .upload-loader {  
  15.   positionabsolute;  
  16.   width60px;  
  17.   height60px;  
  18.   left: 50%;  
  19.   top: 50%;  
  20.   transform: translate(-50%-50%);  
  21. }  
  22.   
  23. .upload-loader .loader {  
  24.   border5px solid #f3f3f3 !important;  
  25.   border-radius: 50%;  
  26.   border-top5px solid #005eb8 !important;  
  27.   width100% !important;  
  28.   height100% !important;  
  29.   -webkit-animation: spin 2s linear infinite;  
  30.   animation: spin 2s linear infinite;  
  31. }  
  32.   
  33. @-webkit-keyframes spin {  
  34.   0% {  
  35.     -webkit-transform: rotate(0deg);  
  36.   }  
  37.   100% {  
  38.     -webkit-transform: rotate(360deg);  
  39.   }  
  40. }  
  41.   
  42. @keyframes spin {  
  43.   0% {  
  44.     transform: rotate(0deg);  
  45.   }  
  46.   100% {  
  47.     transform: rotate(360deg);  
  48.   }  
  49. }  
  50.   
  51. /* Spin End*/  
We have completed the coding part in the Angular project also. We can run both, Web API project and Angular project, now.
 
C# Corner Author Posts Analytics With Angular 8
 
Enter an author id and click “Populate Author Data” button to fetch author post details from the C# Corner site.
 
C# Corner Author Posts Analytics With Angular 8
 
It will take some time to scrape data from the site based on the number of posts this author has published.
 
After completing the data population, the user can select an author name from the drop-down list.
 
C# Corner Author Posts Analytics With Angular 8
 
Whenever you process a new author's data, the author name will be added to the drop-down list automatically. After choosing the author name, the post's category will be shown as a chart. You can see the category name as a tooltip in the chart.
 
C# Corner Author Posts Analytics With Angular 8
 
It will add posted categories to the category drop-down also.
 
C# Corner Author Posts Analytics With Angular 8
 
You can choose any of these categories and will get the entire list of posts for that category.
 
Currently, I have added three types of Charts - “Pie”, “Doughnut” and “Polar Area”.
 
C# Corner Author Posts Analytics With Angular 8
 
You can view the chart in any of these types. Below is a Polar Type chart. The previous chart was a Pie chart.
 
C# Corner Author Posts Analytics With Angular 8
 
You can choose a category by clicking on a chart or choosing from the category drop-down. The entire posts for that category will be shown as below.
 
C# Corner Author Posts Analytics With Angular 8
 
You can view the posts in different orders by choosing from "Order By" drop-down.
 
C# Corner Author Posts Analytics With Angular 8
 
C# Corner Author Posts Analytics With Angular 8
 
I have hosted the Angular app on Azure. You can analyze your post details through this Live App.
 

Conclusion

 
In this article, we saw how to scrape C# Corner author post information from the site using “HtmlAgilityPack” library and Web API service. We also discussed how to show this data in an Angular 8 application. We have used Chart.js library to show different types of charts and showed the entire posts in a grid. Please note that currently, C# Corner feeds give a maximum of 100 post details only. Hence, we can analyze a maximum of 100 posts only. I have attached the source code of both the Angular and Web API projects with this article. Please check from your side and give your valuable feedback on this article and application.