使用Markdown和ASP.NET MVC3创建基于文本的博客

前言:

步骤1:创建初始的Blog应用

本次示例使用Phil Haack's Really Empty MVC Project Template,添加文件夹:App_Data/BlogPosts,Content/BlogPostImages;添加Controller: HomeController;以及相关联的View Views/Home/Index.cshtml;和其他Shared Views,初始的CSS样式。完整的Solution结构和完成后的应用运行截图如下:

placeholder:截图1

步骤2:安装Markdown

Markdown是一个text-to-HTML转化工具,针对人群为网络写手,博主等。Markdown允许你用容易读/写的纯文本格式写文章,然后转变成结构正确的XHTML(或者HTML)。Markdown格式的文本设计目的是允许内容以as-is发布,像纯文本,不需要用tag标记等。Markdown是免费软件,遵循BSD-style开源协议license。

既然我们使用MVC 3(语言为C#),可以利用MarkdownSharp,也是一个开源的Markdown处理器的C#实现,在Stack Overflow上被featured。使用Nuget Package Manager console安装(添加一个单独文件MarkdownSharp.dll到Bin目录):

PM> Install-Package MarkdownSharp

步骤3:Blog Post和Summary Data命名约定

Blog post是.txt文件,存储在文件夹AppData/BlogPosts。本次示例,我们对文件blog post和文件blog summary使用如下的命名约定,使用字符分割日期和名称信息:

YYYY-MM-DD[blog-post-name-separated-by-hyphens].txt // blog post markdown syntax content
YYYY-MM-DD
[blog-post-name-separated-by-hyphens]_summary.txt // JSON formatted blog summary info

使用两个文件的目的是将Markdown的内容和Blog Post summary。Markdown的目标就是内容的as-is,无须任何额外的标记tag或者指令。YYYY-MM-DD用来表示博客发布时间,[]内为博客标题,使用-字符分开单词。Summary文件使用JSON语法现实blog summary。

步骤4:创建File System Data Model

首先,创建model Models/BlogListing.cs,用来表征创建博客需要的数据。

namespace TxtBasedBlog.Sample.Models { public class BlogListing { public string Url { get; set; } public string Title { get; set; } public string ShortDescription { get; set; } public string Content { get; set; } public string Author { get; set; } public DateTime PostDate { get; set; } public string Image { get; set; } } }

然后,创建model Models/BlogPost.cs负责加载博客文章内容。

namespace TxtBasedBlog.Sample.Models { public class BlogPost : BlogListing { public string Body { get; set; } } }

步骤5:写一个Blog Post示例

遵循上述命名约定,我们创建两个示例文件。Summary文件使用JSON语法,完整的blog post使用Markdown语法。 

{ Title: "ASP.NET MVC Overview", Url: "asp_net_mvc_overview", PostDate: "2012-02-09", Author: "Microsoft ASP.NET Team", ShortDescription: "ASP.NET MVC gives you a powerful, patterns-based way to build dynamic websites that enables a clean separation of concerns and that gives you full control over markup for enjoyable, agile development.", Image: "content/blogpostimages/image001.jpg" }

**ASP.NET MVC** gives you a powerful, patterns-based way to build dynamic websites that enables a clean separation of concerns and that gives you full control over markup for enjoyable, agile development. MVC includes many features that enable: * fast, TDD-friendly development for creating sophisticated applications * use the latest web standards. [Learn More About MVC](http://www.asp.net/mvc "Learn more about MVC today!")

步骤6:显示博客列表

在web.config中的appSetting键值中添加BlogPostsDirectory配置,这样可以无需编译修改数据目录。

 

<appSettings> ... <add key="BlogPostsDirectory" value="~/App_Data/BlogPosts"/> ... </appSettings>

 

Models/BlogFileSystemManager.cs用来检查上面配置的数据目录,然后返回博客列表。

 

namespace TxtBasedBlog.Sample.Models { public class BlogFileSystemManager { private string filePathToBlogPosts; public BlogFileSystemManager(string dirPath) { filePathToBlogPosts = dirPath; } public List<BlogListing> GetBlogListings(int limit) { var allFileNames = getBlogPostsFiles(); var blogListings = new List<BlogListing>(); foreach (var fileName in allFileNames.OrderByDescending(i => i).Take(limit)) { var fileData = File.ReadAllText(fileName); var blogListing = new JavaScriptSerializer().Deserialize<BlogListing>(fileData); blogListings.Add(blogListing); } return blogListings; } private IEnumerable<string> getBlogPostsFiles() { return Directory.GetFiles(filePathToBlogPosts, "*summary.txt").ToList(); } } }

 

HomeController 加载BlogListing ,在View中渲染。

 

namespace TxtBasedBlog.Sample.Controllers { public class HomeController : Controller { public ActionResult Index() { var manager = new BlogFileSystemManager(Server.MapPath(ConfigurationManager.AppSettings["BlogPostsDirectory"])); var model = manager.GetBlogListings(5); return View(model); } public ActionResult Error() { return View(); } } }

 

最后,是Views/Home/Index.cshtml的代码,注意我们使用的root级的URL,需要在下步定义一些Routes。

 

@model IEnumerable<TxtBasedBlog.Sample.Models.BlogListing> <h2>Recent Blog Posts</h2> @{ foreach (var item in Model) { <div class="post"> <div class="img-post"> <a href="/@item.Url" title="@item.Title"><img src="../../@item.Image" alt="" /></a> </div> <div class="inline"> <p><a href="@item.Url">@item.PostDate.ToString("MM/dd/yyyy") - @item.Title</a><br />@Html.Raw(item.ShortDescription)</p> </div> </div> } }

 

现在,运行网站,如下图:

placeholder 截图

步骤7:显示博客内容

在Global.asax.cs定义一些Routes:

 

public static void RegisterRoutes(RouteCollection routes) { ... routes.MapRoute( "HomePage", "", new { controller = "Home", action = "Index" } ); routes.MapRoute( "Error", "Oops", new { controller = "Home", action = "Error" } ); routes.MapRoute( "BlogPost", "{postName}", new { controller = "Home", action = "ViewBlogPost", postName = "" } ); ... }

 

当用户点击一个博客链接时,调用Action方法ViewBlogPost ,在HomeController中添加ViewBlogPost 有关的代码。

namespace TxtBasedBlog.Sample.Controllers { public class HomeController : Controller { ... public ActionResult ViewBlogPost(string postName) { var manager = new BlogFileSystemManager(Server.MapPath(ConfigurationManager.AppSettings["BlogPostsDirectory"])); if (!manager.BlogPostFileExistsByTitleForUrl(postName)) { return RedirectToRoute("Error"); } var model = manager.GetBlogPostByTitleForUrl(postName); return View(model); } ... } }

添加BlogFileSystemManager 来确保获得有效的数据文件。

namespace TxtBasedBlog.Sample.Models { public class BlogFileSystemManager { ... public bool BlogPostFileExistsByTitleForUrl(string titleForUrl) { var matchingFiles = getFilesForBlogPostByTitleForUrl(titleForUrl); return (matchingFiles.Count == 2); } public BlogPost GetBlogPostByTitleForUrl(string titleForUrl) { var matchingFiles = getFilesForBlogPostByTitleForUrl(titleForUrl); var summaryFileData = File.ReadAllText(matchingFiles.Where(i => i.Contains("_summary")).FirstOrDefault()); var blogPost = new JavaScriptSerializer().Deserialize(summaryFileData); blogPost.Body = File.ReadAllText(matchingFiles.Where(i => !i.Contains("_summary")).FirstOrDefault()); return blogPost; } private List getFilesForBlogPostByTitleForUrl(string titleForUrl) { // Updated 2012-03-07:  // Richard Fawcett's regex suggestion to prevent titleForUrl subset results. Thanks Richard! var files = Directory.GetFiles(filePathToBlogPosts, string.Format("*{0}*.txt", titleForUrl)); var r = new Regex(@"\d{4}-\d{2}-\d{2}_" + titleForUrl + @"(_summary)?\.txt", RegexOptions.IgnoreCase); return files.Where(f => r.IsMatch(f)).ToList(); } ... } }

然后,Views/Home/ViewBlogPost.cshtml负责渲染博客文章页面,我们使用一个自定义的Helper方法,@Html.Markdown() 来调用之前安装的Markdown库。

@using TxtBasedBlog.Sample.Models @model TxtBasedBlog.Sample.Models.BlogPost @{ ViewBag.Title = Model.Title; } <h2>@Model.Title</h2> <p>Posted on @Convert.ToDateTime(Model.PostDate).ToString("dd MMM, yyyy") by @Model.Author - @Html.ActionLink("Back to Blog List", "Index")</p> @Html.Markdown(Model.Body)

@Html.Markdown()是由 Danny Tuppeny创建,原文见original post

 

namespace TxtBasedBlog.Sample.Models { public static class MarkdownHelper { static readonly Markdown MarkdownTransformer = new Markdown(); public static IHtmlString Markdown(this HtmlHelper helper, string text) { var html = MarkdownTransformer.Transform(text); return MvcHtmlString.Create(html); } } }

 

最终完成,运行截图如下,注意URL已经优化。

placeholder:截图。

你可能感兴趣的:(markdown)