ASP.NET MVC 2 投票控件






4.77/5 (10投票s)
简单的投票控件,用于 MVC 项目,使用局部视图

引言
这是一个简单的投票控件,用于 MVC 项目。它实现为一个局部视图,因此可以根据需要包含在任何地方。
主要特点
- 易于包含在任何视图中
- 问题、答案和投票存储在 XML 文件中(每个投票控件一个文件)
- 可配置样式
- AJAX 调用更新结果
- 所有投票控件的通用 JavaScript 和样式表
Using the Code
在源代码中,我在默认的 Index.aspx 页面中添加了两个投票控件。这些是通过在视图中使用以下代码包含的:
<%
VoteDataModel petModel = (VoteDataModel)ViewData[VoteDataModel.GetViewDataName("Pet")];
petModel.ControlWidth = 200;
Html.RenderPartial("VoteControl", petModel);
%>
在上面的代码中,我们指定了投票控件的名称 (Pet
) 和宽度 (200
)。
为了使其正常工作,您需要在视图顶部,紧跟在 page 指令下方添加以下这行:
<%@ Import Namespace="VoteControl.Models"%>
另外,在这种我使用了默认母版页的情况下,您必须在母版页的 head
部分添加样式表和 JavaScript 包含:
<link href="~/Content/VoteControl.css" rel="stylesheet" type="text/css" />
<script src='<%= Url.Content("~/Scripts/VoteControl.js") %>' type="text/javascript">
</script>
我将它们添加在 Site.css 的包含之后。如果我们不使用母版页,这些行将直接包含在页面中。
在控制器中,必须使用以下代码构造模型并将其传递给视图:
VoteDataModel model = new VoteDataModel
("Pet", Request.PhysicalApplicationPath + "App_Data\\");
model.Open();
ViewData[model.ViewDataName] = model;
整个页面(在对两个投票进行投票后)如下所示:

命名约定
投票控件的名称在 VoteDataModel
构造函数中指定。它必须是唯一的,并以各种方式使用,例如 XML 文件名 - NamePoll.xml(存储在 app_data 目录中)。
实现
代码由以下新文件组成:
- 模型 - VoteModel.cs
- 控制器 - VoteController.cs
- 局部视图 - VoteControl.ascx
- 脚本 - VoteControl.js
- 样式表 - VoteControl.css
- App_Data - 包含投票控件数据的 XML 文件
文件位于解决方案中您期望的位置,如下所示:

此外,我还修改了 Global.asax,以便将路由 ~/DoVote/{name}/{id} 重定向到 VoteController
。
routes.MapRoute(
"VoteButton", // Route name
"DoVote/{uniqueName}/{voteId}", // URL with parameters
new { controller = "Vote", action = "DoVote" },
new { voteId = @"\d{1,3}" } // voteId can only be numeric, and less than 1000
);
这在按下投票按钮时用于 AJAX 调用。然后,此调用的结果用于使用新的投票/百分比更新控件。
幕后
大部分代码都在模型中(应该如此)。
VodelDataModel
对象包含一个答案列表 (List<AnswerItem>
) 和 IP 地址列表 (List<IpAddressItem>
)。每个答案都有一个 AnswerItem
,并且为每个唯一的客户端 IP 地址添加一个条目。这就是我们确保用户只投票一次的方式。这并不完美 - 请参阅“兴趣点”部分了解更多详细信息。
模型中提供了以下 public
方法:
static string GetViewDataName(string uniqueName) and string ViewDataName
这会获取一个将用于视图数据的名称。我们在控制器和视图中使用它 - 其中有一个 static
方法用于视图,还有一个实例属性用于控制器。
public VoteDataModel(string name, string path)
对象构造函数。
public bool Open()
构造对象后应始终调用 Open。这会从 XML 文件读取数据。
public bool DoVote(int voteId, string ipAddr)
线程安全 - 请参阅最后一节。
这会更新对象和文件。如果投票被接受,则返回 true
;如果找到 IP 地址(即用户已投票),则返回 false
。
public int GetPercentage(int index)
这用于局部视图显示百分比。
public int GetBarLength(int index, int controlWidth)
这用于局部视图显示百分比条。
局部视图 HTML
控件有三个 div
用于不同的视图:

