一步一步创建ASP.NET MVC5程序[Repository+Autofac+Automapper+SqlSugar](九)

前言

童鞋们,大家好

我是专注.NET开发者社区建设的实践者Rector。

首先,为自己间隔了两个星期五再更新本系列文章找个不充分的理由:Rector最近工作,家庭的各种事务所致,希望大家谅解。

本文知识要点

回到本文的主题,还是关于系列文章:《一步一步创建ASP.NET MVC5程序Repository+Autofac+Automapper+SqlSugar》,本文将为大家分享的主要内容有:

  • 响应式网站首页的布局与制作
  • 文章列表的展示
  • 文章详情页面

前端布局与制作

响应式网站首页的布局与制作

在以本文之前的系列文章的页面中,我们的网站首页以及文章列表页面都没有应用样式,本文将给大家分享首页的制作,其中包含的内容有:

  • 头部导航
  • 文章列表
  • Bootstrap响应式布局

最终的首页效果图如下:

create-aspnet-mvc-5-web-application-repository-autofac-automapper-sqlsugar-step-by-step-09-01.png
create-aspnet-mvc-5-web-application-repository-autofac-automapper-sqlsugar-step-by-step-09-01.png

CSS样式

首先,在项目[TsBlog.Frontend]中创建资源文件夹命名为:resources,在其中创建一个css样式文件夹,并新建一个样式文件,命名为:site.css,此时的目录结构如下:

create-aspnet-mvc-5-web-application-repository-autofac-automapper-sqlsugar-step-by-step-09-02.png
create-aspnet-mvc-5-web-application-repository-autofac-automapper-sqlsugar-step-by-step-09-02.png

样式代码如下:

site.css

html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, font, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td { border: 0; margin: 0; padding: 0; }

