From a1199fe9fe6cd7cee7aa9a02035ecddb1db83be1 Mon Sep 17 00:00:00 2001 From: Tommy Parnell Date: Sun, 4 Oct 2015 21:53:01 -0400 Subject: [PATCH] init --- .gitignore | 223 ++++++++++++++++++ DotNetMashup.sln | 40 ++++ NuGet.Config | 8 + global.json | 6 + src/DotNetMashup.Web/.bowerrc | 3 + .../Controllers/HomeController.cs | 44 ++++ src/DotNetMashup.Web/DotNetMashup.Web.xproj | 19 ++ .../Extensions/StringHtmlExtension.cs | 169 +++++++++++++ .../Factories/BlogPostFactory.cs | 151 ++++++++++++ src/DotNetMashup.Web/Factories/IFactory.cs | 15 ++ src/DotNetMashup.Web/Global/ISiteSetting.cs | 12 + src/DotNetMashup.Web/Global/SiteSettings.cs | 12 + src/DotNetMashup.Web/Model/BlogPost.cs | 19 ++ src/DotNetMashup.Web/Model/IExternalData.cs | 15 ++ src/DotNetMashup.Web/Model/IFeedData.cs | 16 ++ src/DotNetMashup.Web/Model/MetaData.cs | 12 + src/DotNetMashup.Web/Project_Readme.html | 203 ++++++++++++++++ src/DotNetMashup.Web/Startup.cs | 91 +++++++ src/DotNetMashup.Web/Views/Home/About.cshtml | 7 + .../Views/Home/Contact.cshtml | 17 ++ src/DotNetMashup.Web/Views/Home/Index.cshtml | 104 ++++++++ .../Views/Shared/Error.cshtml | 6 + .../Views/Shared/_Layout.cshtml | 80 +++++++ .../Views/_ViewImports.cshtml | 2 + src/DotNetMashup.Web/Views/_ViewStart.cshtml | 3 + src/DotNetMashup.Web/blogfeed.json | 9 + src/DotNetMashup.Web/bower.json | 12 + src/DotNetMashup.Web/config.json | 7 + src/DotNetMashup.Web/gulpfile.js | 45 ++++ src/DotNetMashup.Web/hosting.ini | 2 + src/DotNetMashup.Web/package.json | 11 + src/DotNetMashup.Web/project.json | 48 ++++ src/DotNetMashup/DotNetMashup.xproj | 20 ++ .../Extensions/StringHtmlExtension.cs | 169 +++++++++++++ src/DotNetMashup/Model/BlogPost.cs | 19 ++ src/DotNetMashup/Model/MetaData.cs | 12 + src/DotNetMashup/project.json | 22 ++ 37 files changed, 1653 insertions(+) create mode 100644 .gitignore create mode 100644 DotNetMashup.sln create mode 100644 NuGet.Config create mode 100644 global.json create mode 100644 src/DotNetMashup.Web/.bowerrc create mode 100644 src/DotNetMashup.Web/Controllers/HomeController.cs create mode 100644 src/DotNetMashup.Web/DotNetMashup.Web.xproj create mode 100644 src/DotNetMashup.Web/Extensions/StringHtmlExtension.cs create mode 100644 src/DotNetMashup.Web/Factories/BlogPostFactory.cs create mode 100644 src/DotNetMashup.Web/Factories/IFactory.cs create mode 100644 src/DotNetMashup.Web/Global/ISiteSetting.cs create mode 100644 src/DotNetMashup.Web/Global/SiteSettings.cs create mode 100644 src/DotNetMashup.Web/Model/BlogPost.cs create mode 100644 src/DotNetMashup.Web/Model/IExternalData.cs create mode 100644 src/DotNetMashup.Web/Model/IFeedData.cs create mode 100644 src/DotNetMashup.Web/Model/MetaData.cs create mode 100644 src/DotNetMashup.Web/Project_Readme.html create mode 100644 src/DotNetMashup.Web/Startup.cs create mode 100644 src/DotNetMashup.Web/Views/Home/About.cshtml create mode 100644 src/DotNetMashup.Web/Views/Home/Contact.cshtml create mode 100644 src/DotNetMashup.Web/Views/Home/Index.cshtml create mode 100644 src/DotNetMashup.Web/Views/Shared/Error.cshtml create mode 100644 src/DotNetMashup.Web/Views/Shared/_Layout.cshtml create mode 100644 src/DotNetMashup.Web/Views/_ViewImports.cshtml create mode 100644 src/DotNetMashup.Web/Views/_ViewStart.cshtml create mode 100644 src/DotNetMashup.Web/blogfeed.json create mode 100644 src/DotNetMashup.Web/bower.json create mode 100644 src/DotNetMashup.Web/config.json create mode 100644 src/DotNetMashup.Web/gulpfile.js create mode 100644 src/DotNetMashup.Web/hosting.ini create mode 100644 src/DotNetMashup.Web/package.json create mode 100644 src/DotNetMashup.Web/project.json create mode 100644 src/DotNetMashup/DotNetMashup.xproj create mode 100644 src/DotNetMashup/Extensions/StringHtmlExtension.cs create mode 100644 src/DotNetMashup/Model/BlogPost.cs create mode 100644 src/DotNetMashup/Model/MetaData.cs create mode 100644 src/DotNetMashup/project.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..653b47d --- /dev/null +++ b/.gitignore @@ -0,0 +1,223 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +build/ +bld/ +[Bb]in/ +[Oo]bj/ + +# Visual Studio 2015 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +wwwroot/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# DNX +project.lock.json +artifacts/ + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opensdf +*.sdf +*.cachefile + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# TODO: Comment the next line if you want to checkin your web deploy settings +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config + +# Windows Azure Build Output +csx/ +*.build.csdef + +# Windows Store app package directory +AppPackages/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +[Ss]tyle[Cc]op.* +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.pfx +*.publishsettings +node_modules/ +orleans.codegen.cs + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe + +# FAKE - F# Make +.fake/ diff --git a/DotNetMashup.sln b/DotNetMashup.sln new file mode 100644 index 0000000..980d50e --- /dev/null +++ b/DotNetMashup.sln @@ -0,0 +1,40 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.23107.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{67290B97-2478-46D3-9D10-87A0B89A03C2}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{8610EB85-922A-40CE-AB4B-355048BFC7D2}" + ProjectSection(SolutionItems) = preProject + global.json = global.json + NuGet.Config = NuGet.Config + EndProjectSection +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "DotNetMashup", "src\DotNetMashup\DotNetMashup.xproj", "{E68542EE-79D4-47A1-BC66-BFB4B78856A1}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "DotNetMashup.Web", "src\DotNetMashup.Web\DotNetMashup.Web.xproj", "{53E3E5AF-13BF-465D-89E8-B68E4CCC4D7E}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {E68542EE-79D4-47A1-BC66-BFB4B78856A1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E68542EE-79D4-47A1-BC66-BFB4B78856A1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E68542EE-79D4-47A1-BC66-BFB4B78856A1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E68542EE-79D4-47A1-BC66-BFB4B78856A1}.Release|Any CPU.Build.0 = Release|Any CPU + {53E3E5AF-13BF-465D-89E8-B68E4CCC4D7E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {53E3E5AF-13BF-465D-89E8-B68E4CCC4D7E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {53E3E5AF-13BF-465D-89E8-B68E4CCC4D7E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {53E3E5AF-13BF-465D-89E8-B68E4CCC4D7E}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {E68542EE-79D4-47A1-BC66-BFB4B78856A1} = {67290B97-2478-46D3-9D10-87A0B89A03C2} + {53E3E5AF-13BF-465D-89E8-B68E4CCC4D7E} = {67290B97-2478-46D3-9D10-87A0B89A03C2} + EndGlobalSection +EndGlobal diff --git a/NuGet.Config b/NuGet.Config new file mode 100644 index 0000000..95143bd --- /dev/null +++ b/NuGet.Config @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/global.json b/global.json new file mode 100644 index 0000000..329918d --- /dev/null +++ b/global.json @@ -0,0 +1,6 @@ +{ + "projects": [ "src", "test" ], + "sdk": { + "version": "1.0.0-beta7" + } +} diff --git a/src/DotNetMashup.Web/.bowerrc b/src/DotNetMashup.Web/.bowerrc new file mode 100644 index 0000000..6406626 --- /dev/null +++ b/src/DotNetMashup.Web/.bowerrc @@ -0,0 +1,3 @@ +{ + "directory": "wwwroot/lib" +} diff --git a/src/DotNetMashup.Web/Controllers/HomeController.cs b/src/DotNetMashup.Web/Controllers/HomeController.cs new file mode 100644 index 0000000..f46a7b1 --- /dev/null +++ b/src/DotNetMashup.Web/Controllers/HomeController.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using DotNetMashup.Web.Factories; +using Microsoft.AspNet.Mvc; + +namespace DotNetMashup.Web.Controllers +{ + public class HomeController : Controller + { + private readonly BlogPostFactory factory; + + public HomeController(BlogPostFactory factory) + { + this.factory = factory; + } + + public IActionResult Index() + { + var data = factory.GetData(); + return View(); + } + + public IActionResult About() + { + ViewData["Message"] = "Your application description page."; + + return View(); + } + + public IActionResult Contact() + { + ViewData["Message"] = "Your contact page."; + + return View(); + } + + public IActionResult Error() + { + return View("~/Views/Shared/Error.cshtml"); + } + } +} \ No newline at end of file diff --git a/src/DotNetMashup.Web/DotNetMashup.Web.xproj b/src/DotNetMashup.Web/DotNetMashup.Web.xproj new file mode 100644 index 0000000..731f982 --- /dev/null +++ b/src/DotNetMashup.Web/DotNetMashup.Web.xproj @@ -0,0 +1,19 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + 53e3e5af-13bf-465d-89e8-b68e4ccc4d7e + DotNetMashup.Web + ..\..\artifacts\obj\$(MSBuildProjectName) + ..\..\artifacts\bin\$(MSBuildProjectName)\ + + + 2.0 + 59713 + + + \ No newline at end of file diff --git a/src/DotNetMashup.Web/Extensions/StringHtmlExtension.cs b/src/DotNetMashup.Web/Extensions/StringHtmlExtension.cs new file mode 100644 index 0000000..599a58e --- /dev/null +++ b/src/DotNetMashup.Web/Extensions/StringHtmlExtension.cs @@ -0,0 +1,169 @@ +//This code came from https://raw.githubusercontent.com/robvolk/Helpers.Net/master/Src/Helpers.Net/StringHtmlExtensions.cs + +namespace DotNetMashup.Web.Extensions +{ + using System.Collections.Generic; + using System.Text; + using System.Text.RegularExpressions; + + public static class StringHtmlExtensions + { + /// + /// Truncates a string containing HTML to a number of text characters, keeping whole words. + /// The result contains HTML and any tags left open are closed. + /// + /// + /// + public static string TruncateHtml(this string html, int maxCharacters, string trailingText) + { + if(string.IsNullOrEmpty(html)) + return html; + + // find the spot to truncate + // count the text characters and ignore tags + var textCount = 0; + var charCount = 0; + var ignore = false; + foreach(char c in html) + { + charCount++; + if(c == '<') + ignore = true; + else if(!ignore) + textCount++; + + if(c == '>') + ignore = false; + + // stop once we hit the limit + if(textCount >= maxCharacters) + break; + } + + // Truncate the html and keep whole words only + var trunc = new StringBuilder(html.TruncateWords(charCount)); + + // keep track of open tags and close any tags left open + var tags = new Stack(); + var matches = Regex.Matches(trunc.ToString(), + @"<((?[^\s/>]+)|/(?[^\s>]+)).*?(?/)?\s*>", + RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.Multiline); + + foreach(Match match in matches) + { + if(match.Success) + { + var tag = match.Groups["tag"].Value; + var closeTag = match.Groups["closeTag"].Value; + + // push to stack if open tag and ignore it if it is self-closing, i.e.
+ if(!string.IsNullOrEmpty(tag) && string.IsNullOrEmpty(match.Groups["selfClose"].Value)) + tags.Push(tag); + + // pop from stack if close tag + else if(!string.IsNullOrEmpty(closeTag)) + { + // pop the tag to close it.. find the matching opening tag + // ignore any unclosed tags + while(tags.Pop() != closeTag && tags.Count > 0) + { } + } + } + } + + if(html.Length > charCount) + // add the trailing text + trunc.Append(trailingText); + + // pop the rest off the stack to close remainder of tags + while(tags.Count > 0) + { + trunc.Append("'); + } + + return trunc.ToString(); + } + + /// + /// Truncates a string containing HTML to a number of text characters, keeping whole words. + /// The result contains HTML and any tags left open are closed. + /// + /// + /// + public static string TruncateHtml(this string html, int maxCharacters) + { + return html.TruncateHtml(maxCharacters, null); + } + + /// + /// Strips all HTML tags from a string + /// + /// + /// + public static string StripHtml(this string html) + { + if(string.IsNullOrEmpty(html)) + return html; + + return Regex.Replace(html, @"<(.|\n)*?>", string.Empty); + } + + /// + /// Truncates text to a number of characters + /// + /// + /// + /// + /// + public static string Truncate(this string text, int maxCharacters) + { + return text.Truncate(maxCharacters, null); + } + + /// + /// Truncates text to a number of characters and adds trailing text, i.e. elipses, to the end + /// + /// + /// + /// + /// + public static string Truncate(this string text, int maxCharacters, string trailingText) + { + if(string.IsNullOrEmpty(text) || maxCharacters <= 0 || text.Length <= maxCharacters) + return text; + else + return text.Substring(0, maxCharacters) + trailingText; + } + + /// + /// Truncates text and discars any partial words left at the end + /// + /// + /// + /// + /// + public static string TruncateWords(this string text, int maxCharacters) + { + return text.TruncateWords(maxCharacters, null); + } + + /// + /// Truncates text and discars any partial words left at the end + /// + /// + /// + /// + /// + public static string TruncateWords(this string text, int maxCharacters, string trailingText) + { + if(string.IsNullOrEmpty(text) || maxCharacters <= 0 || text.Length <= maxCharacters) + return text; + + // trunctate the text, then remove the partial word at the end + return Regex.Replace(text.Truncate(maxCharacters), + @"\s+[^\s]+$", string.Empty, RegexOptions.IgnoreCase | RegexOptions.Compiled) + trailingText; + } + } +} \ No newline at end of file diff --git a/src/DotNetMashup.Web/Factories/BlogPostFactory.cs b/src/DotNetMashup.Web/Factories/BlogPostFactory.cs new file mode 100644 index 0000000..6eca9e9 --- /dev/null +++ b/src/DotNetMashup.Web/Factories/BlogPostFactory.cs @@ -0,0 +1,151 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.ServiceModel.Syndication; +using System.Threading.Tasks; +using System.Xml; +using DotNetMashup.Web.Extensions; +using DotNetMashup.Web.Global; +using DotNetMashup.Web.Model; +using Microsoft.Framework.Caching.Memory; + +namespace DotNetMashup.Web.Factories +{ + public class BlogPostFactory : IFactory + { + private readonly ISiteSetting setting; + + private readonly IMemoryCache cache; + private readonly IEnumerable _data; + private const string cacheKey = "blogposts"; + + public BlogPostFactory(IEnumerable data, IMemoryCache cache, ISiteSetting setting) + { + this._data = data; + this.cache = cache; + this.setting = setting; + } + + public string FactoryName + { + get + { + return "BlogPost"; + } + } + + public IEnumerable GetData() + { + var cachedata = cache.Get>(cacheKey); + if(cachedata != null) return cachedata; + var syndicationFeeds = GetSyndicationFeeds(_data); + + var data = syndicationFeeds + .SelectMany(pair => pair.Value.Items, (pair, item) => new { Id = pair.Key, Item = item }) + .Where(x => x.Item.Categories.Any(category => setting.Categories.Any(setting => string.Equals(setting, category.Name, StringComparison.OrdinalIgnoreCase)))) + .Select(x => + { + var metaauthor = _data.First(y => y.Id == x.Id); + var authorname = metaauthor.Author; + var authoremail = metaauthor.AuthorEmail; + + var link = x.Item.Links.FirstOrDefault(y => y.RelationshipType == "alternate"); + var locallink = string.Empty; + if(link != null) + { + locallink = link.Uri.Segments.Last(); + if(locallink.Contains(".")) + { + locallink = locallink.Substring(0, locallink.IndexOf(".", System.StringComparison.Ordinal)); + } + } + + var originallink = link == null ? string.Empty : link.Uri.AbsoluteUri; + + var summary = x.Item.Summary == null + ? ((TextSyndicationContent)x.Item.Content).Text + : x.Item.Summary.Text; + + var truncatedSummary = summary.TruncateHtml(700, ""); + + var encodedcontent = x.Item.ElementExtensions.ReadElementExtensions("encoded", + "http://purl.org/rss/1.0/modules/content/"); + + var content = string.Empty; + + if(encodedcontent.Any()) + { + content = encodedcontent.First(); + } + else if(x.Item.Content != null) + { + content = ((TextSyndicationContent)x.Item.Content).Text; + } + else + { + content = summary; + } + + return new BlogPost + { + Title = x.Item.Title.Text, + Summary = truncatedSummary, + Author = authorname, + AuthorEmail = authoremail, + Localink = locallink, + OriginalLink = originallink, + PublishedDate = x.Item.PublishDate.DateTime, + Content = content + }; + }) + .OrderByDescending(x => x.PublishedDate) + .ToList(); + cache.Set(cacheKey, data.Cast()); + return data; + } + + private static IEnumerable> GetSyndicationFeeds(IEnumerable metadataEntries) + { + var syndicationFeeds = new List>(); + foreach(var metadata in metadataEntries) + { + GetFeed(metadata.FeedUrl, metadata.Id, syndicationFeeds); + } + + return syndicationFeeds; + } + + private static void GetFeed(string url, string id, List> syndicationFeeds) + { + try + { + SyndicationFeed feed = null; + using(var reader = XmlReader.Create(url)) + { + feed = SyndicationFeed.Load(reader); + } + + if(feed != null) + { + syndicationFeeds.Add(new KeyValuePair(id, feed)); + if(feed.Links.Any(x => x.RelationshipType == "next")) + { + foreach(var pagingLink in feed.Links.Where(x => x.RelationshipType == "next")) + { + GetFeed(pagingLink.Uri.AbsoluteUri, id, syndicationFeeds); + } + } + } + } + catch(WebException) + { + //Unable to load RSS feed + } + catch(XmlException) + { + //Unable to load RSS feed + } + } + } +} \ No newline at end of file diff --git a/src/DotNetMashup.Web/Factories/IFactory.cs b/src/DotNetMashup.Web/Factories/IFactory.cs new file mode 100644 index 0000000..e3baa36 --- /dev/null +++ b/src/DotNetMashup.Web/Factories/IFactory.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using DotNetMashup.Web.Model; + +namespace DotNetMashup.Web.Factories +{ + public interface IFactory + { + string FactoryName { get; } + + IEnumerable GetData(); + } +} \ No newline at end of file diff --git a/src/DotNetMashup.Web/Global/ISiteSetting.cs b/src/DotNetMashup.Web/Global/ISiteSetting.cs new file mode 100644 index 0000000..79b4a14 --- /dev/null +++ b/src/DotNetMashup.Web/Global/ISiteSetting.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace DotNetMashup.Web.Global +{ + public interface ISiteSetting + { + List Categories { get; } + } +} \ No newline at end of file diff --git a/src/DotNetMashup.Web/Global/SiteSettings.cs b/src/DotNetMashup.Web/Global/SiteSettings.cs new file mode 100644 index 0000000..05ac41d --- /dev/null +++ b/src/DotNetMashup.Web/Global/SiteSettings.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace DotNetMashup.Web.Global +{ + public class SiteSettings : ISiteSetting + { + public List Categories { get; } = new List { "c#", "csharp", "cs", "asp.net", "NancyFx", "Nancy", "vNext", "asp.net 5" }; + } +} \ No newline at end of file diff --git a/src/DotNetMashup.Web/Model/BlogPost.cs b/src/DotNetMashup.Web/Model/BlogPost.cs new file mode 100644 index 0000000..7f152f8 --- /dev/null +++ b/src/DotNetMashup.Web/Model/BlogPost.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace DotNetMashup.Web.Model +{ + public class BlogPost : IExternalData + { + public string Title { get; set; } + public string Content { get; set; } + public DateTime PublishedDate { get; set; } + public string Summary { get; set; } + public string Localink { get; set; } + public string OriginalLink { get; set; } + public string Author { get; set; } + public string AuthorEmail { get; set; } + } +} \ No newline at end of file diff --git a/src/DotNetMashup.Web/Model/IExternalData.cs b/src/DotNetMashup.Web/Model/IExternalData.cs new file mode 100644 index 0000000..557a5be --- /dev/null +++ b/src/DotNetMashup.Web/Model/IExternalData.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace DotNetMashup.Web.Model +{ + public interface IExternalData + { + string Title { get; set; } + string Content { get; set; } + string OriginalLink { get; set; } + string Author { get; set; } + } +} \ No newline at end of file diff --git a/src/DotNetMashup.Web/Model/IFeedData.cs b/src/DotNetMashup.Web/Model/IFeedData.cs new file mode 100644 index 0000000..537f6fc --- /dev/null +++ b/src/DotNetMashup.Web/Model/IFeedData.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace DotNetMashup.Web.Model +{ + public interface IFeedData + { + string FeedUrl { get; set; } + string Author { get; set; } + string AuthorEmail { get; set; } + string GravatarUrl { get; set; } + string Id { get; set; } + } +} \ No newline at end of file diff --git a/src/DotNetMashup.Web/Model/MetaData.cs b/src/DotNetMashup.Web/Model/MetaData.cs new file mode 100644 index 0000000..1cdbea0 --- /dev/null +++ b/src/DotNetMashup.Web/Model/MetaData.cs @@ -0,0 +1,12 @@ +namespace DotNetMashup.Web.Model +{ + //stolen idea from: https://github.com/NancyFx/Nancy.Blog/blob/master/src/Nancy.Blog/Model/MetaData.cs + public class MetaData : IFeedData + { + public string FeedUrl { get; set; } + public string Author { get; set; } + public string AuthorEmail { get; set; } + public string GravatarUrl { get; set; } + public string Id { get; set; } + } +} \ No newline at end of file diff --git a/src/DotNetMashup.Web/Project_Readme.html b/src/DotNetMashup.Web/Project_Readme.html new file mode 100644 index 0000000..bc23184 --- /dev/null +++ b/src/DotNetMashup.Web/Project_Readme.html @@ -0,0 +1,203 @@ + + + + + Welcome to ASP.NET 5 + + + + + + + + + + diff --git a/src/DotNetMashup.Web/Startup.cs b/src/DotNetMashup.Web/Startup.cs new file mode 100644 index 0000000..e2941f0 --- /dev/null +++ b/src/DotNetMashup.Web/Startup.cs @@ -0,0 +1,91 @@ +using System.Collections.Generic; +using System.IO; +using DotNetMashup.Web.Factories; +using DotNetMashup.Web.Global; +using DotNetMashup.Web.Model; +using Microsoft.AspNet.Builder; +using Microsoft.AspNet.Hosting; +using Microsoft.Dnx.Runtime; +using Microsoft.Framework.Caching.Memory; +using Microsoft.Framework.Configuration; +using Microsoft.Framework.DependencyInjection; +using Microsoft.Framework.Logging; +using Newtonsoft.Json; + +namespace DotNetMashup.Web +{ + public class Startup + { + private IEnumerable _feedData = null; + + public Startup(IHostingEnvironment env, IApplicationEnvironment appEnv) + { + // Setup configuration sources. + var builder = new ConfigurationBuilder(appEnv.ApplicationBasePath) + .AddJsonFile("config.json") + .AddEnvironmentVariables(); + Configuration = builder.Build(); + _feedData = JsonConvert.DeserializeObject>(File.ReadAllText(Path.Combine(appEnv.ApplicationBasePath, "blogfeed.json"))); + } + + public IConfigurationRoot Configuration { get; set; } + + // This method gets called by the runtime. + public void ConfigureServices(IServiceCollection services) + { + services.AddSingleton(prov => + { + return new SiteSettings(); + }); + services.AddSingleton((provider) => + { + return new MemoryCache(new MemoryCacheOptions()); + }); + services.AddInstance(_feedData); + services.AddSingleton(); + // Add MVC services to the services container. + services.AddMvc(); + + // Uncomment the following line to add Web API services which makes it easier to port Web API 2 controllers. + // You will also need to add the Microsoft.AspNet.Mvc.WebApiCompatShim package to the 'dependencies' section of project.json. + // services.AddWebApiConventions(); + } + + // Configure is called after ConfigureServices is called. + public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) + { + loggerFactory.MinimumLevel = LogLevel.Information; + loggerFactory.AddConsole(); + loggerFactory.AddDebug(); + + // Configure the HTTP request pipeline. + + // Add the following to the request pipeline only in development environment. + if(env.IsDevelopment()) + { + app.UseBrowserLink(); + app.UseErrorPage(); + } + else + { + // Add Error handling middleware which catches all application specific errors and + // send the request to the following path or controller action. + app.UseErrorHandler("/Home/Error"); + } + + // Add static files to the request pipeline. + app.UseStaticFiles(); + + // Add MVC to the request pipeline. + app.UseMvc(routes => + { + routes.MapRoute( + name: "default", + template: "{controller=Home}/{action=Index}/{id?}"); + + // Uncomment the following line to add a route for porting Web API 2 controllers. + // routes.MapWebApiRoute("DefaultApi", "api/{controller}/{id?}"); + }); + } + } +} \ No newline at end of file diff --git a/src/DotNetMashup.Web/Views/Home/About.cshtml b/src/DotNetMashup.Web/Views/Home/About.cshtml new file mode 100644 index 0000000..50476d1 --- /dev/null +++ b/src/DotNetMashup.Web/Views/Home/About.cshtml @@ -0,0 +1,7 @@ +@{ + ViewData["Title"] = "About"; +} +

