使用 MVC4 和 Entity Framework 的论坛应用程序





5.00/5 (4投票s)
我想分享这个使用 MVC4 和 Entity Framework 完成的应用程序。
引言
大家好,在本文中,我想向大家展示如何使用 MVC4 和 Entity Framework 创建一个论坛应用程序。
此应用程序主要涵盖以下内容:
- 注册用户
- 登录
- 就现有技术创建问题
- 为已发布的问题添加回复。
还包括其他一些功能,例如获取某个问题的最后一条回复,以及不允许用户在未登录的情况下发布问题或回复。
使用代码
首先为当前应用程序创建一个数据库,创建所需的表和存储过程,如下所示:
USE [newForumDB]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
SET ANSI_PADDING ON
GO
CREATE TABLE [dbo].[tblUser](
[UserName] [varchar](50) NOT NULL,
[EmailID] [varchar](50) NOT NULL,
[DisplayName] [varchar](50) NOT NULL,
[DateJoined] [datetime] NOT NULL,
[Password] [varchar](50) NOT NULL,
[Photo] [image] NULL,
CONSTRAINT [PK_tblUser] PRIMARY KEY CLUSTERED
(
[UserName] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF,
ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO
SET ANSI_PADDING OFF
GO
USE [newForumDB]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
SET ANSI_PADDING ON
GO
CREATE TABLE [dbo].[tblTechnology](
[TechID] [int] IDENTITY(1,1) NOT NULL,
[TechName] [varchar](max) NOT NULL,
[TechDesc] [varchar](100) NULL,
PRIMARY KEY CLUSTERED
(
[TechID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF,
IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
SET ANSI_PADDING OFF
GO
USE [newForumDB]
GO
/****** Object: Table [dbo].[tblQuestions] Script Date: 02/14/2013 14:22:06 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
SET ANSI_PADDING ON
GO
CREATE TABLE [dbo].[tblQuestions](
[QuestionID] [int] IDENTITY(1,1) NOT NULL,
[QuestionTitle] [varchar](max) NOT NULL,
[QuestionDesc] [varchar](max) NOT NULL,
[DatePosted] [datetime] NOT NULL,
[UserName] [varchar](50) NOT NULL,
[TechID] [int] NOT NULL,
[viewCount] [int] NOT NULL,
[ReplyCount] [int] NOT NULL,
PRIMARY KEY CLUSTERED
(
[QuestionID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF,
ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
SET ANSI_PADDING OFF
GO
SELECT [ReplyID]
,[QuestionID]
,[date]
,[TechID]
,[UserName]
,[ReplyMsg]
FROM [newForumDB].[dbo].[tblReplies]
GO
存储过程
USE [newForumDB]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
SET ANSI_PADDING ON
GO
CREATE TABLE [dbo].[tblUser](
[UserName] [varchar](50) NOT NULL,
[EmailID] [varchar](50) NOT NULL,
[DisplayName] [varchar](50) NOT NULL,
[DateJoined] [datetime] NOT NULL,
[Password] [varchar](50) NOT NULL,
[Photo] [image] NULL,
CONSTRAINT [PK_tblUser] PRIMARY KEY CLUSTERED
(
[UserName] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF,
ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO
SET ANSI_PADDING OFF
GO
USE [newForumDB]
GO
/****** Object: StoredProcedure [dbo].[displayallQuesTechID1] Script Date: 02/14/2013 14:23:05 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE procedure [dbo].[displayallQuesTechID1](@TechID int
)as
begin
select FI.QuestionID,FI.QuestionTitle,FI.UserName,FI.DatePosted,FI.[date],FI.RepliedName,FI.viewCount,FI.ReplyCount
,FI.ReplyMsg,TT.TechID,TT.TechName from tblTechnology TT,
(select distinct TQ.TechID,TQ.QuestionID,TQ.QuestionTitle,TQ.UserName,
TQ.DatePosted,TR.[date],TR.UserName as RepliedName,TQ.viewCount,TQ.ReplyCount
,TR.ReplyMsg from tblQuestions TQ LEFT OUTER JOIN tblReplies TR ON TR.TechID=TQ.TechID and TR.QuestionID = TQ.QUESTIONID
and TR.[date]in (select MAX(TR.[date]) from tblReplies TR group by TR.QuestionID)) FI where FI.TechID=TT.TechID and TT.TechID=@TechID
end
GO
USE [newForumDB]
GO
/****** Object: StoredProcedure [dbo].[displayResults] Script Date: 02/14/2013 14:23:38 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE procedure [dbo].[displayResults](@QuestionID int
)as
begin
select tbl.QuestionID,tbl.QuestionDesc,tbl.TechID,tbl.quesaskedby,tbl.QuestionTitle,tbl.DatePosted,tbl.ReplyID,
tbl.date,tbl.ReplyMsg,tbl.ReplyUser from
(select distinct q.QuestionID,q.QuestionDesc,q.TechID, q.UserName as quesaskedby,q.QuestionTitle,q.DatePosted,
r.date,r.ReplyID,r.ReplyMsg,r.UserName as ReplyUser
from tblQuestions q left outer join tblReplies r on r.QuestionID=q.QuestionID) tbl
where tbl.QuestionID=@QuestionID
end
GO
USE [newForumDB]
GO
/****** Object: StoredProcedure [dbo].[selectTechQuestions1] Script Date: 02/14/2013 14:23:47 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE [dbo].[selectTechQuestions1]
As
Begin
WITH A AS (
SELECT top(1) WITH ties Q.TechID
,QuestionID
,QuestionTitle
,DatePosted
,Username,TechName,TechDesc,T.TechID as TechnID
FROM tblTechnology T LEFT OUTER JOIN tblQuestions Q ON Q.TechID = T.TechID
ORDER BY row_number() over(partition BY Q.TechID ORDER BY Dateposted DESC)
)
SELECT * FROM A
OUTER apply (SELECT count(QuestionDesc) Totalposts, sum(ReplyCount) ReplyCount
FROM tblQuestions WHERE A.TechID=tblQuestions.TechID) D
End
GO
好的,现在让我们创建一个 MVC 应用程序。
打开位于 VIEWS->Shared
文件夹内的 _ViewStart.cshtml 文件,并替换为以下内容:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>@ViewBag.Title</title>
@Styles.Render("~/Content/css")
@Scripts.Render("~/bundles/modernizr")
<script src="@Url.Content("~/Scripts/tinymce/jquery.tinymce.js")"
type="text/javascript"></script>
</head>
<body>
<div id="branding">
</div>
<div>
@if (Session["UserName"] != null)
{
<div class="logged_in" id="user_navigation" runat="server">
<a title="Your Profile" href="">@*<img height="50"
width="50" class="photo"
src='<%= Url.Action( "GetPhoto", "image", new { photoId = Session["UserName"] } ) %>' />*@
<img alt="" src="@Url.Action("GetPhoto", "User")"
height="50" width="50" class="photo" />
</a>
<div id="user_info">
<p>
<span class="hide">Signed in as </span><a href="" title="Your Profile" class="ipbmenu">
<span class="ipbmenu">@Html.Label("Name", Session["UserName"].ToString()) </span>
</a>
<img alt=">" src="http://www.gimptalk.com/public/style_images/master/opts_arrow.png" />
</p>
<ul class="boxShadow" id="user_link_menucontent" style="display: none; position: absolute;
z-index: 9999;">
</ul>
<ul id="user_other">
<li><a href="../User/Logout">Logout</a> </li>
</ul>
</div>
<br />
</div>
@*<strong>@Html.Encode(User.Identity.Name)</strong>
@Html.ActionLink("Log Out", "Logout", "User");*@
}
else
{
<div class="not_logged_in" id="user_navigation" runat="server">
<a class="rounded" id="A1" title="Sign In »" href="../User/Login"><span class="left">
Sign In »</span> <span class="services right"></span>
<br />
</a>
<br />
<span class="links">New user? <a id="register_link" title="Register Now!" href="../User/Register">
Register Now!</a> </span>
</div>
}
</div>
<div id="primary_nav">
<ul>
<li class="left active" id="nav_discussion" runat="server"><a title="Go to Forums"
href="@Url.Action("Main", "Home")">Forums</a></li><li class="left" id="nav_members"
runat="server"><a
title="Go to Member List" href="@Url.Action("Members", "Home")">Members</a></li>
</ul>
</div>
@RenderBody()
@Scripts.Render("~/bundles/jquery")
@RenderSection("scripts", required: false)
</body>
</html>
如果你想包含自己的脚本和 CSS,请打开 App_Start 文件夹中的 BundleConfig.cs 文件,你会看到已包含的现有 CSS 或脚本,你需要在那里添加你自己的,如下所示:
bundles.Add(new StyleBundle("~/Content/css").Include("~/Content/css/ipb_common.css",
"~/Content/css/ipb_editor.css",
"~/Content/css/ipb_help.css",
"~/Content/css/ipb_login_register.css",
"~/Content/css/ipb_print.css",
"~/Content/css/ipb_styles.css", "~/Content/css/ipb_ucp.css"));
现在,我们来创建一个包含两个类 userModel
和 Register
的 Model
。
将以下代码复制粘贴到你的类中并构建应用程序。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Web.Mvc;
namespace mvcForumapp.Models
{
public class userModel
{
[Required]
[DataType(DataType.EmailAddress)]
[Display(Name = "Email address")]
[MaxLength(50)]
[RegularExpression(@"[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,4}",
ErrorMessage = "Please enter correct email")]
public string Email { get; set; }
[Required]
[DataType(DataType.Password)]
[Display(Name = "Password")]
public string password { get; set; }
}
public class Register
{
[Required(ErrorMessage = "username required")]
[Display(Name = "Choose username")]
[StringLength(20, MinimumLength = 4)]
[Remote("IsUserNameAvailable", "Register", "Username is already taken")]
public string Username { get; set; }
[Required(ErrorMessage = "display required")]
[StringLength(20, MinimumLength = 4)]
[Remote("IsDisplayAvailable", "Register", "displayname already taken")]
public string Displayname { get; set; }
[Required(ErrorMessage = "EmailAddress required")]
[DataType(DataType.EmailAddress)]
[Display(Name = "Email address")]
[MaxLength(50)]
[RegularExpression(@"[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,4}",
ErrorMessage = "Please enter correct email")]
[Remote("IsEmailAvailable", "Register", "EmailAddress is already taken")]
public string Email { get; set; }
[Required(ErrorMessage = "password required")]
[DataType(DataType.Password)]
[StringLength(20, MinimumLength = 8)]
[Display(Name = "Password")]
public string password { get; set; }
[Required(ErrorMessage = "password required")]
[DataType(DataType.Password)]
[Compare("password", ErrorMessage = "password didn't match")]
[StringLength(20, MinimumLength = 8)]
public string PasswordConfirm { get; set; }
}
}
现在我们创建一个 controller
,并给它起个我们需要的名字。或者,因为我们最初是尝试创建
和注册
用户,所以将其命名为 UserController
。在 MVC 中,一个好的命名约定会让你的工作更轻松,否则在实现或开发大型应用程序时会遇到问题。
首先,我们来处理用户注册。正如我所说,我们正在使用 Entity Framework,所以让我们将实体模型添加到我们的项目中。通过我给出的表和存储过程,你可以轻松地为应用程序添加一个实体模型,即使是刚接触 Entity Framework 的人也能做到。
你可以查看我之前的文章,了解如何从这里为项目添加模型。
现在用以下代码替换你的控制器代码。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.UI;
namespace mvcForumapp.Controllers
{
public class UserController : Controller
{
newForumDBEntities db = new newForumDBEntities();
public ActionResult Index()
{
return View();
}
[HttpGet]
public ActionResult Register()
{
return View();
}
public ActionResult IsUserNameAvailable(string UserName)
{
var usrAvailable = db.tblUsers.Where(p => p.UserName == UserName).Select(img => img.UserName).FirstOrDefault();
if (usrAvailable == null)
{
return Json(true, JsonRequestBehavior.AllowGet);
}
return Json("<span style='color:Red;'> Username in already in use</span>", JsonRequestBehavior.AllowGet);
}
[OutputCache(Location = OutputCacheLocation.None, NoStore = true)]
public JsonResult IsDisplayAvailable(string Displayname)
{
var usrAvailable = db.tblUsers.Where(p => p.DisplayName ==
Displayname).Select(img => img.DisplayName).FirstOrDefault();
if (usrAvailable == null)
{
return Json(true, JsonRequestBehavior.AllowGet);
}
}
[OutputCache(Location = OutputCacheLocation.None, NoStore = true)]
public JsonResult IsEmailAvailable(string Email)
{
var usrAvailable = db.tblUsers.Where(p => p.EmailID == Email).Select(img => img.EmailID).FirstOrDefault();
if (usrAvailable == null)
{
return Json(true, JsonRequestBehavior.AllowGet);
}
return Json("<span style='color:Red;'> Emai in already in use</span>", JsonRequestBehavior.AllowGet);
}
[HttpPost]
public ActionResult Register(mvcForumapp.Models.Register user, HttpPostedFileBase file)
{
if (ModelState.IsValid)
{
if (file == null)
{
ModelState.AddModelError("File", "Please Upload Your file");
}
else if (file.ContentLength > 0)
{
int MaxContentLength = 1024 * 1024 * 3; //3 MB
string[] AllowedFileExtensions = new string[] { ".jpg", ".gif", ".png", ".pdf" };
if (!AllowedFileExtensions.Contains(file.FileName.Substring(file.FileName.LastIndexOf('.'))))
{
ModelState.AddModelError("File", "Please file of type: " +
string.Join(", ", AllowedFileExtensions));
}
else if (file.ContentLength > MaxContentLength)
{
ModelState.AddModelError("File",
"Your file is too large, maximum allowed size is: " +
MaxContentLength + " MB");
}
else
{
using (var db = new newForumDBEntities())
{
Byte[] imgByte = null;
HttpPostedFileBase File = file;
imgByte = new Byte[File.ContentLength];
File.InputStream.Read(imgByte, 0, File.ContentLength);
var userdets = db.tblUsers.CreateObject();
userdets.UserName = user.Username;
userdets.DateJoined = DateTime.Now;
userdets.DisplayName = user.Displayname;
userdets.EmailID = user.Email;
userdets.Password = user.password;
userdets.Photo = imgByte;
db.tblUsers.AddObject(userdets);
db.SaveChanges();
return RedirectToAction("Main", "Home");
}
}
}
}
return View(user);
}
}
}
现在让我们在控制器内为 Register
创建一个 View
。有两个同名的动作结果 Register
,但功能不同,一个是用于 HttpGet,它只返回一个带控件的视图;另一个是用于 HttpPost
,它将数据提交到数据库。
你也可以通过从下拉列表中选择所需的类来创建一个强类型视图,并选择在 Scaffold 模板中创建。如果你想拥有自己的设计,只需创建一个空视图即可。
如果你没有选择强类型视图,你的视图最初将如下所示:
@{
ViewBag.Title = "Register";
Layout = "~/Views/Shared/_Layout.cshtml";
}
复制粘贴以下内容。
@model mvcForumapp.Models.Register
@{
ViewBag.Title = "Register";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<script src="../../Scripts/jquery-1.7.1.min.js" type="text/javascript"></script>
<script src="../../Scripts/jquery.validate.min.js" type="text/javascript"></script>
<script src="../../Scripts/jquery.validate.unobtrusive.min.js" type="text/javascript"></script>
<script type="text/jscript">
//get file size
function GetFileSize(fileid) {
try {
var fileSize = 0;
//for IE
if ($.browser.msie) {
//before making an object of ActiveXObject,
//please make sure ActiveX is enabled in your IE browser
var objFSO = new ActiveXObject("Scripting.FileSystemObject"); var filePath = $("#" + fileid)[0].value;
var objFile = objFSO.getFile(filePath);
var fileSize = objFile.size; //size in kb
fileSize = fileSize / 1048576; //size in mb
}
//for FF, Safari, Opeara and Others
else {
fileSize = $("#" + fileid)[0].files[0].size //size in kb
fileSize = fileSize / 1048576; //size in mb
}
// alert("Uploaded File Size is" + fileSize + "MB");
return fileSize;
}
catch (e) {
alert("Error is :" + e);
}
}
//get file path from client system
function getNameFromPath(strFilepath) {
var objRE = new RegExp(/([^\/\\]+)$/);
var strName = objRE.exec(strFilepath);
if (strName == null) {
return null;
}
else {
return strName[0];
}
}
$("#btnSubmit").live("click", function () {
if ($('#fileToUpload').val() == "") {
$("#spanfile").html("Please upload file");
return false;
}
else {
return checkfile();
}
});
function checkfile() {
var file = getNameFromPath($("#fileToUpload").val());
if (file != null) {
var extension = file.substr((file.lastIndexOf('.') + 1));
// alert(extension);
switch (extension) {
case 'jpg':
case 'JPG':
case 'png':
case 'PNG':
case 'gif':
case 'GIF':
flag = true;
break;
default:
flag = false;
}
}
if (flag == false) {
$("#spanfile").text("You can upload only jpg,png,gif,pdf extension file");
return false;
}
else {
var size = GetFileSize('fileToUpload');
if (size > 3) {
$("#spanfile").text("You can upload file up to 3 MB");
return false;
}
else {
$("#spanfile").text("");
}
}
}
$(function () {
$("#fileToUpload").change(function () {
checkfile();
});
});
</script>
@using (Html.BeginForm("Register", "Register", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
<br />
<br />
<div class="block_wrap left" id="register_form">
<h2>
Ready to register?</h2>
<p class="extra">
It's free and simple to register for our board! We just need
a few pieces of information from you, and you'll be ready to
make your first post in no time!
<br />
If you already have an account, you can go directly to the <a
title="Go to sign in" href="../User/Login">sign in page</a>
<br />
</p>
<div class="generic_bar">
</div>
<h3 style="text-align: center;" class="bar">
Account Information</h3>
<ul>
<li class="field required ">
@Html.LabelFor(m => m.Username)
@Html.TextBoxFor(m => m.Username, new { maxlength = 50 })
<span class="input_error">@Html.ValidationMessageFor(m => m.Username)</span>
</li>
<li class="field required ">
@Html.LabelFor(m => m.Displayname)
@Html.TextBoxFor(m => m.Displayname, new { maxlength = 50 })
<span class="input_error">@Html.ValidationMessageFor(m => m.Displayname)</span>
</li>
<li class="field required ">
@Html.LabelFor(m => m.Email)
@Html.TextBoxFor(m => m.Email, new { maxlength = 50 })
<span class="input_error">@Html.ValidationMessageFor(m => m.Email)</span>
</li>
<li class="field required ">
@Html.LabelFor(m => m.password)
@Html.PasswordFor(m => m.password, new { maxlength = 50 })
<span class="input_error">@Html.ValidationMessageFor(m => m.password)</span>
</li>
<li class="field required ">
@Html.LabelFor(m => m.PasswordConfirm)
@Html.PasswordFor(m => m.PasswordConfirm, new { maxlength = 50 })
<span class="input_error">@Html.ValidationMessageFor(m => m.PasswordConfirm)</span>
</li>
<li class="field required ">
<label>
Select Image</label>
<input type="file" id="fileToUpload" name="file" />
<span class="input_error" id="spanfile"></span></li>
</ul>
<br />
<hr />
<div style="float: left; margin-left: 250px;">
<table>
<tr>
<td>
<input type="submit" class="input_submit" id="btnSubmit" value="Create User" />
</td>
<td>
@Html.ActionLink("Cancel", "Main", new { Controller = "Home" }, new { @class = "input_submit" })
</td>
</tr>
</table>
@*<input type="submit" value=" Cancel" class="input_submit" />*@
</div>
</div>
<br />
<div style="float: right; margin-right: 350px;">
</div>
}
App_Start 文件夹中的 RouteConfig.cs 文件需要做如下更改。
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "User", action = "Index", id = UrlParameter.Optional }
);
}
现在运行应用程序,你将看到如下界面:
点击 Register
将路由到注册视图,点击 Login
将路由到登录视图。
这是你的注册视图的样子:
远程验证
点击此处查看远程验证的实现:http://msdn.microsoft.com/en-us/library/gg508808(v=vs.98).aspx
在这里我检查了用户名、显示名称和电子邮件是否存在;如果存在,远程验证将触发,如果不存在,则不会显示错误。
如果一切正常,你就可以注册了。
注册后,你将返回到默认页面。
现在我们来处理登录
。既然你已经创建了一个模型,我们就来为登录创建控制器和所需的视图。
按照我们处理注册的步骤,从控制器开始,首先在 Controller 文件夹中添加一个名为 logincontroller
的控制器,然后用以下代码替换。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace mvcForumapp.Controllers
{
public class LoginController : Controller
{
newForumDBEntities db = new newForumDBEntities();
[HttpGet]
public ActionResult Login()
{
return View();
}
[HttpPost]
public ActionResult Login(mvcForumapp.Models.userModel user)
{
if (ModelState.IsValid)
{
if (isValid(user.Email, user.password))
{
var user1 = db.tblUsers.FirstOrDefault(u => u.EmailID == user.Email).UserName;
Session["UserName"] = user1;
return RedirectToAction("Index", "Register");
}
else
{
ModelState.AddModelError("", "Login Data is Incorrect");
}
}
return View(user);
}
private bool isValid(string Email, string password)
{
bool isvalid = false;
using (var db = new newForumDBEntities())
{
var user = db.tblUsers.FirstOrDefault(u => u.EmailID == Email);
if (user != null)
{
if (user.Password == password)
{
isvalid = true;
}
}
}
return isvalid;
}
}
}
像我们为注册
做的那样相应地添加视图,添加视图后,运行程序,你的登录视图将如下所示:
成功登录后,你将看到你的用户名和头像,如下所示。在此之前,请确保你实现了显示登录用户头像的代码,我们将按以下方式实现:
你可以在 _Layout.cshtml 文件中看到如下内容:
<img alt="" src="@Url.Action("GetPhoto", "User")"
height="50" width="50" class="photo" />
这意味着我们正试图从 控制器(User)
中加载图片,方法或函数名为 GetPhoto
。请根据你的控制器和方法名在此进行更改。为了更好地理解,我将创建一个新的控制器,并将其命名为 displayImage
,函数名为 ShowImage
,所以我的图片源现在将是如下所示:
<img alt="" src="@Url.Action("ShowImage", "displayImage")" height="50" width="50" class="photo" />
你用于显示登录用户图像的控制器代码如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace mvcForumapp.Controllers
{
public class displayImageController : Controller
{
newForumDBEntities db = new newForumDBEntities();
public ActionResult Index()
{
return View();
}
[HttpGet]
public ActionResult ShowImage()
{
string user = Session["UserName"] as string;
byte[] photo = null;
var v = db.tblUsers.Where(p => p.UserName == user).Select(img => img.Photo).FirstOrDefault();
photo = v;
return File(photo, "image/jpeg");
}
}
}
这是你成功登录后看到的样子:
现在让我们为注销
创建一个控制器和视图。创建一个控制器并将其命名为Logout
,然后用以下代码替换你的控制器:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace mvcForumapp.Controllers
{
public class LogoutController : Controller
{
public ActionResult Logout()
{
Session.Abandon();
return RedirectToAction("Index", "Register");
}
}
}
现在通过右键点击 Logout
动作结果并选择母版页来添加一个视图,这样你的视图应该如下所示:
@{
ViewBag.Title = "Logout";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>Logout</h2>
成功注销后,你将再次路由到主视图。
到目前为止一切顺利。现在让我们为其余部分创建控制器
和视图
,即发布问题、回复等。首先,让我们从数据库中获取每种技术下的所有问题。
由于我们使用的是 Entity Framework,数据库中的每个存储过程都被视为一个模型,不仅是存储过程,表也是如此。
首先,让我们创建一个控制器
,用于显示可用技术列表、每种技术下的主题和回复数量,以及最后发布问题的信息。
创建一个控制器并将其命名为Technology
,然后用以下代码替换:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace mvcForumapp.Controllers
{
public class TechnologyController : Controller
{
newForumDBEntities db = new newForumDBEntities();
public ActionResult Index()
{
List<mvcForumapp.selectStats_Result_Result> userview = db.selectStats_Result().ToList();
return View(userview);
}
}
}
创建一个名为 Index
的 View
,并将以下内容复制粘贴到该 View
中。
@model IEnumerable<mvcForumapp.selectStats_Result_Result>
@{
ViewBag.Title = "Main";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<div id="secondary_nav">
<ul class="left" id="breadcrumb">
<li class="first"><a href="">MVC-Forum Community</a></li>
</ul>
</div>
<div class="clear" id="content">
<a id="j_content"></a>
<h2 class="hide">
Board Index</h2>
<div class="clearfix" id="board_index">
<div class="no_sidebar clearfix" id="categories">
<!-- CATS AND FORUMS -->
<div class="category_block block_wrap">
<h3 class="maintitle" id="category_47">
<a title="View category" href="../Home/Main">Technology related questions</a></h3>
<div class="table_wrap">
<table summary="Forums within the category 'GimpTalk'" class="ipb_table">
<tbody>
<tr class="header">
<th class="col_c_icon" scope="col">
</th>
<th class="col_c_forum" scope="col">
Forum
</th>
<th class="col_c_stats stats" scope="col">
Stats
</th>
<th class="col_c_post" scope="col">
Last Post Info
</th>
</tr>
<tr class="row1">
<td class="altrow">
</td>
<td>
@foreach (var item in Model)
{
string techname = item.TechName;
@Html.ActionLink(techname, "Details", "Home", new { TechID = item.TechnID }, null) <br />
<br />
@Html.DisplayFor(modelItem => item.TechDesc)
<br />
<br />
}
</td>
<td class="altrow stats">
@foreach (var item in Model)
{
@Html.DisplayFor(modelItem => item.Totalposts)
@Html.Label(" ");
@Html.Label("Topics")
<br />
if (item.ReplyCount != null)
{
@Html.DisplayFor(modelItem => item.ReplyCount)
@Html.Label(" ");
@Html.Label("Replies")
<br />
<br />
}
else
{
@Html.DisplayFor(modelItem => item.ReplyCount)
@Html.Label(" ");
@Html.Label("0 Replies")
<br />
<br />
}
}
</td>
<td>
@foreach (var item in Model)
{
if (item.DatePosted != null)
{
DateTime dt = Convert.ToDateTime(item.DatePosted);
string strDate = dt.ToString("dd MMMM yyyy - hh:mm tt");
@Html.Label(strDate)
<br />
}
else
{
<br />
}
if (item.QuestionTitle != null)
{
@Html.Label("IN : ");
@Html.Label(" ");
string QuestionTitle = item.QuestionTitle;
@Html.ActionLink(QuestionTitle, "displayIndividual", "Display", new { QuestionID = item.QuestionID }, null)
<br />
}
else
{
<br />
}
if (item.Username != null)
{
@Html.Label("By : ");
@Html.Label(" ");
string User = item.Username;
@Html.ActionLink(User, "Details", "Home", new { Username = item.Username }, null) <br />
<br />
}
else
{
@Html.ActionLink("Start New Topic", "PostQuestion", "Home", new { TechID = item.TechnID }, null)
<br />
<br />
}
}
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
之前我将默认路由视图设置为注册页面的 Index,现在让我们更改它,这样当有人访问时,默认视图将显示问题列表。
App_Start 文件夹中的 RouteConfig.cs 文件需要做如下更改:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Technology", action = "Index", id = UrlParameter.Optional }
);
}
这是你运行应用程序时视图的样子:
好的,由于我的每种技术下都有问题,所以都显示出来了。在 tblTechnology 表中添加一种新技术并运行你的应用程序,当该技术下没有问题时,你将有机会在该技术下发布新问题。
目前我们还没有为此实现任何功能,所以它会显示一个错误页面。我们稍后会看到如何发布新问题。首先,让我向你展示如何显示所选技术中所有可用的问题。
我将向现有的 Technology
控制器添加一些方法,或者你也可以根据需要添加一个新的控制器。我正在向现有的控制器,即 Technology
,添加一个方法,如下所示:
public ActionResult DisplayQuestions()
{
int TechID = Convert.ToInt16(Request.QueryString["TechID"].ToString());
List<mvcForumapp.QuestionList_Result> disp = db.QuestionList(TechID).ToList();
return View(disp);
}
通过右键点击 DisplayQuestions
创建一个视图,并用以下代码替换:
@model IEnumerable<mvcForumapp.QuestionList_Result>
@{
ViewBag.Title = "Details";
}
<style type="text/css">
.disabled
{
/* Text and background colour, medium red on light yellow */
float: right;
margin-right: 20px;
background: #999;
background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#dadada), to(#f3f3f3));
border-top: 1px solid #c5c5c5;
border-right: 1px solid #cecece;
border-bottom: 1px solid #d9d9d9;
border-left: 1px solid #cecece;
color: #8f8f8f;
box-shadow: none;
-moz-box-shadow: none;
-webkit-box-shadow: none;
cursor: not-allowed;
text-shadow: 0 -1px 1px #ebebeb;
}
.active
{
box-shadow: none;
-moz-box-shadow: none;
-webkit-box-shadow: none;
cursor: allowed;
}
</style>
<br />
<br />
<div style="float: left; margin-left: 20px;">
@Html.ActionLink("Back", "Index", "Technology")
</div>
<div id='ipbwrapper'>
<ul class="comRigt">
<li>
<br />
<img alt="Start New Topic" src="http://www.gimptalk.com/public/style_images/master/page_white_add.png" />
@if (Session["UserName"] != null)
{
int techID = Convert.ToInt16(@Request.QueryString["TechID"]);
if (Model.Count() == 0)
{
@Html.ActionLink("Start New Topic", "PostQuestion", "Question",
new { @class = "active", onclick = "javascript:return true;", TechID = techID }, null)
}
else
{
foreach (var item in Model)
{
@Html.ActionLink("Start New Topic", "PostQuestion", "Question",
new { @class = "active", onclick = "javascript:return true;", TechID = item.TechID }, null)
break;
}
}
}
else
{
int techID = Convert.ToInt16(@Request.QueryString["TechID"]);
if (Model.Count() == 0)
{
@Html.ActionLink("Start New Topic", "PostQuestion", "Home",
new {title = "Please login to post Questions", @class = "disabled",
onclick = "javascript:return false;", TechID = techID })
}
else
{
foreach (var item in Model)
{
@Html.ActionLink("Start New Topic", "PostQuestion", "Home",
new {title = "Please login to post Questions", TechID = item.TechID,
@class = "disabled", onclick = "javascript:return false;" })
break;
}
}
}
</li>
</ul>
<br />
@if (Model.Count() != 0)
{
<div class="category_block block_wrap">
<table id="forum_table" summary="Topics In This Forum "GimpTalk News and Updates""
class="ipb_table topic_list">
<div class="maintitle">
<span class="main_forum_title">
@foreach (var item in Model)
{
string strTopic = "A forum where you can post questions regarding " + item.TechName;
@Html.Label("Topic", strTopic)
break;
}
</span>
</div>
<tbody>
<tr class="header">
<th class="col_f_icon" scope="col">
</th>
<th class="col_f_topic" scope="col">
Topic
</th>
<th class="col_f_starter short" scope="col">
Started By
</th>
<th class="col_f_views stats" scope="col">
Stats
</th>
<th class="col_f_post" scope="col">
Last Post Info
</th>
</tr>
<tr id="trow_49752" class="row1">
<td class="short altrow">
</td>
<td class="__topic __tid49752" id="anonymous_element_3">
@foreach (var item in Model)
{
<br />
string QuestionTitle = item.QuestionTitle;
@Html.ActionLink(QuestionTitle, "displayIndividual",
"Display", new { QuestionID = item.QuestionID }, null)
<br />
<br />
}
</td>
<td class="short altrow">
@foreach (var item in Model)
{
<br />
string QuestionTitle = item.UserName;
@Html.ActionLink(QuestionTitle, "Details",
"Home", new { Username = item.UserName }, null)
<br />
<br />
}
</td>
<td class="stats">
<ul>
<li>
@foreach (var item in Model)
{
@Html.DisplayFor(modelItem => item.ReplyCount)
@Html.Label(" ");
@Html.Label("Replies")
<br />
@Html.DisplayFor(modelItem => item.viewCount)
@Html.Label(" ");
@Html.Label("Views")
<br />
<br />
}
</li>
</ul>
</td>
<td class="altrow">
@foreach (var item in Model)
{
if (item.date != null)
{
DateTime dt = Convert.ToDateTime(item.date);
string strDate = dt.ToString("dd MMMM yyyy - hh:mm tt");
@Html.Label(strDate)
}
else
{
DateTime dt = Convert.ToDateTime(item.DatePosted);
string strDate = dt.ToString("dd MMMM yyyy - hh:mm tt");
@Html.Label(strDate)
}
<br />
@Html.Label("By : ")
@Html.Label(" ")
if (item.RepliedName != null)
{
string User = item.RepliedName;
@Html.ActionLink(User, "Details", "Home", new { Username = item.RepliedName }, null)
}
else
{
string User = item.UserName;
@Html.ActionLink(User, "Details", "Home", new { Username = item.UserName }, null)
}
<br />
<br />
}
</td>
</tr>
</tbody>
</table>
</div>
}
else
{
<div class="category_block block_wrap">
<table id="forum_table1" summary="Topics In This Forum "GimpTalk
News and Updates"" class="ipb_table topic_list">
<div class="maintitle">
<span style="font-size:larger; margin-left:450px;">No topics available</span>
</div>
</table>
</div>
}
@if (Model.Count() != 0)
{
<ul class="comRigt">
<li>
<br />
<img alt="Start New Topic"
src="http://www.gimptalk.com/public/style_images/master/page_white_add.png" />
@if (Session["UserName"] != null)
{
foreach (var item in Model)
{
@Html.ActionLink("Start New Topic", "PostQuestion", "Question",
new { @class = "active", onclick = "javascript:return true;", TechID = item.TechID }, null)
break;
}
}
else
{
foreach (var item in Model)
{
@Html.ActionLink("Start New Topic", "PostQuestion", "Home",
new {title = "Please login to post Questions", TechID = item.TechID,
@class = "disabled", onclick = "javascript:return false;" })
break;
}
}
</li>
</ul>
}
</div>
效果是,通过在上图中选择特定的技术,将显示与该技术相关的所有问题。
如果用户已登录,你可以在此开始一个新主题或发布一个新问题。
到目前为止一切顺利。现在让我们来完成最后一步,即创建问题和发布回复。首先,让我们看看如何创建问题。
在模型中创建一个名为 Questions
的类,你的类应该如下所示:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel.DataAnnotations;
namespace mvcForumapp.Models
{
public class Questions
{
[Required(ErrorMessage = "Title required")]
[Display(Name = "Enter Title")]
[StringLength(20, MinimumLength = 4)]
public string TopicTitle { get; set; }
[Required(ErrorMessage = "Description required")]
[Display(Name = "Enter Description")]
[StringLength(20, MinimumLength = 10)]
public string TopicDescription { get; set; }
[Required(ErrorMessage = "Content required")]
[StringLength(Int32.MaxValue, MinimumLength = 10)]
public string TopicContent { get; set; }
}
创建一个名为 Question
的控制器并用以下代码替换:
public class QuestionController : Controller
{
[HttpGet]
public ActionResult PostQuestion()
{
int techID = Convert.ToInt16(Request.QueryString["TechID"].ToString());
return View();
}
}
这是你路由到 PostQuestion
视图时看到的样子:
在同一个控制器中创建另一个带有 HttpPost 的方法来发布问题。
[HttpPost]
public ActionResult PostQuestion(mvcForumapp.Models.Questions user)
{
int techID = 0;
if (ModelState.IsValid)
{
techID = Convert.ToInt16(Request.QueryString["TechID"].ToString());
using (var db = new newForumDBEntities())
{
var userdets = db.tblQuestions.CreateObject();
userdets.TechID = Convert.ToInt16(Request.QueryString["TechID"].ToString());
userdets.QuestionTitle = user.TopicTitle;
userdets.QuestionDesc = user.TopicContent;
userdets.DatePosted = DateTime.Now;
userdets.UserName = Session["UserName"].ToString();
userdets.viewCount = 0;
userdets.ReplyCount = 0;
db.tblQuestions.AddObject(userdets);
db.SaveChanges();
return RedirectToAction("DisplayQuestions", "Technology", new { TechID = techID });
}
}
return View(user);
}
成功发布后,你将路由到该技术的问题列表页面。
你可以用同样的方式处理回复,即在模型
中创建一个名为 Replys
的类,并为其添加必要的视图
和控制器
。当一个帖子有回复时,它会像这样显示:
要查看带有回复的问题,请为相应的控制器和视图添加一个控制器
和视图
。你在控制器中的代码应该如下所示:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Data.SqlClient;
using System.Data;
namespace mvcForumapp.Controllers
{
public class Question_AnswerController : Controller
{
newForumDBEntities db = new newForumDBEntities();
int vwcnt = 0;
[HttpGet]
public ActionResult displayQuestionwithAnswers()
{
var paramQuesID = new SqlParameter("@QuestionID", SqlDbType.Int);
var paramRepCnt = new SqlParameter("@viewcount", SqlDbType.Int);
int quesID = Convert.ToInt16(Request.QueryString["QuestionID"].ToString());
paramQuesID.Value = quesID;
var viewcount = db.tblQuestions.Where(e1 => e1.QuestionID == quesID).FirstOrDefault();
vwcnt = viewcount.viewCount;
if (vwcnt == 0)
{
vwcnt++;
paramRepCnt.Value = vwcnt;
var v = db.ExecuteStoreCommand("UPDATE tblQuestions SET viewCount = " +
"@viewcount WHERE QuestionID = @QuestionID", paramRepCnt, paramQuesID);
}
else
{
vwcnt = vwcnt + 1;
paramRepCnt.Value = vwcnt;
var v = db.ExecuteStoreCommand("UPDATE tblQuestions SET viewCount =" +
" @viewcount WHERE QuestionID = @QuestionID", paramRepCnt, paramQuesID);
}
List<mvcForumapp.Questionwithreplys_Result> disp = db.Questionwithreplys(quesID).ToList();
return View(disp);
}
[HttpGet]
public ActionResult GetPhoto()
{
//RouteData.Values["QuesID"]
int quesID = Convert.ToInt16(Request.QueryString["QuestionID"]);
byte[] photo = null;
var usrname = (from a in db.tblQuestions
where a.QuestionID == quesID
select new { a.UserName });
var v = db.tblUsers.Where(p => p.UserName ==
usrname.FirstOrDefault().UserName).Select(img => img.Photo).FirstOrDefault();
photo = v;
return File(photo, "image/jpeg");
}
[HttpGet]
public ActionResult ReplyPhoto()
{
//RouteData.Values["QuesID"]
int quesID = Convert.ToInt16(Request.QueryString["QuestionID"]);
byte[] photo = null;
var usrname = (from a in db.tblReplies
where a.ReplyID == quesID
select new { a.UserName });
var v = db.tblUsers.Where(p => p.UserName ==
usrname.FirstOrDefault().UserName).Select(img => img.Photo).FirstOrDefault();
photo = v;
return File(photo, "image/jpeg");
}
}
}
相应控制器的视图:
@model IEnumerable<mvcForumapp.Questionwithreplys_Result>
@{
ViewBag.Title = "Index";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<style type="text/css">
.disabled
{
/* Text and background colour, medium red on light yellow */
float: right;
margin-right: 20px;
background: #999;
background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#dadada), to(#f3f3f3));
border-top: 1px solid #c5c5c5;
border-right: 1px solid #cecece;
border-bottom: 1px solid #d9d9d9;
border-left: 1px solid #cecece;
color: #8f8f8f;
box-shadow: none;
-moz-box-shadow: none;
-webkit-box-shadow: none;
cursor: not-allowed;
text-shadow: 0 -1px 1px #ebebeb;
}
.active
{
box-shadow: none;
-moz-box-shadow: none;
-webkit-box-shadow: none;
cursor: allowed;
}
</style>
<br />
<div style="float: left; margin-left: 20px;">
@foreach (var item in Model)
{
@Html.ActionLink("Back", "DisplayQuestions",
"Technology", new { TechID = item.TechID }, null)
break;
}
</div>
<div class="topic_controls">
<br />
<ul class="comRigt" runat="server" id="lnkTopic">
<li>
<img alt="Add Reply"
src="http://www.gimptalk.com/public/style_images/master/arrow_rotate_clockwise.png" />
@if (Session["UserName"] != null)
{
foreach (var item in Model)
{
@Html.ActionLink("Add Reply", "PostReply", "Home",
new { @class = "active", onclick = "javascript:return true;",
QuestionID = item.QuestionID, TechID = item.TechID }, null)
break;
}
}
else
{
foreach (var item in Model)
{
@Html.ActionLink("Add Reply", "PostReply", "Home",
new { title = "Please login to post replys", TechID = item.QuestionID,
@class = "disabled", onclick = "javascript:return false;" })
break;
}
}
</li>
</ul>
<br />
<h2 class="maintitle">
<span class="main_topic_title">
@foreach (var item in Model)
{
string strTopic = item.QuestionTitle;
@Html.Label("Topic", strTopic)
break;
}
</span>
</h2>
<br />
<div class="post_wrap">
<h3>
<span class="author vcard">
@foreach (var item in Model)
{
string User = item.quesaskedby;
@Html.ActionLink(User, "Details", "Home", new { Username = item.quesaskedby }, null)
break;
}
@*<asp:linkbutton id="lnkUsername" runat="server"
text='<%#Eval("UserName") %>' font-underline="false"></asp:linkbutton>*@
</span>
</h3>
<div class="authornew">
<ul>
<li class="avatar">
@foreach (var item in Model)
{
<img alt="" src="@Url.Action("GetPhoto",
"Question_Answer", new { QuestionID = item.QuestionID })"
height="100" width="100" class="photo" />
break;
}
</li>
</ul>
</div>
<div class="postbody">
<p class="postnew">
@foreach (var item in Model)
{
DateTime dt = Convert.ToDateTime(item.DatePosted);
string strDate = dt.ToString("dd MMMM yyyy - hh:mm tt");
@Html.Label(strDate)
break;
}
@*<asp:label id="lblDateposted" text='<%#Eval("DatePosted") %>' font-underline="false"
runat="server" cssclass="edit"></asp:label>*@
</p>
<br />
<br />
<div class="post entry-content ">
@foreach (var item in Model)
{
@Html.Label(item.QuestionDesc)
break;
}
</div>
</div>
</div>
<br />
<br />
<br />
<ul style="background-color: #e4ebf3; text-align: right; background-image:
url(http://www.gimptalk.com/public/style_images/master/gradient_bg.png);
background-repeat: repeat-x; background-position: 40%; font-size: 1em; text-align: right;
padding: 6px 10px 10px 6px; clear: both;">
<li>
<img alt="Add Reply" src="http://www.gimptalk.com/public/style_images/master/comment_add.png" />
@if (Session["UserName"] != null)
{
foreach (var item in Model)
{
@Html.ActionLink("Add Reply", "PostReply", "Home",
new { @class = "active", onclick = "javascript:return true;",
QuestionID = item.QuestionID, TechID = item.TechID }, null)
break;
}
}
else
{
foreach (var item in Model)
{
@Html.ActionLink("Add Reply", "PostReply", "Home",
new { title = "Please login to post replys", @class = "disabled",
onclick = "javascript:return false;", TechID = item.QuestionID })
break;
}
}
</li>
</ul>
</div>
<br />
<br />
<div>
@foreach (var item in Model)
{
if (item.ReplyUser != null)
{
<div class="topic_controls">
<div class="post_wrap">
<h3>
@if (item.ReplyUser != null)
{
<span class="author vcard">
@Html.ActionLink(item.ReplyUser.ToString(),
"Details", "Home", new { Username = item.ReplyUser },
null)</span>
}
<br />
</h3>
<div class="authornew">
<ul>
<li class="avatar">
@if (item.ReplyID != null)
{
<img alt="" src="@Url.Action("ReplyPhoto",
"Question_Answer", new { QuestionID = item.ReplyID })"
height="100" width="100" class="photo" />
}
<br />
</li>
</ul>
</div>
<div class="postbody">
<p class="postnew">
@if (item.date != null)
{
@Html.Label(item.date.Value.ToString("dd MMMM yyyy - hh:mm tt"))
}
<br />
</p>
<br />
<br />
<div class="postentry-content ">
@if (item.ReplyMsg != null)
{
@Html.Label(item.ReplyMsg)
}
<br />
</div>
@if (item.ReplyID != null)
{
<ul style="background-color: #e4ebf3; text-align: right; background-image: url(
http://www.gimptalk.com/public/style_images/master/gradient_bg.png);
background-repeat: repeat-x; background-position: 40%; font-size: 1em; text-align: right;
padding: 6px 10px 10px 6px; clear: both;">
<li>
<img alt="Add Reply" src="http://www.gimptalk.com/public/style_images/master/comment_add.png" />
@*<asp:linkbutton id="lnkpostReply" runat="server"
onclick="lnkpostReply_Click" text="Reply"></asp:linkbutton>*@
@if (Session["UserName"] == null)
{
@Html.ActionLink("Add Reply", "PostReply", "Home",
new { title = "Please login to post replys", @class = "disabled",
onclick = "javascript:return false;", TechID = item.QuestionID })
}
else
{
@Html.ActionLink("Add Reply", "PostReply", "Home",
new { @class = "active", onclick = "javascript:return true;",
QuestionID = item.QuestionID, TechID = item.TechID }, null)
}
</li>
</ul>
}
</div>
</div>
</div>
}
}
</div>
这将产生如下结果:
就是这样,论坛应用程序完成了。
下载代码,并通过使用给定的表创建数据库并向应用程序添加实体模型来测试它。请查看我的示例视频以获取演示,视频中没有音频,只是为了展示应用程序如何运行的概览。如果你有任何疑问,请随时提问。