65.9K
CodeProject 正在变化。 阅读更多。
Home

LaxCraft - 示例 MVC3 网站

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.74/5 (9投票s)

2011 年 8 月 1 日

CPOL

5分钟阅读

viewsIcon

39665

一个很好的示例应用程序,它利用了 MVC3、Castle Windsor 和 Fluent-NHibernate。

引言

LaxCraft 是一个 ASP.NET MVC3 站点,允许一支青年队跟踪他们在场上和场下的努力。它目前仅设计用于支持一支球队,并且是为长曲棍球(因此得名)设计的,但它可以轻松发展为支持多支球队甚至多个联赛和运动项目。在我赛季结束后,我没有直接放弃这个网站,而是决定拆解这个应用程序并突出一些很酷的功能,以便其他人可以从中学习,因为它可能比典型的 Twitter Feed 或 Hello World 应用程序更有趣。

背景

该网站利用了以下技术

  • Castle Windsor 用于依赖注入(Ninject 或其他 IoC 产品也可以)
  • Fluent NHibernate,一个建立在 NHibernate 之上的优秀层,它允许无 XML 配置,并且支持真正的 DDD,因为它会为您构建数据库表
  • Razor 视图用于呈现,我觉得它比标准的 ASP.NET 视图引擎更简洁
  • Telerik MVC UI 扩展,在编辑或创建新闻项目时提供所见即所得编辑器

使用代码

您可以在 http://laxcraft.codeplex.com/ 下载代码和数据库备份,因为代码量太大无法在此 CodeProject 上容纳。

如果您有 SQL Server 实例,您可以还原数据库,或者通过更改 web.configApplicationServices 项的连接字符串并将 SchemaUpdate 设置为 "Build",让 Fluent-NHibernate 构建您的数据库。

总体设计/概述

概述

我构建并使用了 LaxCraft 来激励我的队员们自己练习。他们可以花费数小时玩 XBox,但如今,孩子们不像我们以前那样多出门玩耍。该网站模仿了孩子们玩的某些电子游戏,他们可以在其中获得经验值(XP)并在特定类别中升级。该网站允许玩家登录,然后记录他们为某个特定统计数据付出的努力。统计数据会将这些努力转化为获得的 XP,并相应地调整他们的该统计数据的等级。玩家在某个统计数据中获得的 XP 越多,升级的速度就越慢。该应用程序显示所有玩家的团队视图,以及每个玩家在每个统计数据中的等级,以及玩家视图,详细显示玩家在每个统计数据中的位置,并附有详细的统计数据描述以及他们还需要多少 XP 才能进入下一个级别。

individualstats.png

领域设计

一个用户可以是一个玩家或一个管理员。玩家可以为某些统计数据输入努力并获得 XP,而管理员可以管理所有玩家的所有统计数据。这是一个相当通用的设计,可以灵活地处理任何类型的进度跟踪,而不仅仅是长曲棍球或体育运动。

classDiagram.jpg

关注点

依赖注入

Global.asax.csApplication_Start 方法将调用 BootstrapContainer() 方法,该方法将设置将用于特定接口的具体类。Install 方法将查找当前程序集中所有继承自 IWindsorInstaller 的类,并调用它们的 Install 方法。我认为我更喜欢 Ninject 的依赖注入方式,因为它更简洁易懂,但 Castle 的 Windsor 是我在此项目中使用的。

Global.asax.cs
private static void BootstrapContainer()
{
    container = new WindsorContainer()
        .Install(FromAssembly.This());
    var controllerFactory = new WindsorControllerFactory(container.Kernel);
    ControllerBuilder.Current.SetControllerFactory(controllerFactory);
}
PersistenceInstaller.cs
public void Install(IWindsorContainer container, IConfigurationStore store)
{
    container.AddFacility<persistencefacility>();
}

PersistenceFacility 将告知应用程序它将如何存储数据,并告诉 Fluent 应该与哪个数据库进行数据持久化交互。

