2013年12月24日星期二

使用ASP.NET MVC3+EF+Jquery制作文字直播系统

昨天刚刚完成了学校六十校庆上用的文字直播系统,当然不是使用MVC做的。今天我再使用ASP.NET MVC3+EF+JQuery完善一下这个系统,也作为Entity Framework 4 in Action读书笔记系列前期的一个例子吧。

创建解决方案和项目

1.  首先,新建一个空的解决方案
解决方案的名称为:LiveText,如下图:
QQ截图20111013115139
2.  创建完解决方案,还需要创建三个项目,具体如下面的表格:
项目名称Visual Studio项目模板用途
LiveText.Domain
C#类库
保存域的实体和逻辑
LiveText.WebUI
ASP.NET MVC 3 Web Application
存储控制器和视图
LiveText.UnitTests
Test Project
单元测试
3.  添加引用
我们的项目中使用到了Ninject,Moq工具类库,首先需要添加对它们的引用,简便的方法是使用VS的Package Manager Console(View ➤ Other Windows ➤Package Manager Console),输入下面的命令:
Install-Package Ninject -Project LiveText.WebUI
Install-Package Ninject -Project LiveText.UnitTests
Install-Package Moq -Project LiveText.UnitTests
具体如下图:
QQ截图20111013120246
具体项目之间的依赖关系如下表:
项目名称工具依赖项目依赖
LiveText.Domain
没有
没有
LiveText.WebUI
Ninject
LiveText.Domain
LiveText.UnitTests
Ninject,Moq
LiveText.Domain,LiveText.WebUI
4.  设置依赖注入容器
项目中,我们使用Ninject创建控制器和处理依赖注入(DI)。在LiveText.WebUI项目中新建一个Infrastructure的文件夹,在该文件夹中新建一个NinjectControllerFactory类,代码如下:
public class NinjectControllerFactory : DefaultControllerFactory
{
    private IKernel ninjectKernel;
    public NinjectControllerFactory()
    {
        ninjectKernel = new StandardKernel();
        AddBindings();
    }
    protected override IController GetControllerInstance(RequestContext requestContext,
    Type controllerType)
    {
        return controllerType == null
        ? null
        : (IController)ninjectKernel.Get(controllerType);
    }
    private void AddBindings()
    {
    }
}
然后修改Global.asax如下
protected void Application_Start()
{
    AreaRegistration.RegisterAllAreas();

    RegisterGlobalFilters(GlobalFilters.Filters);
    RegisterRoutes(RouteTable.Routes);

    //修改的这个地方
    ControllerBuilder.Current.SetControllerFactory(new NinjectControllerFactory());
}
效果如下图:
QQ截图20111013120349
至此,项目的基本框架就做完了,下面设计数据库。

设计数据库

这里使用EF Code-First。
1.  编写实体类
人民网的文字直播系统分为“国新办发布会直播”、“国台办发布会直播”等类别,每个类别下面又有很多直播的内容。文字直播系统大体需要这几个实体类:
Category       ——      类别类                          Title      ——      标题类
          Text      ——      文字类                          User      ——      用户类
在LiveText.Domain项目中新建一个文件夹Entities,在该文件夹中新建上面四个类:
QQ截图20111013150243
public class Category
{
    /// <summary>
    /// 类别编号
    /// </summary>
    public int CategoryID { get; set; }
    /// <summary>
    /// 类别名称
    /// </summary>
    public string Name { get; set; }

    /// <summary>
    /// 标题集合
    /// </summary>
    public ICollection<Title> Titles { get; set; }
}
public class Title
{
    /// <summary>
    /// 标题编号
    /// </summary>
    public int TitleID { get; set; }
    /// <summary>
    /// 标题名称
    /// </summary>
    public string Name { get; set; }

    /// <summary>
    /// 所属类别
    /// </summary>
    public Category Category { get; set; }
    /// <summary>
    /// 文字集合
    /// </summary>
    public ICollection<Text> Texts { get; set; }
}
public class Text
{
    /// <summary>
    /// 文字编号
    /// </summary>
    public int TextID { get; set; }
    /// <summary>
    /// 发言人
    /// </summary>
    public string Prolocutor { get; set; }
    /// <summary>
    /// 发言内容
    /// </summary>
    public string ProContent { get; set; }
    /// <summary>
    /// 日期
    /// </summary>
    public DateTime ProDate { get; set; }