@ViewData["Title"].

+

@ViewData["Message"]

+ +

Use this area to provide additional information.

diff --git a/src/DotNetMashup.Web/Views/Home/Contact.cshtml b/src/DotNetMashup.Web/Views/Home/Contact.cshtml new file mode 100644 index 0000000..15c12c6 --- /dev/null +++ b/src/DotNetMashup.Web/Views/Home/Contact.cshtml @@ -0,0 +1,17 @@ +@{ + ViewData["Title"] = "Contact"; +} +

@ViewData["Title"].

+

@ViewData["Message"]

+ +
+ One Microsoft Way
+ Redmond, WA 98052-6399
+ P: + 425.555.0100 +
+ +
+ Support: Support@example.com
+ Marketing: Marketing@example.com +
diff --git a/src/DotNetMashup.Web/Views/Home/Index.cshtml b/src/DotNetMashup.Web/Views/Home/Index.cshtml new file mode 100644 index 0000000..1a0fa32 --- /dev/null +++ b/src/DotNetMashup.Web/Views/Home/Index.cshtml @@ -0,0 +1,104 @@ +@{ + ViewData["Title"] = "Home Page"; +} + + + + diff --git a/src/DotNetMashup.Web/Views/Shared/Error.cshtml b/src/DotNetMashup.Web/Views/Shared/Error.cshtml new file mode 100644 index 0000000..4852442 --- /dev/null +++ b/src/DotNetMashup.Web/Views/Shared/Error.cshtml @@ -0,0 +1,6 @@ +@{ + ViewData["Title"] = "Error"; +} + +

