前言:
步骤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:截图。