Nancy简单实战之NancyMusicStore(三):完善商品信息与管理

前言

上一篇,我们做了不少准备,并且还把我们NancyFx音乐商城的首页打造好了。这一篇主要是完善我们在首页的商品浏览问题和添加对商品的管理。

下面开始正题:

商品详情

首先是查看单个商品的详情:

先回顾一下我们在Home/Index.cshtml中写的热销商品列表

<ul id="album-list">
    @foreach (var album in Model)
    {
        <li>
            <a href="javascript:;">
                <img alt="@album.Title" src="@album.AlbumArtUrl" />
                <span>@album.Title</span>
            </a>
        </li>
    }
</ul>

我们在上一篇是没有给这个列表项加具体的链接,为了和MVC MusicStore保持一致,我们也用了同样的请求链接

给Home/Index.cshtml列表的a标签加上链接,具体如下:

<li>
    <a href="/store/details/@album.AlbumId">
        <img alt="@album.Title" src="@album.AlbumArtUrl" />
        <span>@album.Title</span>
    </a>
</li>

所以我们要访问单个商品的详情,是要通过 http://domain.com/store/details/id 的形式来访问。

下面我们就在StoreModule中,添加这一块相应的内容,在StoreModule的构造函数中添加下面的代码:

Get["details/{id:int}"] = _ =>
{
    int id = 0;
    if (int.TryParse(_.id, out id))
    {
        string cmd = "public.get_album_details_by_aid";
        var album = DBHelper.QueryFirstOrDefault<AlbumDetailsViewModel>(cmd, new
        {
            aid = id
        }, null, null, CommandType.StoredProcedure);
        if (album != null)
        {
            return View["Details", album];
        }
    }
    return View["Shared/Error"];
};

在路由中details/{id:int},参数id后面加了一个限制,让其只能是整数。

然后通过_.id来取到这个id的具体值,后面的内容就是根据这个id去拿到第一条数据。

最后就是详情页的页面内容了

@inherits Nancy.ViewEngines.Razor.NancyRazorViewBase<NancyMusicStore.ViewModels.AlbumDetailsViewModel>

@{
    ViewBag.Title = "Album - " + Model.Title;   
}

<h2>@Model.Title</h2>

<p>
    <img alt="@Model.Title" src="@Model.AlbumArtUrl" />
</p>

<div id="album-details">
    <p>
        <em>Genre:</em>
        @Model.GenreName
    </p>
    <p>
        <em>Artist:</em>
        @Model.ArtistName
    </p>
    <p>
        <em>Price:</em>
        @String.Format("{0:F}", Model.Price)
    </p>
    <p class="button">
        <a href="/ShoppingCart/AddToCart/@Model.AlbumId">Add to cart</a>        
    </p>
</div>

此时单个商品的详情已经可看到了,效果如下:

details

商品分类列表

下面要查看的是某个分类的所有商品。

我们在通过ajax填充分类信息时,填充的内容是:

$("#categories").append('<li><a href="javascript:;">' + res[i].name + '</a></li>');

现在把具体的链接地址加上,修改成如下代码:

<li><a href="/store/browse/' + res[i].name + '">' + res[i].name + '</a></li>

可以看到,我们要访问某个分类有那些商品,是要通过 http://domain.com/store/browse/name 的形式来访问。

同样在StoreModule的构造函数添加一个相应的处理:

Get["browse/{genre}"] = _ =>
{
    string genre = _.genre;
    ViewBag.Genre = genre;

    string cmd = "public.get_album_list_by_gname";
    var albumList = DBHelper.Query<AlbumListViewModel>(cmd, new
    {
        gname = genre
    }, null, true, null, CommandType.StoredProcedure).ToList();
    return View["Browse", albumList];
};

其对应的页面内容是和首页的热销商品一个形式的,只是数量比较多而已。

在这里点击某个商品也是能跳到对应的详情页的,具体如下:

@inherits Nancy.ViewEngines.Razor.NancyRazorViewBase<List<NancyMusicStore.ViewModels.AlbumListViewModel>>

@{
    ViewBag.Title = "Browse Albums";    
}
<div class="genre">
    <h3><em>@ViewBag.Genre</em> Albums</h3>

    <ul id="album-list">
        @foreach (var album in Model)
        {
            <li>
                <a href="/store/details/@album.AlbumId">
                    <img alt="@album.Title" src="@album.AlbumArtUrl" />
                    <span>@album.Title</span>
                </a>
            </li>
        }
    </ul>
</div>

示例为Disco分类下面的列表页:

disco

到这里,我们已经完成了商品信息浏览的相关处理。

下面就添加对这些商品的管理,这里的管理实则是对专辑(Albums)信息的CURD操作。

商品管理

这些操作的具体内容就不详细展开了。在Modules文件夹下面新建一个StoreManagerModule.cs

具体内容如下(省略了部分代码,占的篇幅有点多) :

using Nancy;
using Nancy.ModelBinding;
using Nancy.Security;
using NancyMusicStore.Common;
using NancyMusicStore.Models;
using NancyMusicStore.ViewModels;
using System;
using System.Data;