Error.

+

An error occurred while processing your request.

diff --git a/src/DotNetMashup.Web/Views/Shared/_Layout.cshtml b/src/DotNetMashup.Web/Views/Shared/_Layout.cshtml new file mode 100644 index 0000000..e08b886 --- /dev/null +++ b/src/DotNetMashup.Web/Views/Shared/_Layout.cshtml @@ -0,0 +1,80 @@ + + + + + + @ViewData["Title"] - DotNetMashup.Web + + + + + + + + + + + + + + +
+ @RenderBody() +
+
+

© 2015 - DotNetMashup.Web

+
+
+ + + + + + + + + + + + + + + + + @RenderSection("scripts", required: false) + + diff --git a/src/DotNetMashup.Web/Views/_ViewImports.cshtml b/src/DotNetMashup.Web/Views/_ViewImports.cshtml new file mode 100644 index 0000000..568a7de --- /dev/null +++ b/src/DotNetMashup.Web/Views/_ViewImports.cshtml @@ -0,0 +1,2 @@ +@using DotNetMashup.Web +@addTagHelper "*, Microsoft.AspNet.Mvc.TagHelpers" diff --git a/src/DotNetMashup.Web/Views/_ViewStart.cshtml b/src/DotNetMashup.Web/Views/_ViewStart.cshtml new file mode 100644 index 0000000..a5f1004 --- /dev/null +++ b/src/DotNetMashup.Web/Views/_ViewStart.cshtml @@ -0,0 +1,3 @@ +@{ + Layout = "_Layout"; +} diff --git a/src/DotNetMashup.Web/blogfeed.json b/src/DotNetMashup.Web/blogfeed.json new file mode 100644 index 0000000..1001e49 --- /dev/null +++ b/src/DotNetMashup.Web/blogfeed.json @@ -0,0 +1,9 @@ +[ + { + "id": "tparnell", + "feedurl": "https://blog.tommyparnell.com/rss/", + "author": "Tommy Parnell", + "authoremail": "tparnell8@gmail.com", + "gravatarurl": "http://www.gravatar.com/avatar/333e3cea32cd17ff2007d131df336061.png?s=128" + } +] \ No newline at end of file diff --git a/src/DotNetMashup.Web/bower.json b/src/DotNetMashup.Web/bower.json new file mode 100644 index 0000000..51002ed --- /dev/null +++ b/src/DotNetMashup.Web/bower.json @@ -0,0 +1,12 @@ +{ + "name": "ASP.NET", + "private": true, + "dependencies": { + "bootstrap": "3.0.0", + "bootstrap-touch-carousel": "0.8.0", + "hammer.js": "2.0.4", + "jquery": "2.1.4", + "jquery-validation": "1.11.1", + "jquery-validation-unobtrusive": "3.2.2" + } +} diff --git a/src/DotNetMashup.Web/config.json b/src/DotNetMashup.Web/config.json new file mode 100644 index 0000000..6e86a34 --- /dev/null +++ b/src/DotNetMashup.Web/config.json @@ -0,0 +1,7 @@ +{ + "Data": { + "DefaultConnection": { + "ConnectionString": "Server=(localdb)\\mssqllocaldb;Database=DotNetMashup;Trusted_Connection=True;MultipleActiveResultSets=true" + } + } +} \ No newline at end of file diff --git a/src/DotNetMashup.Web/gulpfile.js b/src/DotNetMashup.Web/gulpfile.js new file mode 100644 index 0000000..684c5ac --- /dev/null +++ b/src/DotNetMashup.Web/gulpfile.js @@ -0,0 +1,45 @@ +/// + +var gulp = require("gulp"), + rimraf = require("rimraf"), + concat = require("gulp-concat"), + cssmin = require("gulp-cssmin"), + uglify = require("gulp-uglify"), + project = require("./project.json"); + +var paths = { + webroot: "./" + project.webroot + "/" +}; + +paths.js = paths.webroot + "js/**/*.js"; +paths.minJs = paths.webroot + "js/**/*.min.js"; +paths.css = paths.webroot + "css/**/*.css"; +paths.minCss = paths.webroot + "css/**/*.min.css"; +paths.concatJsDest = paths.webroot + "js/site.min.js"; +paths.concatCssDest = paths.webroot + "css/site.min.css"; + +gulp.task("clean:js", function (cb) { + rimraf(paths.concatJsDest, cb); +}); + +gulp.task("clean:css", function (cb) { + rimraf(paths.concatCssDest, cb); +}); + +gulp.task("clean", ["clean:js", "clean:css"]); + +gulp.task("min:js", function () { + gulp.src([paths.js, "!" + paths.minJs], { base: "." }) + .pipe(concat(paths.concatJsDest)) + .pipe(uglify()) + .pipe(gulp.dest(".")); +}); + +gulp.task("min:css", function () { + gulp.src([paths.css, "!" + paths.minCss]) + .pipe(concat(paths.concatCssDest)) + .pipe(cssmin()) + .pipe(gulp.dest(".")); +}); + +gulp.task("min", ["min:js", "min:css"]); diff --git a/src/DotNetMashup.Web/hosting.ini b/src/DotNetMashup.Web/hosting.ini new file mode 100644 index 0000000..126132d --- /dev/null +++ b/src/DotNetMashup.Web/hosting.ini @@ -0,0 +1,2 @@ +server=Microsoft.AspNet.Server.WebListener +server.urls=http://localhost:5000 diff --git a/src/DotNetMashup.Web/package.json b/src/DotNetMashup.Web/package.json new file mode 100644 index 0000000..d4d71a9 --- /dev/null +++ b/src/DotNetMashup.Web/package.json @@ -0,0 +1,11 @@ +{ + "name": "ASP.NET", + "version": "0.0.0", + "devDependencies": { + "gulp": "3.8.11", + "gulp-concat": "2.5.2", + "gulp-cssmin": "0.1.7", + "gulp-uglify": "1.2.0", + "rimraf": "2.2.8" + } +} diff --git a/src/DotNetMashup.Web/project.json b/src/DotNetMashup.Web/project.json new file mode 100644 index 0000000..eadeae2 --- /dev/null +++ b/src/DotNetMashup.Web/project.json @@ -0,0 +1,48 @@ +{ + "webroot": "wwwroot", + "version": "1.0.0-*", + + "dependencies": { + "Microsoft.AspNet.Diagnostics": "1.0.0-beta7", + "Microsoft.AspNet.Mvc": "6.0.0-beta7", + "Microsoft.AspNet.Mvc.TagHelpers": "6.0.0-beta7", + "Microsoft.AspNet.Server.IIS": "1.0.0-beta7", + "Microsoft.AspNet.Server.WebListener": "1.0.0-beta7", + "Microsoft.AspNet.StaticFiles": "1.0.0-beta7", + "Microsoft.AspNet.Tooling.Razor": "1.0.0-beta7", + "Microsoft.Framework.Configuration.Json": "1.0.0-beta7", + "Microsoft.Framework.Logging": "1.0.0-beta7", + "Microsoft.Framework.Logging.Console": "1.0.0-beta7", + "Microsoft.Framework.Logging.Debug": "1.0.0-beta7", + "Microsoft.VisualStudio.Web.BrowserLink.Loader": "14.0.0-beta7", + "Newtonsoft.Json": "7.0.1" + }, + + "commands": { + "web": "Microsoft.AspNet.Hosting --config hosting.ini" + }, + + "frameworks": { + "dnx451": { + "frameworkAssemblies": { + "System.ServiceModel": "4.0.0.0" + } + } + }, + + "exclude": [ + "wwwroot", + "node_modules", + "bower_components" + ], + "publishExclude": [ + "node_modules", + "bower_components", + "**.xproj", + "**.user", + "**.vspscc" + ], + "scripts": { + "prepublish": [ "npm install", "bower install", "gulp clean", "gulp min" ] + } +} \ No newline at end of file diff --git a/src/DotNetMashup/DotNetMashup.xproj b/src/DotNetMashup/DotNetMashup.xproj new file mode 100644 index 0000000..472f429 --- /dev/null +++ b/src/DotNetMashup/DotNetMashup.xproj @@ -0,0 +1,20 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + + e68542ee-79d4-47a1-bc66-bfb4b78856a1 + DotNetMashup + ..\..\artifacts\obj\$(MSBuildProjectName) + ..\..\artifacts\bin\$(MSBuildProjectName)\ + + + + 2.0 + + + diff --git a/src/DotNetMashup/Extensions/StringHtmlExtension.cs b/src/DotNetMashup/Extensions/StringHtmlExtension.cs new file mode 100644 index 0000000..4e4b5ac --- /dev/null +++ b/src/DotNetMashup/Extensions/StringHtmlExtension.cs @@ -0,0 +1,169 @@ +//This code came from https://raw.githubusercontent.com/robvolk/Helpers.Net/master/Src/Helpers.Net/StringHtmlExtensions.cs + +namespace DotNetMashup.Extensions +{ + using System.Collections.Generic; + using System.Text; + using System.Text.RegularExpressions; + + public static class StringHtmlExtensions + { + /// + /// Truncates a string containing HTML to a number of text characters, keeping whole words. + /// The result contains HTML and any tags left open are closed. + /// + /// + /// + public static string TruncateHtml(this string html, int maxCharacters, string trailingText) + { + if(string.IsNullOrEmpty(html)) + return html; + + // find the spot to truncate + // count the text characters and ignore tags + var textCount = 0; + var charCount = 0; + var ignore = false; + foreach(char c in html) + { + charCount++; + if(c == '<') + ignore = true; + else if(!ignore) + textCount++; + + if(c == '>') + ignore = false; + + // stop once we hit the limit + if(textCount >= maxCharacters) + break; + } + + // Truncate the html and keep whole words only + var trunc = new StringBuilder(html.TruncateWords(charCount)); + + // keep track of open tags and close any tags left open + var tags = new Stack(); + var matches = Regex.Matches(trunc.ToString(), + @"<((?[^\s/>]+)|/(?[^\s>]+)).*?(?/)?\s*>", + RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.Multiline); + + foreach(Match match in matches) + { + if(match.Success) + { + var tag = match.Groups["tag"].Value; + var closeTag = match.Groups["closeTag"].Value; + + // push to stack if open tag and ignore it if it is self-closing, i.e.
+ if(!string.IsNullOrEmpty(tag) && string.IsNullOrEmpty(match.Groups["selfClose"].Value)) + tags.Push(tag); + + // pop from stack if close tag + else if(!string.IsNullOrEmpty(closeTag)) + { + // pop the tag to close it.. find the matching opening tag + // ignore any unclosed tags + while(tags.Pop() != closeTag && tags.Count > 0) + { } + } + } + } + + if(html.Length > charCount) + // add the trailing text + trunc.Append(trailingText); + + // pop the rest off the stack to close remainder of tags + while(tags.Count > 0) + { + trunc.Append("'); + } + + return trunc.ToString(); + } + + /// + /// Truncates a string containing HTML to a number of text characters, keeping whole words. + /// The result contains HTML and any tags left open are closed. + /// + /// + /// + public static string TruncateHtml(this string html, int maxCharacters) + { + return html.TruncateHtml(maxCharacters, null); + } + + /// + /// Strips all HTML tags from a string + /// + /// + /// + public static string StripHtml(this string html) + { + if(string.IsNullOrEmpty(html)) + return html; + + return Regex.Replace(html, @"<(.|\n)*?>", string.Empty); + } + + /// + /// Truncates text to a number of characters + /// + /// + /// + /// + /// + public static string Truncate(this string text, int maxCharacters) + { + return text.Truncate(maxCharacters, null); + } + + /// + /// Truncates text to a number of characters and adds trailing text, i.e. elipses, to the end + /// + /// + /// + /// + /// + public static string Truncate(this string text, int maxCharacters, string trailingText) + { + if(string.IsNullOrEmpty(text) || maxCharacters <= 0 || text.Length <= maxCharacters) + return text; + else + return text.Substring(0, maxCharacters) + trailingText; + } + + /// + /// Truncates text and discars any partial words left at the end + /// + /// + /// + /// + /// + public static string TruncateWords(this string text, int maxCharacters) + { + return text.TruncateWords(maxCharacters, null); + } + + /// + /// Truncates text and discars any partial words left at the end + /// + /// + /// + /// + /// + public static string TruncateWords(this string text, int maxCharacters, string trailingText) + { + if(string.IsNullOrEmpty(text) || maxCharacters <= 0 || text.Length <= maxCharacters) + return text; + + // trunctate the text, then remove the partial word at the end + return Regex.Replace(text.Truncate(maxCharacters), + @"\s+[^\s]+$", string.Empty, RegexOptions.IgnoreCase | RegexOptions.Compiled) + trailingText; + } + } +} \ No newline at end of file diff --git a/src/DotNetMashup/Model/BlogPost.cs b/src/DotNetMashup/Model/BlogPost.cs new file mode 100644 index 0000000..bf74124 --- /dev/null +++ b/src/DotNetMashup/Model/BlogPost.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace DotNetMashup.Model +{ + public class BlogPost + { + public string Title { get; set; } + public string Content { get; set; } + public DateTime PublishedDate { get; set; } + public string Summary { get; set; } + public string Localink { get; set; } + public string OriginalLink { get; set; } + public string Author { get; set; } + public string AuthorEmail { get; set; } + } +} \ No newline at end of file diff --git a/src/DotNetMashup/Model/MetaData.cs b/src/DotNetMashup/Model/MetaData.cs new file mode 100644 index 0000000..e4eac7b --- /dev/null +++ b/src/DotNetMashup/Model/MetaData.cs @@ -0,0 +1,12 @@ +namespace DotNetMashup +{ + //stolen idea from: https://github.com/NancyFx/Nancy.Blog/blob/master/src/Nancy.Blog/Model/MetaData.cs + public class MetaData + { + public string FeedUrl { get; set; } + public string Author { get; set; } + public string AuthorEmail { get; set; } + public string GravatarUrl { get; set; } + public string Id { get; set; } + } +} \ No newline at end of file diff --git a/src/DotNetMashup/project.json b/src/DotNetMashup/project.json new file mode 100644 index 0000000..7e8089b --- /dev/null +++ b/src/DotNetMashup/project.json @@ -0,0 +1,22 @@ +{ + "version": "1.0.0-*", + "description": "DotNetMashup Class Library", + "authors": [ "Tommy Parnell" ], + "tags": [ "" ], + "projectUrl": "", + "licenseUrl": "", + + "frameworks": { + "dnx451": { }, + "dnxcore50": { + "dependencies": { + "Microsoft.CSharp": "4.0.1-beta-23225", + "System.Collections": "4.0.11-beta-23225", + "System.Linq": "4.0.1-beta-23225", + "System.Runtime": "4.0.21-beta-23225", + "System.Threading": "4.0.11-beta-23225", + "System.Text.RegularExpressions": "4.0.11-beta-23225" + } + } + } +} \ No newline at end of file