第一个默认显示。其他两个默认在内联样式中设置为 display:none
。根据按下哪个按钮或链接,其中一个被设置为显示,另外两个被设置为 display:none
。
JavaScript
我决定使用纯 JavaScript 进行 AJAX 调用,但我也可以使用 jquery(或 MicrosoftAjax.js 文件)。我选择此方法是因为我希望在此方面尽可能灵活(和透明)。您可能希望将其更改为使用 jquery - 尤其是在现有页面上已经使用 jquery 的情况下。请注意,我已经删除了未使用的脚本(jquery 和 MicrosoftAjax),因为否则下载量会非常大!
还有另外两个函数 - 这些函数用于显示或隐藏与问题、百分比答案或带投票数的答案相关的控件。
样式表
VoteControl.css 样式表中的大多数元素都有类 - 希望名称相当直观。特别是,您可能希望更改颜色以适应您网站的配色方案 - 为此,请注意正常行和交替行可以(并且在示例中)以不同颜色着色。
关注点
- 控件包含在一个
DIV
中,但如果您需要任何特殊格式,最简单的方法可能是将控件包装在另一个DIV
中(在包含局部视图的视图中),并为其应用其他style
属性。示例代码显示了如何浮动“pet”控件,使其可以嵌入文本部分。 - 在控制器中,我构造模型并将其在
ViewData
中传递给视图。这可以通过许多其他方式实现 - 我这样做的方式不一定是最好的方法。您可能希望以适合您特定项目的方式进行。 - 在包含
css
和javascript
文件的地方,我使用了~/...
和Url.Content("~/...")
。这些不同的原因是 CSS 包含是服务器端包含,而 JavaScript 包含是客户端包含。但重要的是,通过使用“~
”,文件路径将始终正确,无论我们在哪里包含此代码,它都会添加虚拟目录(如果需要)。虚拟目录在本地与托管版本不同是很常见的 - 但这两种情况都适用。 - 需要两种类型的线程安全。我们需要确保文件不会同时被两个线程写入,或者在写入时被读取。这是通过对
static
对象进行锁定来实现的。我们还需要确保,当用户选择一个项目并对其进行投票时 - 如果在此期间有另一位用户进行了投票 - 我们需要重新加载文件 - 否则前一次投票将丢失。第一种锁定是悲观锁定(我们等待直到获得独占访问权),第二种是乐观锁定(如果发生冲突,我们则重新加载)。我们必须这样做 - 我们无法阻止所有其他用户 - 因为投票的用户相对来说非常慢,而读取和写入文件非常快。 - 除非用户已注册,否则没有简单的方法可以阻止人们重复投票。我选择使用客户端的 IP 地址,但这本身也可能导致问题,而且绝不完美(用户可以从多台机器投票,多台机器可能共享单个 IP 地址等)。另外,由于 XML 文件中可能累积大量 IP 地址,我决定在一天后删除条目。这意味着坚定的用户可以每天投票。我见过其他人使用 cookie,但同样,坚定的用户可以删除 cookie 并再次投票。因此,如果您真的需要投票是不可篡改的,那么您必须让人们以有效用户的身份登录。即使这样,如果用户可以多次注册,也可能被滥用。
- 我本来可以将投票存储在 SQL Server 数据库中,但我决定使用 XML 文件。这是因为并非所有托管公司都提供免费的 SQL Server 数据库。如果您想修改
VoteDataModel
使其使用数据库,应该只需要修改Open
和Save
方法即可。为了更彻底地修复,可能需要对ParseXML
进行一些重构。 - 我已将样式表和 JavaScript 文件包含在局部视图中。但是,这被
if (false)
包围,这意味着不会生成任何代码。包含这些文件的唯一原因是使 IntelliSense 工作。
历史
- 2011 年 4 月 - 初始版本