namespace NancyMusicStore.Modules
{
    public class StoreManagerModule : NancyModule
    {
        public StoreManagerModule() : base("/storemanager")
        {
            Get["/"] = _ =>
            {
                string cmd = "get_all_albums";
                var list = DBHelper.Query<AlbumListViewModel>(cmd, null, null, true, null, CommandType.StoredProcedure);
                return View["Index", list];
            };

            Get["/create"] = _ =>{};
        
            Post["/create"] = _ =>{};

            Get["/details/{id:int}"] = _ =>{};

            Get["/edit/{id:int}"] = _ =>{};

            Post["/edit"] = _ =>{};

            Get["/delete/{id:int}"] = _ =>{};
        
            Post["/delete/{id:int}"] = _ =>{};

            Get["/getallgenres"] = _ =>{};

            Get["/getallartists"] = _ =>{};
        }
    }
}

视图代码就不放出来了,可以在最后的文章中去下载。

商品管理的效果如下:

manager

我们在_Layout.cshtml中指定了商品管理的入口在导航菜单的【Admin】中

enter

但是现在这种情况下,是任何人都可以对商品信息修改,这显然是不合理的。

在MVC MusicStore中是通过Membership来处理这个问题的。对于这一块我是通过Nancy本身的Forms认证来处理。

Forms认证,有几个重要的地方需要说明一下:

  • 在启动器中要添加Forms认证的支持

  • 实现一个继承IUserMapper接口的UserMapper类

  • 通过IoC去注册这个UserMapper类

  • 在要认证的地方添加this.RequiresAuthentication()即可

我们先在web.config的appSettings中添加一个配置项,用来指定没授权用户的登录url

<add key="logonUrl" value="~/account/logon" />    

在启动器(CustomerBootstrapper.cs)中,是这样进行处理的

protected override void RequestStartup(TinyIoCContainer container, IPipelines pipelines, NancyContext context)
{
    base.RequestStartup(container, pipelines, context);
    //form authentication
    var formsAuthConfiguration = new FormsAuthenticationConfiguration
    {
        RedirectUrl = ConfigHelper.GetAppSettingByKey("logonUrl"),
        UserMapper = container.Resolve<IUserMapper>(),
    };
    FormsAuthentication.Enable(pipelines, formsAuthConfiguration);
}

protected override void ConfigureRequestContainer(TinyIoCContainer container, NancyContext context)
{
    base.ConfigureRequestContainer(container, context);
    container.Register<IUserMapper, UserMapper>();
}

在这里,用的IoC容器也是Nancy本身就支持的TinyIoc,没有用业界流行的AutoFac。

UserMapper的实现如下 :

internal class UserMapper : IUserMapper
{
    public IUserIdentity GetUserFromIdentifier(Guid identifier, NancyContext context)
    {
        string cmd = "public.get_user_by_userid";
        var user = DBHelper.QueryFirstOrDefault<SysUser>(cmd, new
        {
            uid = identifier.ToString()
        }, null, null, CommandType.StoredProcedure);

        return user == null
            ? null
            : new UserIdentity
            {
                UserName = user.SysUserName,
                Claims = new[] { "SystemUser" }
            };           
    }
}

到这一步,Forms的配置已经OK了。

有关Nancy中Forms认证的内容,可以参考之前的博客 Nancy之Forms authentication的简单使用

下面就只要在StoreManagerModule的构造函数中添加 this.RequiresAuthentication();

这时,我们想直接通过入口来打开管理界面,就会跳转到登录界面,要求我们先进行登录才可以。

效果图如下:

logon

可以看到,我们的效果已经达到了。

下面仔细观察一下url,发现参数中还附带了returnUrl,可以让我们登录后跳转到指定的页面。

登录注册相关的操作放在在AccountModule中

下面来看看这个returnUrl的处理

Get["/logon"] = _ =>
{
    var returnUrl = this.Request.Query["returnUrl"];
    ViewBag.returnUrl = returnUrl;
    return View["LogOn"];
};

Post["/logon"] = _ =>
{
    var logonModel = this.Bind<LogOnModel>();

    string cmd = "public.get_user_by_name_and_password";
    var user = DBHelper.QueryFirstOrDefault<SysUser>(cmd, new
    {
        uname = logonModel.SysUserName,
        upwd = logonModel.SysUserPassword
    }, null, null, CommandType.StoredProcedure);

    if (user == null)
    {
        return View["LogOn"];
    }
    else
    {
        MigrateShoppingCart(user.SysUserName);

        var redirectUrl = string.IsNullOrWhiteSpace(logonModel.ReturnUrl) ? "/" : logonModel.ReturnUrl;
        return this.LoginAndRedirect(Guid.Parse(user.SysUserId), fallbackRedirectUrl: redirectUrl);
    }
};

returnUrl是以QueryString的形式存在,所以在打开登录界面的时候,用一个viewbag把它存起来

登录的时候再POST到后台。登录的POST操作用到了Nancy的ModelBinding,与MVC的模型绑定是类似的,可以参考 Nancy之ModelBinding(模型绑定)

所以在LogOnModel中,添加了一个属性用于绑定这个returnUrl。

public class LogOnModel
{
    public string SysUserName { get; set; }
    public string SysUserPassword { get; set; }
    public string ReturnUrl { get; set; }        
}

到这里,商品信息和管理已经完成了。实战的第二篇文章也就结束了。

下面一篇的内容就是要完成购物车的内容。

本文也已经同步到 Nancy之大杂烩

posted @ 2017-01-21 07:50 Catcher8 阅读(...) 评论(...) 编辑 收藏