    /// <summary>
    /// 所属标题
    /// </summary>
    public Title Title { get; set; }
}
public class User
{
    /// <summary>
    /// 用户编号
    /// </summary>
    public int UserID { get; set; }
    /// <summary>
    /// 用户名
    /// </summary>
    public string UserName { get; set; }
    /// <summary>
    /// 用户密码
    /// </summary>
    public string Password { get; set; }
}
2.  添加EFCodeFirst
在Package Manager Console中输入命令:
Install-Package EFCodeFirst -Project LiveText.Domain
QQ截图20111013152422
3.  创建上下文类
在LiveText.Domain项目中,新建名为Concrete的文件夹,在该文件夹中新建一个LiveTextDbContext的类,它继承自System.Data.Entity.DbContext,具体代码如下:
public class LiveTextDbContext : DbContext
{
    public DbSet<Category> Categories { get; set; }
    public DbSet<Title> Titles { get; set; }
    public DbSet<Text> Texts { get; set; }
    public DbSet<User> Users { get; set; }
}
4.  修改Web.config
打开LiveText.WebUI项目的Web.config,添加一个数据库连接字符串,name的值要和上下文类的名称一样。
  <connectionStrings>
    <add name="LiveTextDbContext" 
         connectionString="Data Source=.;Initial Catalog=LiveText;Integrated Security=True;Pooling=False" 
         providerName="System.Data.SqlClient"/>
  </connectionStrings>
新建一个HomeController,添加如下代码:
public class HomeController : Controller
{
    LiveTextDbContext context = new LiveTextDbContext();

    //
    // GET: /Home/

    public ActionResult Index()
    {
        var categories = context.Categories;
        return View(categories);
    }

}
给Index添加一个View,如下图:
QQ截图20111013161349
现在就可以运行了,运行结果如下:
QQ截图20111013161922
再看看数据库里,EF已经为我们自动生成了数据库,数据库的结构如下图:
QQ截图20111013162156
至此,我们数据库的设计就完成了。

实现登录

这一篇,简简单单的把后台的登录功能实现,没有什么技术含量 亚当
首先在LiveText.WebUI项目中的Model文件夹中添加一个LogOnViewModel类,代码如下:


public class LogOnViewModel
{
    [Required(ErrorMessage = "不能为空")]
    public string UserName { get; set; }