PersistenceFacility.cs
protected override void Init()
{
    var config = BuildDatabaseConfiguration();

    Kernel.Register(
        Component.For<isessionfactory>()
            .UsingFactoryMethod(config.BuildSessionFactory),
        Component.For<isession>()
            .UsingFactoryMethod(k => k.Resolve<isessionfactory>().OpenSession())
            .LifeStyle.PerWebRequest);
}

private Configuration BuildDatabaseConfiguration()
{
    return Fluently.Configure()
        .Database(SetupDatabase)
        .Mappings(m => m.AutoMappings.Add(CreateMappingModel()))
        .ExposeConfiguration(ConfigurePersistence)
        .BuildConfiguration();
}

DotNetOpenAuth 身份验证

该项目利用了 Visual Studio 中构建新的 MVC3 Web 应用程序时添加的标准 DotNetOpenAuth 身份验证,但经过了调整:成功身份验证后,系统会检查数据库中是否已存在该电子邮件,如果存在,则将用户映射到特定玩家。如果电子邮件不存在,则会引导用户到一个屏幕,在那里他们可以选择他们是哪个玩家,然后创建关联。如果这是一个生产级别的应用程序,这一点需要稍作调整,但对于该应用程序的用途来说,已经足够了。

AccountController.cs
[HttpPost]
public ActionResult MapPlayer(FormCollection collection)
{
    var user = new User
                   {
                       Username = HttpContext.User.Identity.Name,
                       Email = collection["Email"],
                       Player = team.GetPlayer(int.Parse(collection["PlayerId"]))
                   };
    users.SaveUser(user);
    NHibernateUtil.Initialize(user.Player);

    Session["CurrentUser"] = user;
    
    return RedirectToAction("Index", "Team");
}

团队视图

teamstats.png

控制器 (Controller)

TeamController 会注入一个 ITeamRepository,这使得它能够获取所有团队成员和统计数据。

public class TeamController : Controller
{
    private readonly ITeamRepository team;

    public TeamController(ITeamRepository team)
    {
        this.team = team;
    }