body { color: #333; font-size: 14px; font-family: -apple-system,'helvetica neue', helvetica,"Helvetica Neue",Helvetica,Arial,"PingFang SC","Hiragino Sans GB","WenQuanYi Micro Hei","Microsoft Yahei",sans-serif; line-height: 22px; width: 100%; height: 100%; }
h1, h2, h3, h4, h5, h6 { clear: both; font-weight: normal; }
ol, ul { list-style: none; }
blockquote { quotes: none; border-left: 5px solid #eee; font-size: 14px; margin: 10px 0; padding: 10px 20px; }
    blockquote:before, blockquote:after { content: ''; content: none; }

a { color: #4484ce; }

/*bootstrap override*/
.btn { border-radius: 2px !important; }
.btn-primary { background-color: #4484CE !important; }
    .btn-primary:hover { background-color: #3A77BE !important; }

.navbar-nav a { color: #333 !important; }
    .navbar-nav > .active > a, .navbar-nav > .active > a:focus, .navbar-nav > .active > a:hover, .navbar-nav a:hover { border-radius: 2px; background-color: #e7e7e7 !important; }
/*site begin*/
.ts-navbar .navbar-nav { height: 50px; vertical-align: middle; line-height: 50px; }
    .ts-navbar .navbar-nav li { vertical-align: middle; line-height: 50px; float: none; display: inline-block; }
.ts-navbar .dropdown li { display: block; }
.ts-navbar .navbar-nav li a { padding: 8px 15px; }
.navbar-brand { height: auto; }
a.nav-btn-login { color: #fff !important; }
    a.nav-btn-login:hover { background-color: #3A77BE !important; color: #fff !important; }
.navbar-profile { margin-right: 0; }
/*home begin*/
.jumbotron h1 { margin-bottom: 15px; }
.jumbotron p { line-height: 28px; }
.post-title { display: block; font-size: 16px; font-weight: 600; border-bottom: 2px solid #e7e7e7; padding-bottom: 8px; }
.post-item-box { margin-bottom: 15px; }
    .post-item-box li { margin-top: 15px; margin-bottom: 15px; padding-top: 10px; padding-bottom: 10px; }
        .post-item-box li h2 { font-size: 16px; font-weight: 500; margin-bottom: 10px; }
.post-item-summary { color: #555; }
.footer-box { padding: 15px; margin-top: 15px; border-top: 1px solid #e7e7e7; }


/*post details*/

.article-content { padding-top: 15px; padding-bottom: 15px; }
    .article-content p { margin-top: 20px; margin-bottom: 20px; }
.article-fixed p { margin-top: 0; margin-bottom: 5px; }
.article-content h1, .article-content h2, .article-content h3, .article-content h4, .article-content h5, .article-content h6 { margin: 15px 0 10px; }
.article-content h1, .article-content h2 { border-bottom: 1px solid #eee; padding-bottom: 10px; }
.article-content h2 { font-size: 1.75em; line-height: 1.2 }
.article-content h3 { font-size: 1.5em; line-height: 1.2 }
.article-content blockquote { background: #f6f6f6 none repeat scroll 0 0; border-left: 2px solid #009a61; color: #555; font-size: 1em; }
.cloud-tags .cloud-tag-item { border: 1px solid #efefef; background-color: #f7f7f7; padding: 5px 10px; }
.cloud-tags .cloud-tag-item, .side-bar-article-list, .article-content { word-break: break-all; word-wrap: break-word; white-space: normal; }
    .article-content pre { background-attachment: scroll; background-clip: border-box; background-color: #f6f6f6; border: medium none; line-height: 1.45; max-height: 35em; overflow: auto; padding: 1em; position: relative; margin-bottom: 15px; margin-top: 15px; }
    .article-content ul li { padding-left: 15px; list-style: inside; }
    .article-content ul li { padding-left: 15px; list-style: inside; }
    .article-content ul, .article-content ol { margin-left: 3em; padding-left: 0; }
        .article-content ul li, .article-content ol li { margin: .3em 0; }

以上的样式表是本文中所用到的,你只需要复制即可。

头部导航

打开视图文件[...TsBlog\src\Presentation\TsBlog.Frontend\Views\Home\Index.cshtml],首先制作头部导航条,其中导航条的HTML代码如下:

<nav class="navbar navbar-default navbar-static-top ts-navbar">
        <div class="container">
            <div class="navbar-header">
                <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
                    <span class="sr-only">Toggle navigation</span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                </button>
                <a class="navbar-brand" href="~/">TSBLOG</a>
            </div>
            <div id="navbar" class="navbar-collapse collapse">
                <ul class="nav navbar-nav">
                    <li class="active"><a href="~/">网站首页</a></li>
                    <li class="dropdown">
                        <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">分类导航 <span class="caret"></span></a>
                        <ul class="dropdown-menu">
                            <li class="dropdown-header">后端开发</li>
                            <li><a href="http://2sharings.com/category/csharp-development">C#程序设计</a></li>
                            <li><a href="http://2sharings.com/category/dot-net">.NET程序设计</a></li>
                            <li><a href="http://2sharings.com/category/asp-dot-net">ASP.NET</a></li>
                            <li><a href="http://2sharings.com/category/asp-net-mvc">ASP.NET MVC</a></li>
                            <li><a href="http://2sharings.com/category/asp-dotnet-core">ASP.NET Core</a></li>
                            <li><a href="http://2sharings.com/category/winform">Winform</a></li>
                            <li role="separator" class="divider"></li>
                            <li class="dropdown-header">数据库</li>
                            <li><a href="http://2sharings.com/category/mysql">MySQL</a></li>
                            <li><a href="http://2sharings.com/category/sql-server">SQL Server</a></li>
                            <li><a href="http://2sharings.com/category/sqlite">SqLite</a></li>
                        </ul>
                    </li>
                    <li><a href="~/home/about">关于我们</a></li>
                    <li><a href="~/home/contact">联系我们</a></li>
                </ul>
                <ul class="nav navbar-nav navbar-right navbar-profile">
                    <li><a href="~/account/register">免费注册</a></li>
                    <li><a class="btn btn-primary nav-btn-login" href="~/account/login">立即登录</a></li>
                </ul>
            </div><!--/.nav-collapse -->
        </div>
    </nav>

正文HTML

其中正文的第一部分为一个BANNER,在这个区域中,可以放置一些重要的关于站点的描述信息,也可以放滚动播放的广告图片等,按自己的需要处理就可以了。

第二部分则是一个文章列表区域,其中列出了网站最近发布的20条文章列表,正文的HTML代码如下:

<div class="container">
        <div class="jumbotron">
            <h1>小伙伴,你好</h1>
            <p>欢迎来到 Rector 的ASP.NET MVC 5 系列文章教程。在这里,Rector将和你一起一步一步创建一个集成Repository+Autofac+Automapper+SqlSugar的WEB应用程序。</p>
            <p>你准备好了吗?</p>
            <p>......</p>
            <p>让我们开始ASP.NET MVC 5 应用程序的探索之旅吧!!!</p>
        </div>
        <strong class="post-title">文章列表(@(Model.Count())篇)</strong>
        <ul class="list-unstyled post-item-box">
            @foreach (var p in Model)
            {
                <li>
                    <h2><a href="~/post/details/@p.Id">@p.Title</a></h2>
                    <p class="post-item-summary">@p.Summary ... <a href="~/post/details/@p.Id">阅读全文</a></p>
                </li>
            }
        </ul>
    </div>

页脚

页面最后为页脚部分,包含比较简单的版权等信息,HTML代码如下:

<footer class="footer-box">
        <div class="container">
            版权所有 &copy; @(DateTime.Now.Year)
        </div>
    </footer>

首页完整的HTML代码如下:

Index.cshtml

@model IEnumerable<TsBlog.ViewModel.Post.PostViewModel>
@{
    Layout = null;
}
<!doctype html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>ASP.NET MVC 5 系列文章教程--首页 | TSBLOG</title>
    <link href="~/Content/bootstrap.min.css" rel="stylesheet" />
    <link href="~/resources/css/site.css" rel="stylesheet" />

</head>
<body>
    <nav class="navbar navbar-default navbar-static-top ts-navbar">
        <div class="container">
            <div class="navbar-header">
                <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
                    <span class="sr-only">Toggle navigation</span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                </button>
                <a class="navbar-brand" href="~/">TSBLOG</a>
            </div>
            <div id="navbar" class="navbar-collapse collapse">
                <ul class="nav navbar-nav">
                    <li class="active"><a href="~/">网站首页</a></li>
                    <li class="dropdown">
                        <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">分类导航 <span class="caret"></span></a>
                        <ul class="dropdown-menu">
                            <li class="dropdown-header">后端开发</li>
                            <li><a href="http://2sharings.com/category/csharp-development">C#程序设计</a></li>
                            <li><a href="http://2sharings.com/category/dot-net">.NET程序设计</a></li>
                            <li><a href="http://2sharings.com/category/asp-dot-net">ASP.NET</a></li>
                            <li><a href="http://2sharings.com/category/asp-net-mvc">ASP.NET MVC</a></li>
                            <li><a href="http://2sharings.com/category/asp-dotnet-core">ASP.NET Core</a></li>
                            <li><a href="http://2sharings.com/category/winform">Winform</a></li>
                            <li role="separator" class="divider"></li>
                            <li class="dropdown-header">数据库</li>
                            <li><a href="http://2sharings.com/category/mysql">MySQL</a></li>
                            <li><a href="http://2sharings.com/category/sql-server">SQL Server</a></li>
                            <li><a href="http://2sharings.com/category/sqlite">SqLite</a></li>
                        </ul>
                    </li>
                    <li><a href="~/home/about">关于我们</a></li>
                    <li><a href="~/home/contact">联系我们</a></li>
                </ul>
                <ul class="nav navbar-nav navbar-right navbar-profile">
                    <li><a href="~/account/register">免费注册</a></li>
                    <li><a class="btn btn-primary nav-btn-login" href="~/account/login">立即登录</a></li>
                </ul>
            </div><!--/.nav-collapse -->
        </div>
    </nav>
    <div class="container">
        <div class="jumbotron">
            <h1>小伙伴,你好</h1>
            <p>欢迎来到 Rector 的ASP.NET MVC 5 系列文章教程。在这里,Rector将和你一起一步一步创建一个集成Repository+Autofac+Automapper+SqlSugar的WEB应用程序。</p>
            <p>你准备好了吗?</p>
            <p>......</p>
            <p>让我们开始ASP.NET MVC 5 应用程序的探索之旅吧!!!</p>
        </div>
        <strong class="post-title">文章列表(@(Model.Count())篇)</strong>
        <ul class="list-unstyled post-item-box">
            @foreach (var p in Model)
            {
                <li>
                    <h2><a href="~/post/details/@p.Id">@p.Title</a></h2>
                    <p class="post-item-summary">@p.Summary ... <a href="~/post/details/@p.Id">阅读全文</a></p>
                </li>
            }
        </ul>
    </div>
    <footer class="footer-box">
        <div class="container">
            版权所有 &copy; @(DateTime.Now.Year)
        </div>
    </footer>
    <script src="~/Scripts/jquery-3.2.1.min.js"></script>
    <script src="~/Scripts/bootstrap.min.js"></script>
</body>
</html>

后端接口与实现

在完成了前端页面的布局与制作之后,我们需要后端程序提供接口和服务,来供前端页面调用,如首页视图中的视图模型:

@model IEnumerable<TsBlog.ViewModel.Post.PostViewModel>

文章仓储接口和实现

打开文件[IPostRepository.cs],在其中新增接口方法: FindHomePagePosts ,代码如下:

using System.Collections.Generic;
using TsBlog.Domain.Entities;

namespace TsBlog.Repositories
{
    public interface IPostRepository : IRepository<Post>
    {
        /// <summary>
        /// 查询首页文章列表
        /// </summary>
        /// <param name="limit">要查询的记录数</param>
        /// <returns></returns>
        IEnumerable<Post> FindHomePagePosts(int limit = 20);
    }
}

打开文件[PostRepository.cs],实现对应的接口方法:FindHomePagePosts,代码如下:

using SqlSugar;
using System.Collections.Generic;
using TsBlog.Domain.Entities;

namespace TsBlog.Repositories
{
    /// <summary>
    /// POST表的数据库操作类
    /// </summary>
    public class PostRepository : GenericRepository<Post>, IPostRepository
    {
        #region Implementation of IPostRepository

        /// <summary>
        /// 查询首页文章列表
        /// </summary>
        /// <param name="limit">要查询的记录数</param>
        /// <returns></returns>
        public IEnumerable<Post> FindHomePagePosts(int limit = 20)
        {
            using (var db = DbFactory.GetSqlSugarClient())
            {
                var list = db.Queryable<Post>().OrderBy(x => x.Id, OrderByType.Desc).Take(limit).ToList();
                return list;
            }
        }
    }
    #endregion
}

文章服务接口和实现

打开文件[IPostService.cs],在其中新增接口方法: FindHomePagePosts ,代码如下:

using System.Collections.Generic;
using TsBlog.Domain.Entities;

namespace TsBlog.Services
{
    public interface IPostService : IService<Post>
    {
        /// <summary>
        /// 查询首页文章列表
        /// </summary>
        /// <param name="limit">要查询的记录数</param>
        /// <returns></returns>
        IEnumerable<Post> FindHomePagePosts(int limit = 20);
    }
}

打开文件[PostService.cs],实现对应的接口方法:FindHomePagePosts,代码如下:

using System.Collections.Generic;
using TsBlog.Domain.Entities;
using TsBlog.Repositories;

namespace TsBlog.Services
{
    public class PostService : GenericService<Post>, IPostService
    {
        private readonly IPostRepository _repository;
        public PostService(IPostRepository repository) : base(repository)
        {
            _repository = repository;
        }


        #region Implementation of IPostService

        /// <summary>
        /// 查询首页文章列表
        /// </summary>
        /// <param name="limit">要查询的记录数</param>
        /// <returns></returns>
        public IEnumerable<Post> FindHomePagePosts(int limit = 20)
        {
            return _repository.FindHomePagePosts(limit);
        }

        #endregion
    }
}

附加修改:重构了一下仓储接口中的 FindListByClause 方法,将orderBy参数设置为可空参数,具体实现如下:

/// <summary>
/// 根据条件查询数据
/// </summary>
/// <param name="predicate">条件表达式树</param>
/// <param name="orderBy">排序</param>
/// <returns>泛型实体集合</returns>
IEnumerable<T> FindListByClause(Expression<Func<T, bool>> predicate, string orderBy = "");

对应的修改泛型仓储中的对应实现:

/// <summary>
/// 根据条件查询数据
/// </summary>
/// <param name="predicate">条件表达式树</param>
/// <param name="orderBy">排序</param>
/// <returns>泛型实体集合</returns>
public IEnumerable<T> FindListByClause(Expression<Func<T, bool>> predicate, string orderBy = "")
{
    using (var db = DbFactory.GetSqlSugarClient())
    {
        var query = db.Queryable<T>().Where(predicate);
        if (!string.IsNullOrEmpty(orderBy))
        {
            query = query.OrderBy(orderBy);
        }
        var entities = query.ToList();
        return entities;
    }
}

同样的,服务层中也作相应的修改:

IService.cs 文件中的 FindListByClause接口方法:

/// <summary>
/// 根据条件查询数据
/// </summary>
/// <param name="predicate">条件表达式树</param>
/// <param name="orderBy">排序</param>
/// <returns>泛型实体集合</returns>
IEnumerable<T> FindListByClause(Expression<Func<T, bool>> predicate, string orderBy = "");

泛型服务类:GenericService.cs 中的 FindListByClause 方法实现:

/// <summary>
/// 根据条件查询数据
/// </summary>
/// <param name="predicate">条件表达式树</param>
/// <param name="orderBy">排序</param>
/// <returns>泛型实体集合</returns>
public IEnumerable<T> FindListByClause(Expression<Func<T, bool>> predicate, string orderBy = "")
{
    return _repository.FindListByClause(predicate, orderBy);
}

在开始处理HomeController控制器之前 ,我们先在项目[TsBlog.Core]中新建两个帮助类,分别为:HtmlHelper.csStringHelper.cs。其中代码分别为:

HtmlHelper.cs:

using System.Text.RegularExpressions;

namespace TsBlog.Core
{
    public static class HtmlHelper
    {
        #region 去掉HTML中的所有标签,只留下纯文本
        /// <summary>
        /// 去掉HTML中的所有标签,只留下纯文本
        /// </summary>
        /// <param name="strHtml"></param>
        /// <returns></returns>
        public static string CleanHtml(this string strHtml)
        {
            if (string.IsNullOrEmpty(strHtml)) return strHtml;
            //删除脚本
            strHtml = Regex.Replace(strHtml, "(\\<script(.+?)\\</script\\>)|(\\<style(.+?)\\</style\\>)", "", RegexOptions.IgnoreCase | RegexOptions.Singleline);
            //删除标签
            var r = new Regex(@"<\/?[^>]*>", RegexOptions.IgnoreCase);
            Match m;
            for (m = r.Match(strHtml); m.Success; m = m.NextMatch())
            {
                strHtml = strHtml.Replace(m.Groups[0].ToString(), "");
            }
            return strHtml.Trim();
        }

        #endregion
    }
}

StringHelper.cs

using System;

namespace TsBlog.Core
{
    public static class StringHelper
    {
        #region
        /// <summary>
        /// 截取指定长度的字符串
        /// </summary>
        /// <param name="str">原始字符串</param>
        /// <param name="strLength">要保留的字符串长度</param>
        /// <returns></returns>
        public static string CutStrLength(this string str, int strLength)
        {
            var strNew = str;
            if (string.IsNullOrEmpty(strNew)) return strNew;
            var strOriginalLength = strNew.Length;
            if (strOriginalLength > strLength)
            {
                strNew = strNew.Substring(0, strLength) + "...";
            }
            return strNew;
        }

        #endregion

        #region
        /// <summary>
        /// 截取指定长度的字符串
        /// </summary>
        /// <param name="str">原始字符串</param>
        /// <param name="strLength">要保留的字符串长度</param>
        /// <param name="endWithEllipsis">是或以省略号(...)结束</param>
        /// <returns></returns>
        public static string CutStrLength(string str, int strLength, bool endWithEllipsis)
        {
            string strNew = str;
            if (!strNew.Equals(""))
            {
                int strOriginalLength = strNew.Length;
                if (strOriginalLength > strLength)
                {
                    strNew = strNew.Substring(0, strLength);
                    if (endWithEllipsis)
                    {
                        strNew += "...";
                    }
                }
            }
            return strNew;
        }

        #endregion

        #region 截断字符串(可保留完整单词)
        /// <summary>
        /// 截断字符串(可保留完整单词)
        /// </summary>
        /// <param name="valueToTruncate">需处理的字符串</param>
        /// <param name="maxLength">字符数</param>
        /// <param name="options">截断选项</param>
        /// <returns></returns>
        public static string TruncateString(this string valueToTruncate, int maxLength, TruncateOptions options)
        {
            if (valueToTruncate == null)
            {
                return "";
            }

            if (valueToTruncate.Length <= maxLength)
            {
                return valueToTruncate;
            }

            var includeEllipsis = (options & TruncateOptions.IncludeEllipsis) ==
                    TruncateOptions.IncludeEllipsis;
            var finishWord = (options & TruncateOptions.FinishWord) ==
                    TruncateOptions.FinishWord;
            var allowLastWordOverflow =
              (options & TruncateOptions.AllowLastWordToGoOverMaxLength) ==
              TruncateOptions.AllowLastWordToGoOverMaxLength;

            var retValue = valueToTruncate;

            if (includeEllipsis)
            {
                maxLength -= 1;
            }

            var lastSpaceIndex = retValue.LastIndexOf(" ",
              maxLength, StringComparison.CurrentCultureIgnoreCase);

            if (!finishWord)
            {
                retValue = retValue.Remove(maxLength);
            }
            else if (allowLastWordOverflow)
            {
                var spaceIndex = retValue.IndexOf(" ",
                  maxLength, StringComparison.CurrentCultureIgnoreCase);
                if (spaceIndex != -1)
                {
                    retValue = retValue.Remove(spaceIndex);
                }
            }
            else if (lastSpaceIndex > -1)
            {
                retValue = retValue.Remove(lastSpaceIndex);
            }

            if (includeEllipsis && retValue.Length < valueToTruncate.Length)
            {
                retValue += "...";
            }
            return retValue;
        }

        #endregion
    }

    #region 截断字符串用的枚举
    /// <summary>
    /// 截断字符串用的枚举
    /// </summary>
    [Flags]
    public enum TruncateOptions
    {
        /// <summary>
        /// 不作任何处理
        /// </summary>
        None = 0x0,
        /// <summary>
        /// 保留完整单词
        /// </summary>
        FinishWord = 0x1,
        /// <summary>
        /// 允许最后一个单词超过最大长度限制
        /// </summary>
        AllowLastWordToGoOverMaxLength = 0x2,
        /// <summary>
        /// 字符串最后跟省略号
        /// </summary>
        IncludeEllipsis = 0x4
    }
    #endregion
}

在项[TsBlog.ViewModel]中的文章视图文件[...TsBlog\src\Libraries\TsBlog.ViewModel\Post\PostViewModel.cs]中添加一个新的属性:Summary,此时的PostViewModel是这样的:

namespace TsBlog.ViewModel.Post
{
    /// <summary>
    /// 博文视图实体类
    /// </summary>
    public class PostViewModel
    {
        /// <summary>
        /// ID
        /// </summary>
        public int Id { get; set; }
        /// <summary>
        /// 标题
        /// </summary>
        public string Title { get; set; }
        /// <summary>
        /// 内容
        /// </summary>
        public string Content { get; set; }
        /// <summary>
        /// 作者ID
        /// </summary>
        public string AuthorId { get; set; }
        /// <summary>
        /// 作者姓名
        /// </summary>
        public string AuthorName { get; set; }
        /// <summary>
        /// 创建时间
        /// </summary>
        public string CreatedAt { get; set; }
        /// <summary>
        /// 发布时间
        /// </summary>
        public string PublishedAt { get; set; }
        /// <summary>
        /// 是否标识已删除
        /// </summary>
        public string IsDeleted { get; set; }
        /// <summary>
        /// 是否允许展示
        /// </summary>
        public bool AllowShow { get; set; }
        /// <summary>
        /// 浏览量
        /// </summary>
        public int ViewCount { get; set; }

        /// <summary>
        /// 摘要
        /// </summary>
        public string Summary { get; set; }
    }
}

在项目[TsBlog.Frontend]中创建一个名为:Extensions 文件夹,并在其中创建一个文章的静态扩展类[...\TsBlog.Frontend\Extensions\PostExtension.cs],同时实现以下静态扩展方法:

using TsBlog.Core;
using TsBlog.ViewModel.Post;

namespace TsBlog.Frontend.Extensions
{
    public static class PostExtension
    {
        /// <summary>
        /// 格式化文章的视图实体
        /// </summary>
        /// <param name="model">文章视图实体类</param>
        /// <returns></returns>
        public static PostViewModel FormatPostViewModel(this PostViewModel model)
        {
            if (model == null)
            {
                return null;
            }

            model.Summary = model.Content
                .CleanHtml()            //去掉所有HTML标签
                .TruncateString(200, TruncateOptions.FinishWord | TruncateOptions.AllowLastWordToGoOverMaxLength);     //截断指定长度作为文章摘要
            return model;
        }
    }
}

网站首页[HomeController]

在首页的控制器[...TsBlog.Frontend\Controllers\HomeController.cs]中,利用文章服务接口的方法实现首页文章列表的查询,代码如下:

using System.Linq;
using System.Web.Mvc;
using TsBlog.AutoMapperConfig;
using TsBlog.Frontend.Extensions;
using TsBlog.Services;

namespace TsBlog.Frontend.Controllers
{
    public class HomeController : Controller
    {
        /// <summary>
        /// 文章服务接口
        /// </summary>
        private readonly IPostService _postService;
        public HomeController(IPostService postService)
        {
            _postService = postService;
        }
        /// <summary>
        /// 首页
        /// </summary>
        /// <returns></returns>
        public ActionResult Index()
        {
            var list = _postService.FindHomePagePosts();
            var model = list.Select(x => x.ToModel().FormatPostViewModel());
            return View(model);
        }
    }
}

好了,到此我们的首页制作与数据绑定等到完成了,按F5运行,我们即可看到本文开篇所示的首页效果。

文章详情页[PostController]

新建一个名为:PostController的控制器,并添加如下代码:

using System.Web.Mvc;
using TsBlog.AutoMapperConfig;
using TsBlog.Services;

namespace TsBlog.Frontend.Controllers
{
    public class PostController : Controller
    {
        /// <summary>
        /// 文章服务接口
        /// </summary>
        private readonly IPostService _postService;

        public PostController(IPostService postService)
        {
            _postService = postService;
        }

        /// <summary>
        /// 文章详情
        /// </summary>
        /// <param name="id">文章ID</param>
        /// <returns></returns>
        public ActionResult Details(int id)
        {
            var post = _postService.FindById(id);
            var model = post.ToModel();
            return View(model);
        }
    }
}

再添加文章详情页的视图[...\TsBlog.Frontend\Views\Post\Details.cshtml],添加如下视图HTML代码:

@model TsBlog.ViewModel.Post.PostViewModel
@{
    Layout = null;
}
<!doctype html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>@(Model.Title) | TSBLOG</title>
    <link href="~/Content/bootstrap.min.css" rel="stylesheet" />
    <link href="~/resources/css/site.css" rel="stylesheet" />
</head>
<body>
    <nav class="navbar navbar-default navbar-static-top ts-navbar">
        <div class="container">
            <div class="navbar-header">
                <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
                    <span class="sr-only">Toggle navigation</span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                </button>
                <a class="navbar-brand" href="~/">网站名称</a>
            </div>
            <div id="navbar" class="navbar-collapse collapse">
                <ul class="nav navbar-nav">
                    <li><a href="~/">网站首页</a></li>
                    <li class="dropdown">
                        <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">分类导航 <span class="caret"></span></a>
                        <ul class="dropdown-menu">
                            <li class="dropdown-header">后端开发</li>
                            <li><a href="http://2sharings.com/category/csharp-development">C#程序设计</a></li>
                            <li><a href="http://2sharings.com/category/dot-net">.NET程序设计</a></li>
                            <li><a href="http://2sharings.com/category/asp-dot-net">ASP.NET</a></li>
                            <li><a href="http://2sharings.com/category/asp-net-mvc">ASP.NET MVC</a></li>
                            <li><a href="http://2sharings.com/category/asp-dotnet-core">ASP.NET Core</a></li>
                            <li><a href="http://2sharings.com/category/winform">Winform</a></li>
                            <li role="separator" class="divider"></li>
                            <li class="dropdown-header">数据库</li>
                            <li><a href="http://2sharings.com/category/mysql">MySQL</a></li>
                            <li><a href="http://2sharings.com/category/sql-server">SQL Server</a></li>
                            <li><a href="http://2sharings.com/category/sqlite">SqLite</a></li>
                        </ul>
                    </li>
                    <li><a href="~/home/about">关于我们</a></li>
                    <li><a href="~/home/contact">联系我们</a></li>
                </ul>
                <ul class="nav navbar-nav navbar-right navbar-profile">
                    <li><a href="~/account/register">免费注册</a></li>
                    <li><a class="btn btn-primary nav-btn-login" href="~/account/login">立即登录</a></li>
                </ul>
            </div>
        </div>
    </nav>
    <div class="container">
        <h1 class="post-title">@Model.Title</h1>
        <article class="article-content">
            @Html.Raw(Model.Content)
        </article>
    </div>
    <footer class="footer-box">
        <div class="container">
            版权所有 &copy; @(DateTime.Now.Year)
        </div>
    </footer>
    <script src="~/Scripts/jquery-3.2.1.min.js"></script>
    <script src="~/Scripts/bootstrap.min.js"></script>
</body>
</html>

OK,今天这期的关于网站首页及文章详情页面的布局与制作就分享到这里,希望对你了解ASP.NET MVC WEB应用程序开发有所帮助。

本期源码托管地址:https://github.com/lampo1024/TsBlog/releases/tag/v1.9.0
数据库脚本文件请到目录下查看:TsBlog\document\scripts\mysql\v1.9\

如果你喜欢Rector的本系列文章,请为我点个大大的赞。

**看完教程如果觉得还不过瘾的,想“勾对”的,欢迎加入图享网官方QQ群:483350228,如果你按照教程还原出来的程序运行有问题,请参照本期源码对应调整与修改遇到问题的,也欢迎加入QQ群。有什么,你懂的。。。_ **

谢谢你的耐心阅读,本系列未完待续,我们下期再见……

本文来源自 图享网一步一步创建ASP.NET MVC5程序[Repository+Autofac+Automapper+SqlSugar](九)

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,639评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,277评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,221评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,474评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,570评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,816评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,957评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,718评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,176评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,511评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,646评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,322评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,934评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,755评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,987评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,358评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,514评论 2 348

推荐阅读更多精彩内容