    [Required(ErrorMessage = "不能为空")]
    [DataType(DataType.Password)]
    public string Password { get; set; }
}
第二,添加一个AccountController,在Scaffolding选项里面Template选择Empty controller,如下图所示:
QQ截图20111020212415
AccountController中的代码也很简单,主要是一个登录的action和退出的action,下面是具体代码:
public class AccountController : Controller
    {
        private LiveTextDbContext context = new LiveTextDbContext();

        //
        // GET: /Account/LogOn

        public ActionResult LogOn()
        {
            return View();
        }

        //
        // POST: /Account/LogOn
        [HttpPost]
        public ActionResult LogOn(LogOnViewModel model)
        {
            if (ModelState.IsValid)
            {
                if (context.Users.Any(u => u.UserName == model.UserName && u.Password == model.Password))
                {
                    FormsAuthentication.SetAuthCookie(model.UserName, false);
                    return View("~/Views/Admin/Index.cshtml");
                }
                else
                {
                    ModelState.AddModelError("", "用户名或密码不正确");
                }
            }

            return View(model);
        }

        public ActionResult LogOff()
        {
            FormsAuthentication.SignOut();
            return View("LogOn");
        }
在LogOn action中,如果用户提供的用户名和密码正确,就跳转到Index.cshtml。Index.cshtml在View文件夹里的Admin文件夹中,Admin是我新建的文件夹。
第三,在LogOn上右击,选择AddView,默认选项即可,添加之后,在View文件夹里自动生成Account文件夹,以及Account文件夹里LogOn.cshtml。
第四,在LogOn.cshtml,我在网上随便找了个模板加上了,具体代码可以下载源代码看一下。下面我只贴出关键性的代码:
@Html.EditorFor(u => u.UserName)
@Html.ValidationMessageFor(u => u.UserName)
@Html.EditorFor(u => u.Password)
@Html.ValidationMessageFor(u => u.Password)
下面让我们看一下运行的效果吧:
登录页面
QQ截图20111020215619
QQ截图20111020215703
输入用户名:admin 密码:admin登录成功
QQ截图20111020215834

上一篇简单的实现了一下登录的功能,这一篇首先把后台的框架撘一撘。


搭框架

还是使用上文提到的模板,该模板使用frameset框架,关于在ASP.NET MVC下如何使用frameset框架,我推荐大家看一篇文章在ASP.NET MVC下使用frameset框架!
上一篇中,我们在View文件夹下新建了一个Admin文件夹,所以,我们先新建一个AdminController,添加如下代码:
public class AdminController : Controller
{
    //
    // GET: /Admin/

    [Authorize]
    public ActionResult Index()
    {
        return View("Index");
    }

    [Authorize]
    public ActionResult Top()
    {
        return View("Top");
    }

    [Authorize]
    public ActionResult Left()
    {
        return View("Left");
    }
}
相对应新建如下视图:
QQ截图20111023110532
Index.cshtml里面使用frameset引用Top.cshtml和Left.cshtml,方式如下,具体可以看上面那篇文章。
<frame src="@Url.Action("Top")" noresize="noresize" frameborder="NO" name="topFrame" scrolling="no" marginwidth="0" marginheight="0" target="main" />
下面还需要修改一下AccountController里面的LogOn action,具体如下:
//
// POST: /Account/LogOn
[HttpPost]
public ActionResult LogOn(LogOnViewModel model)
{
    if (ModelState.IsValid)
    {
        if (context.Users.Any(u => u.UserName == model.UserName && u.Password == model.Password))
        {
            FormsAuthentication.SetAuthCookie(model.UserName, false);
            //下面是修改的地方
            return RedirectToAction("Index", "Admin");
        }
        else
        {
            ModelState.AddModelError("", "用户名或密码不正确");
        }
    }

    return View(model);
}
这样框架大体就搭好了,效果如下:
QQ截图20111023143027

实现用户管理

要实现用户管理,我们先修改LiveText.Domain项目中User实体,因为我们是用Code-First生成的数据库,如果修改了实体,再一次运行就会出错,所以第一件要做的事就是在LiveText.WebUI项目中的Model文件夹中新建一个LiveTextDbInitializer类,用于当实体发生变化时重新生成数据库。
public class LiveTextDbInitializer : DropCreateDatabaseIfModelChanges<LiveTextDbContext>
{
    protected override void Seed(LiveTextDbContext context)
    {
        context.Users.Add(new Domain.Entities.User
        {
            UserName = "admin",
            Password = "admin"
        });
        base.Seed(context);
    }
}
然后在Global.asax中的 Application_Start方法中注册一下:
Database.SetInitializer(new LiveTextDbInitializer());
下面我们就可以修改User实体了。如果一开始都写好的话,现在就不用修改了,麻烦,困惑。修改后的代码如下:
public class User
{
    /// <summary>
    /// 用户编号
    /// </summary>
    public virtual int UserID { get; set; }
    /// <summary>
    /// 用户名
    /// </summary>
    [Required(ErrorMessage = "不能为空")]
    [StringLength(30)]
    [Display(Name = "用户名")]
    public virtual string UserName { get; set; }
    /// <summary>
    /// 用户密码
    /// </summary>
    [Required(ErrorMessage = "不能为空")]
    [DataType(DataType.Password)]
    [StringLength(30, MinimumLength = 5, ErrorMessage = "密码最短为5个字符")]
    [Display(Name = "密码")]
    public virtual string Password { get; set; }
}
接下来,我们新建一个UserController,按照下图所示选择。
QQ截图20111023120355
最后,将Left.cshtml里面的超链接改一下:
   <li><a href="@Url.Action("Create", "User")" target="main">添加用户</a></li>
   <li><a href="@Url.Action("Index", "User")" target="main">用户列表</a></li>
OK,再运行一下程序:
QQ截图20111023122634QQ截图20111023122807
没有加样式,所以很难看悲伤

实现文字直播管理

首先,实现类别管理。
修改Category实体。
public class Category
{
    /// <summary>
    /// 类别编号
    /// </summary>
    public virtual int CategoryID { get; set; }
    /// <summary>
    /// 类别名称
    /// </summary>
    [Required(ErrorMessage = "不能为空")]
    [Display(Name = "类别名称")]
    public virtual string Name { get; set; }

    /// <summary>
    /// 标题集合
    /// </summary>
    public virtual List<Title> Titles { get; set; }
}
新建CategoryController。
QQ截图20111023144339
Now,类别管理完成了,看一下运行效果:
QQ截图20111023144547QQ截图20111023144627
第二,实现标题管理
首先修改Title实体。
public class Title
{
    /// <summary>
    /// 标题编号
    /// </summary>
    public virtual int TitleID { get; set; }

    /// <summary>
    /// 类别编号
    /// </summary>
    [Required(ErrorMessage = "类别不能为空")]
    public virtual int CategoryID { get; set; }

    /// <summary>
    /// 标题名称
    /// </summary>
    [Required(ErrorMessage = "标题不能为空")]
    [Display(Name = "标题名称")]
    public virtual string Name { get; set; }

    /// <summary>
    /// 所属类别
    /// </summary>
    public virtual Category Category { get; set; }
    /// <summary>
    /// 文字集合
    /// </summary>
    public virtual List<Text> Texts { get; set; }
}
新建TitleController。
QQ截图20111023152941
这样就完成了,看一下运行效果:
QQ截图20111023164751QQ截图20111023164847
实现文字管理
首先修改Text实体。
public class Text
{
    /// <summary>
    /// 文字编号
    /// </summary>
    public virtual int TextID { get; set; }
    /// <summary>
    /// 标题编号
    /// </summary>
    [Required(ErrorMessage = "标题不能为空")]
    [Display(Name = "所属标题")]
    public virtual int TitleID { get; set; }
    /// <summary>
    /// 发表人
    /// </summary>
    [Required(ErrorMessage = "发表人不能为空")]
    [Display(Name = "发表人")]
    public virtual string Prolocutor { get; set; }
    /// <summary>
    /// 发表内容
    /// </summary>
    [Required(ErrorMessage = "发表内容不能为空")]
    [Display(Name = "发表内容")]
    public virtual string ProContent { get; set; }
    /// <summary>
    /// 标题
    /// </summary>
    [Display(Name = "发表时间")]
    public virtual DateTime ProDate { get; set; }
    /// <summary>
    /// 所属标题
    /// </summary>
    [Display(Name = "所属标题")]
    public virtual Title Title { get; set; }
}
新建TextController。
QQ截图20111023165707
OK,看一下运行结果:
QQ截图20111023172926
QQ截图20111023173123
今天到此为止,下一篇用JQuery在前台显示。

在上一篇中本来打算结束的,最后遇到点小问题,不得不分开,废话少说,开始吧。


这一篇中,我们完成最后的工作,在页面中显示数据。我返回的是JSON数据,所以首先写一个JsonHelper类。
在LiveText.WebUI项目里新建一个Tool文件夹,在这个文件夹里新建一个JsonHelper类。代码如下:
public static class JsonHelper
{
    /// <summary>
    /// Json序列化
    /// </summary>
    /// <param name="value"></param>
    /// <returns></returns>
    public static string JsonSerializer(this object value)
    {
        JavaScriptSerializer s = new JavaScriptSerializer();
        return s.Serialize(value);
    }
}
然后我们在新建一个一般处理程序,就命名为GetInfoList.ashx吧。在前台页面,我们对它发起AJAX请求,返回JSON数据。
public class GetInfoList : IHttpHandler
{

    public void ProcessRequest(HttpContext context)
    {
        context.Response.ContentType = "text/plain";

        LiveTextDbContext dbContext = new LiveTextDbContext();

        var list = from t in dbContext.Texts.Where(t => t.Title.Name == "校庆文字直播")
                   select new
                   {
                       t.Prolocutor,
                       t.ProContent,
                       t.ProDate
                   };

        string data = list.JsonSerializer();

        context.Response.ContentType = "application/json";
        context.Response.Write(data);
        context.Response.Flush();
        context.Response.End();
    }

    public bool IsReusable
    {
        get
        {
            return false;
        }
    }
}
我在上面的代码中为了方便将程序写死了,只取得标题为“校庆文字直播”的文字。当然,你得在后台新建一个“校庆文字直播”的标题,将它放在一个类别里面。我已经将数据都放在数据库里面了,一会就可以看到程序运行的效果了。
下面我们就完成前台的东西吧。我们需要修改的就是Views➤Home下面的Index.cshtml。
在Body里面加上下面代码即可:
    <div id="container">
        <div id="live">
            <ul>
            </ul>
        </div>
    </div>
然后加点CSS眨眼

    <style type="text/css">
        div#container
        {
            width: 770px;
            margin-left: auto;
            margin-right: auto;
        }
        div#live
        {
            width: 100%;
        }
        div#live ul
        {
            list-style: none;
        }
        div#live ul li
        {
            padding-bottom: 19px;
        }
        div#live ul li p
        {
            margin-top: 0;
            margin-bottom: 0;
        }
        div#live .evenlibackcolor
        {
            background-color: #F5F5F5;
        }
        div#live .oddlibackcolor
        {
            background-color: #FFF;
        }
        div#live .namespan
        {
            color: #E211A5;
        }
        div#live .timespan
        {
            font-size: small;
            color: #AAA;
            margin-left: 10px;
        }
    </style>
在引入两个JS文件,DateFormate.js的下载地址:http://files.cnblogs.com/nianming/DateFormat.js
    <script src="@Url.Content("~/Scripts/jquery-1.5.1.js")" type="text/javascript"></script>
    <script src="@Url.Content("~/Scripts/DateFormat.js")" type="text/javascript"></script>
在写一下JS代码就搞定了:
    <script type="text/javascript">
        $(function () {
            init();
            setInterval("dyoper()", 10000);
        });

        //初始
        function init() {
            var noCache = new Date();
            $.ajax({
                type: 'post',
                url: '/Models/GetInfoList.ashx?m=' + Date(),
                data: {},
                success: function (data) {
                    if (data != null) {
                        jQuery.each(data, function (entryIndex, entry) {
                            var mydate = ConvertJSONDateToJSDateObject(entry['ProDate']);
                            var html = '';
                            if (entryIndex % 2 == 0) {
                                html = '<li class="evenlibackcolor"><p><span class="namespan">[';
                            }
                            else {
                                html = '<li class="oddlibackcolor"><p><span class="namespan">[';
                            }
                            html += entry['Prolocutor'] + ']:</span><span>';
                            html += entry['ProContent'] + '</span><span class="timespan">[';
                            html += mydate.pattern("HH:mm:ss") + ']</span></p></li>';
                            $("#live ul").append(html);
                        });
                    }
                }
            });
        }

        function dyoper() {
            var noCache = new Date();
            $.ajax({
                type: 'post',
                url: '/Models/GetInfoList.ashx?m=' + Date(),
                data: {},
                success: function (data) {
                    if (data != null) {
                        $("#live ul>li").remove();
                        jQuery.each(data, function (entryIndex, entry) {
                            var mydate = ConvertJSONDateToJSDateObject(entry['ProDate']);
                            var html = '';
                            if (entryIndex % 2 == 0) {
                                html = '<li class="evenlibackcolor"><p><span class="namespan">[';
                            }
                            else {
                                html = '<li class="oddlibackcolor"><p><span class="namespan">[';
                            }
                            html += entry['Prolocutor'] + ']:</span><span>';
                            html += entry['ProContent'] + '</span><span class="timespan">[';
                            html += mydate.pattern("HH:mm:ss") + ']</span></p></li>';
                            $("#live ul").append(html);
                        });
                    }
                }
            });
        }

        function ConvertJSONDateToJSDateObject(JSONDateString) {
            var date = new Date(parseInt(JSONDateString.replace("/Date(", "").replace(")/", ""), 10));
            return date;
        }
    </script>
下面看一下运行效果吧:
QQ截图20111025094755
至此,就完成了。程序源代码下载地址:http://files.cnblogs.com/nianming/LiveText20101025.rar