    public ActionResult Index()
    {
        ViewBag.Players = team.GetPlayers().OrderBy(p => p.Name).ToList();
        ViewBag.Stats = team.GetStats().OrderBy(s => s.Order).ToList();
    
        return View(ViewBag);
    }

视图

视图使用 Razor 语法构建一个表,通过遍历 ViewBag 中的统计数据以及团队中的每个玩家。由于 XP 和等级的计算方式,这里可能有一些性能优化或更好的缓存空间。PlayerStat 上的 GetLevel 方法中有一些工作,该方法对每个玩家的每个统计数据都会被调用。所以在团队页面上,如果您有 10 个玩家和 10 个统计数据,该方法将被调用 100 次。

可能有些前端开发者看到那些 Table 标签会皱眉,但我相信如果您的内容确实是数据并且应该放在表格中,那么使用表格而不是 div 标签是可以接受的。

利用了一个名为 TableSorter 的 jQuery 插件,允许用户按特定统计数据排序表格。利用了一个名为 jQueryTools 的 jQuery 插件用于工具提示,在玩家详情页面上,动画进度条利用了 Twits jQuery Progress Bar

<table id="teamTable" class="tablesorter">
<thead>
    <tr>
        <th>
            Name
        </th>
        @foreach (var stat in ViewBag.Stats) {
            <th>
                <span class="abbreviation">@stat.Abreviation</span>
                <div class="tooltip">
                    @stat.Name - @stat.Description
                </div>
            </th>
        }
    </tr>
</thead>
<tbody>
    @foreach (var player in ViewBag.Players) {
        <tr>
            <td class="playerName">
                @(Html.ActionLink((string)player.Name, "Player", 
                           "Team", new { id = player.Id }, null))
            </td>
            @foreach (var stat in player.GetCompletePlayerStats(ViewBag.Stats)) {
                <td>
                    @{var level = stat.GetLevel();}
                    @{var title = string.Format("level {0}: {1}", 
                            level, LaxCraftSession.LevelTitles[level]);}
                    @{var src = Url.Content("~/content/images/" + level + ".png");}
                    <img src="@src" alt="@title" title="@title" />
                </td>
            }
        </tr>
    }
</tbody>

新闻发布

控制器 (Controller)

Post Controller 非常直接,它会注入一个 IPostRepository,并使用该存储库获取所有帖子进行显示。

private readonly IPostRepository blog;

public PostController(IPostRepository blog)
{
    this.blog = blog;
}

public ActionResult Index()
{
    ViewBag.Posts = blog.GetPosts().OrderByDescending(p => p.CreatedOn);
    ViewBag.Admin = LaxCraftSession.CurrentUser.IsAdministrator;

    return View(ViewBag);
}

视图

Post 视图将遍历每个帖子,显示 HTML 和元数据,包括标签和作者,并显示编辑、删除或创建(如果用户是管理员)的链接。

@foreach (var post in ViewBag.Posts) {
    <div class="postTitle">@post.Title</div>
    <div class="postBody">
        @post.GetHtml()
    </div>  
    <div class="postFooter">
        @string.Format("Posted by {0} on {1:g}", post.CreatedBy, post.CreatedOn);
        <br />
        @if (!string.IsNullOrEmpty(post.Tags)) {
             foreach (var tag in post.Tags.Split(",".ToCharArray())) {
            <span class="tag">@tag</span>
              }
            }
    </div>
  
                if (ViewBag.Admin) {
    <div class="adminAction">
        @Html.ActionLink("Edit", "Edit", new { id = post.Id })
        @Html.ActionLink("Delete", "Delete", 
              new { id = post.Id }, new { rel = "nofollow" })
    </div>
    }
}

所见即所得编辑

Post 的创建和编辑视图使用了 Telerik 的 MVC3 扩展,提供了一个漂亮的富文本编辑器。

@Html.ValidationSummary(true)
    <fieldset>
        <div class="editor-label">
            @Html.LabelFor(model => model.Title)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Title) 
                  @Html.ValidationMessageFor(model => model.Title)
        </div>
        <div class="editor-label">
            @Html.LabelFor(model => model.Body)
        </div>
        <div class="editor-field">
            @Html.TextAreaFor(model => model.Body) 
                 @Html.ValidationMessageFor(model => model.Body)
             @{ Html.Telerik().Editor()
                    .Name("editor")
                    .HtmlAttributes(new { style = "height:400px" })
                    .Encode(false)
                    .Value(@<text>
                        <p>
                            <img src="@Url.Content("~/Content/Images/33.png")" 
                              alt="Editor for ASP.NET MVC logo" 
                              style="display:block;margin-left:auto;margin-right:auto;" />
                            Telerik Editor for ASP.NET MVC allows your users 
                            to edit HTML in a familiar, user-friendly way.
                            <br />In this version, the Editor provides 
                            the core HTML editing engine, which includes
                        </p>
                        </text>).Render();
                 }
        </div>
        <div class="editor-label">
            @Html.LabelFor(model => model.Tags)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Tags) 
                  @Html.ValidationMessageFor(model => model.Tags)
        </div>
        <div class="editor-label">
            @Html.LabelFor(model => model.CreatedBy)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.CreatedBy) 
                  @Html.ValidationMessageFor(model => model.CreatedBy)
        </div>
        <p>
            <input type="submit" value="Create" />
        </p>
    </fieldset>
}

这是 Controller 中处理表单提交并将新帖子保存到数据库的方法

[HttpPost]
[ValidateInput(false)]
public ActionResult Create(Post post)
{
    if (ModelState.IsValid)
    {

        var newPost = new Post { Body = post.Body, Tags = post.Tags, 
            Title = post.Title, CreatedBy = post.CreatedBy, 
            CreatedOn = DateTime.Now };
        blog.Save(newPost);

        return RedirectToAction("Index");
    }

    return View(post);
}

历史

  • 文章提交日期:2011 年 8 月 1 日。
© . All rights reserved.