Orchard模块开发全接触6:自定义用户注册

我们都知道 Orchard 的用户注册相当简单,现在,我们需要一个自定义的用户注册,现在,开始吧。

 

一:定义实体

Models/CustomerPartRecord.cs:

public class CustomerPartRecord : ContentPartRecord
{
    public virtual string FirstName { get; set; }
    public virtual string LastName { get; set; }
    public virtual string Title { get; set; }
    public virtual DateTime CreatedUtc { get; set; }
}

Models/CustomerPart.cs:

public class CustomerPart : ContentPart<CustomerPartRecord>
{

    public string FirstName
    {
        get { return Record.FirstName; }
        set { Record.FirstName = value; }
    }

    public string LastName
    {
        get { return Record.LastName; }
        set { Record.LastName = value; }
    }

    public string Title
    {
        get { return Record.Title; }
        set { Record.Title = value; }
    }

    public DateTime CreatedUtc
    {
        get { return Record.CreatedUtc; }
        set { Record.CreatedUtc = value; }
    }
}

Models/AddressPartRecord.cs:

public class AddressPartRecord : ContentPartRecord
{
    public virtual int CustomerId { get; set; }
    public virtual string Type { get; set; }
}

Models/AddressPart.cs:

public class AddressPart : ContentPart<AddressPartRecord>
{

    public int CustomerId
    {
        get { return Record.CustomerId; }
        set { Record.CustomerId = value; }
    }

    public string Type
    {
        get { return Record.Type; }
        set { Record.Type = value; }
    }
}

修改Migrations.cs

public int UpdateFrom4()
{
    SchemaBuilder.CreateTable("CustomerPartRecord", table => table
        .ContentPartRecord()
        .Column<string>("FirstName", c => c.WithLength(50))
        .Column<string>("LastName", c => c.WithLength(50))
        .Column<string>("Title", c => c.WithLength(10))
        .Column<DateTime>("CreatedUtc")
        );

    SchemaBuilder.CreateTable("AddressPartRecord", table => table
        .ContentPartRecord()
        .Column<int>("CustomerId")
        .Column<string>("Type", c => c.WithLength(50))
        );

    ContentDefinitionManager.AlterPartDefinition("CustomerPart", part => part
        .Attachable(false)
        );

    ContentDefinitionManager.AlterTypeDefinition("Customer", type => type
        .WithPart("CustomerPart")
        .WithPart("UserPart")
        );

    ContentDefinitionManager.AlterPartDefinition("AddressPart", part => part
        .Attachable(false)
        .WithField("Name", f => f.OfType("TextField"))
        .WithField("AddressLine1", f => f.OfType("TextField"))
        .WithField("AddressLine2", f => f.OfType("TextField"))
        .WithField("Zipcode", f => f.OfType("TextField"))
        .WithField("City", f => f.OfType("TextField"))
        .WithField("Country", f => f.OfType("TextField"))
        );

    ContentDefinitionManager.AlterTypeDefinition("Address", type => type
        .WithPart("CommonPart")
        .WithPart("AddressPart")
        );

    return 5;
}

Handlers/CustomerPartHandler.cs:

public class CustomerPartHandler : ContentHandler
{
    public CustomerPartHandler(IRepository<CustomerPartRecord> repository)
    {
        Filters.Add(StorageFilter.For(repository));
        Filters.Add(new ActivatingFilter<UserPart>("Customer"));
    }
}

注意哦,使用 UsrerPart,必须引用 Orchard.Users,于是乎,我们修改 Dependencies:

name: tminji.shop
antiforgery: enabled
author: tminji.com
website: http://www.tminji.com
version: 1.0.0
orchardversion: 1.0.0
description: The tminji.com module is a shopping module.
Dependencies: Orchard.Projections, Orchard.Forms, Orchard.jQuery, Orchard.jQuery, AIM.LinqJs, Orchard.Knockout, Orchard.Users
features:
    shop:
        Description: shopping module.
        Category: ASample

注意哦,如果我们使用 UserPart,那么,我们就不能再 attach CommonPart,否则会导致 StackOverflowException

Handlers/AddressPartHandler.cs:

public class AddressPartHandler : ContentHandler
{
    public AddressPartHandler(IRepository<AddressPartRecord> repository)
    {
        Filters.Add(StorageFilter.For(repository));
    }
}

然后,Drivers/CustomerPartDriver.cs:

public class CustomerPartDriver : ContentPartDriver<CustomerPart>
{

    protected override string Prefix
    {
        get { return "Customer"; }
    }

    protected override DriverResult Editor(CustomerPart part, dynamic shapeHelper)
    {
        return ContentShape("Parts_Customer_Edit", () => shapeHelper.EditorTemplate(TemplateName: "Parts/Customer", Model: part, Prefix: Prefix));
    }

    protected override DriverResult Editor(CustomerPart part, IUpdateModel updater, dynamic shapeHelper)
    {
        updater.TryUpdateModel(part, Prefix, null, null);
        return Editor(part, shapeHelper);
    }
}

然后,Drivers/AddressPartDriver.cs:

public class AddressPartDriver : ContentPartDriver<AddressPart>
{

    protected override string Prefix
    {
        get { return "Address"; }
    }

    protected override DriverResult Editor(AddressPart part, dynamic shapeHelper)
    {
        return ContentShape("Parts_Address_Edit", () => shapeHelper.EditorTemplate(TemplateName: "Parts/Address", Model: part, Prefix: Prefix));
    }

    protected override DriverResult Editor(AddressPart part, IUpdateModel updater, dynamic shapeHelper)
    {
        updater.TryUpdateModel(part, Prefix, null, null);
        return Editor(part, shapeHelper);
    }
}

Views/EditorTemplates/Parts/Customer.cshtml:

@using System.Web.Mvc.Html
@model TMinji.Shop.Models.CustomerPart
<fieldset>
    <div class="editor-label">@Html.LabelFor(x => x.Title)</div>
    <div class="editor-field">@Html.EditorFor(x => x.Title)</div>

    <div class="editor-label">@Html.LabelFor(x => x.FirstName)</div>
    <div class="editor-field">
        @Html.EditorFor(x => x.FirstName)
        @Html.ValidationMessageFor(x => x.FirstName)
    </div>

    <div class="editor-label">@Html.LabelFor(x => x.LastName)</div>
    <div class="editor-field">
        @Html.EditorFor(x => x.LastName)
        @Html.ValidationMessageFor(x => x.LastName)
    </div>
</fieldset>

Views/EditorTemplates/Parts/Address.cshtml:

@using System.Web.Mvc.Html
@model TMinji.Shop.Models.AddressPart
<fieldset>
    <div class="editor-label">@Html.LabelFor(x => x.Type)</div>
    <div class="editor-field">@Html.EditorFor(x => x.Type)</div>

    <div class="editor-label">@Html.LabelFor(x => x.CustomerId)</div>
    <div class="editor-field">
        @Html.EditorFor(x => x.CustomerId)
        @Html.ValidationMessageFor(x => x.CustomerId)
    </div>
</fieldset>

Placement.info:

<Placement>
  <Place Parts_Product_Edit="Content:1" />
  <Place Parts_Product="Content:0" />
  <Place Parts_Product_AddButton="Content:after" />
  <Place Parts_ShoppingCartWidget="Content:0" />
  <Place Parts_Customer_Edit="Content:0" />
  <Place Parts_Address_Edit="Content:0" />
</Placement>

运行之,可以看到创建了数据库表:

image

 

二:前台准备

控制器 CheckoutController.cs:

using Orchard;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Web.Mvc;
using Orchard.Mvc;
using Orchard.Themes;

namespace TMinji.Shop.Controllers
{
    public class CheckoutController : Controller
    {
        private readonly IOrchardServices _services;
        private Localizer T { get; set; }

        public CheckoutController(IOrchardServices services)
        {
            _services = services;
        }

        [Themed]
        public ActionResult SignupOrLogin()
        {

            return new ShapeResult(this, _services.New.Checkout_SignupOrLogin());
        }
    }

}

创建视图 Checkout.SignupOrLogin.cshtml:

@using Orchard.ContentManagement
@using Orchard.Core.Title.Models
@using TMinji.Shop.Models

@{
    Style.Require("TMinji.Shop.Common");
}
<article>
    <p>@T("Are you a returning customer or a new customer?")</p>

    <ul class="action bullets">
        <li><a href="@Url.Action("Login", "Checkout", new { area = "Skywalker.Webshop" })">I am a <strong>returning customer</strong> and already have an account</a></li>
        <li><a href="@Url.Action("Signup", "Checkout", new { area = "Skywalker.Webshop" })">I am a <strong>new customer</strong></a></li>
    </ul>
</article>

我们为 Common.css 增加一些属性:

ul.bullets li {
    background: url("../images/bullets.png") no-repeat;
    line-height: 30px;
    list-style: none;
    padding-left: 15px;
}
ul.bullets.action li {
    background-position: 0 0;
}
ul.bullets.action li:hover {
    background-position: 1px 0;
}
ul.bullets li a {
    text-decoration: none;
}
ul.bullets li a:hover {
    color: #434343;
}

创建一个 bullets.png 的图片到 Images 下:

bullets

然后,输入 URL:http://localhost:30321/OrchardLocal/tminji.shop/Checkout/SignupOrLogin,得到:

image

现在,修改 ShoppingCartController:

public ActionResult Update(string command, UpdateShoppingCartItemViewModel[] items)
{

    UpdateShoppingCart(items);

    if (Request.IsAjaxRequest())
        return Json(true);

    // Return an action result based on the specified command
    switch (command)
    {
        case "Checkout":
            return RedirectToAction("SignupOrLogin", "Checkout");
        case "ContinueShopping":
            break;
        case "Update":
            break;
    }

    // Return to Index if no command was specified
    return RedirectToAction("Index");
}

以及 CheckoutController:

using Orchard;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Web.Mvc;
using Orchard.Mvc;
using Orchard.Themes;
using Orchard.Localization;
using Orchard.Security;

namespace TMinji.Shop.Controllers
{
    public class CheckoutController : Controller
    {
        private readonly IAuthenticationService _authenticationService;
        private readonly IOrchardServices _services;

        public CheckoutController(IOrchardServices services, IAuthenticationService authenticationService)
        {
            _authenticationService = authenticationService;
            _services = services;
        }

        [Themed]
        public ActionResult SignupOrLogin()
        {

            if (_authenticationService.GetAuthenticatedUser() != null)
                return RedirectToAction("SelectAddress");

            return new ShapeResult(this, _services.New.Checkout_SignupOrLogin());
        }

        [Themed]
        public ActionResult Signup()
        {
            var shape = _services.New.Checkout_Signup();
            return new ShapeResult(this, shape);
        }

        [Themed]
        public ActionResult Login()
        {
            var shape = _services.New.Checkout_Login();
            return new ShapeResult(this, shape);
        }

        [Themed]
        public ActionResult SelectAddress()
        {
            var shape = _services.New.Checkout_SelectAddress();
            return new ShapeResult(this, shape);
        }

        [Themed]
        public ActionResult Summary()
        {
            var shape = _services.New.Checkout_Summary();
            return new ShapeResult(this, shape);
        }
    }
}

实际上,可以好好看看这个代码,这里告诉了我们,如果当前用户没有登录,应该怎么重定向,很重要哦。

 

三:后台准备

ViewModels/SignupViewModel.cs,注意哦,这里是 ViewModels 文件夹:

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace TMinji.Shop.ViewModels
{
    public class SignupViewModel : IValidatableObject
    {
        [StringLength(10), Display(Name = "Title")]
        public string Title { get; set; }

        [StringLength(50), Required, Display(Name = "Firstname")]
        public string FirstName { get; set; }

        [StringLength(50), Required, Display(Name = "Lastname")]
        public string LastName { get; set; }

        [StringLength(255), Required, DataType(DataType.EmailAddress), Display(Name = "Email")]
        public string Email { get; set; }

        [StringLength(255), Required, DataType(DataType.Password), Display(Name = "Password")]
        public string Password { get; set; }

        [StringLength(255), Required, DataType(DataType.Password), Compare("Password"), Display(Name = "Repeat password")]
        public string RepeatPassword { get; set; }

        public bool ReceiveNewsletter { get; set; }
        public bool AcceptTerms { get; set; }

        public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
        {
            if (!AcceptTerms)
                yield return new ValidationResult("You need to accept our terms and conditions in order to make use of our services");
        }
    }

}

注意,引用:

image

Views/Checkout.Signup.cshtml:

@using Orchard.ContentManagement
@using Orchard.Core.Title.Models
@using TMinji.Shop.Models
@using TMinji.Shop.ViewModels
@{
    var signup = (SignupViewModel)Model.Signup;

    Style.Require("TMinji.Shop.Common");
}

<h2>@T("New customer")</h2>
<p>@T("Please fill out the form below")</p>

@Html.ValidationSummary()

@using (Html.BeginFormAntiForgeryPost(Url.Action("Signup", "Checkout", new { area = "TMinji.Shop" })))
{
    <article class="form">
        <fieldset>
            <ul>
                <li>
                    <div class="field-label">@Html.LabelFor(m => signup.Title, T("Title"))</div>
                    <div class="field-editor">@Html.EditorFor(m => signup.Title)</div>
                </li>
                <li>
                    <div class="field-label">@Html.LabelFor(m => signup.FirstName, T("First name"))</div>
                    <div class="field-editor">@Html.EditorFor(m => signup.FirstName)</div>
                </li>
                <li>
                    <div class="field-label">@Html.LabelFor(m => signup.LastName, T("Last name"))</div>
                    <div class="field-editor">@Html.EditorFor(m => signup.LastName)</div>
                </li>
            </ul>
        </fieldset>

        <fieldset>
            <ul>
                <li>
                    <div class="field-label">@Html.LabelFor(m => signup.Email, T("Email"))</div>
                    <div class="field-editor">@Html.EditorFor(m => signup.Email)</div>
                </li>
                <li>
                    <div class="field-label">@Html.LabelFor(m => signup.Password, T("Password"))</div>
                    <div class="field-editor">@Html.EditorFor(m => signup.Password)</div>
                </li>
                <li>
                    <div class="field-label">@Html.LabelFor(m => signup.RepeatPassword, T("Repeat password"))</div>
                    <div class="field-editor">@Html.EditorFor(m => signup.RepeatPassword)</div>
                </li>
            </ul>
        </fieldset>

        <fieldset>
            <ul>
                <li>
                    <div class="checkbox-and-label">
                        <div class="checkbox">@Html.CheckBoxFor(m => signup.ReceiveNewsletter)</div>
                        <div class="label">@Html.LabelFor(m => signup.ReceiveNewsletter, T("Subscribe to our mailing list"))</div>
                    </div>
                </li>
                <li>
                    <div class="checkbox-and-label">
                        <div class="checkbox">@Html.CheckBoxFor(m => signup.AcceptTerms)</div>
                        <div class="label">
                            <label for="@Html.FieldIdFor(m => signup.AcceptTerms)">
                                @Html.Raw(T("I have read and accept the <a href=\"{0}\" target=\"_blank\">Terms and Conditions</a>", "#").ToString())
                            </label>
                        </div>
                    </div>
                </li>
            </ul>
        </fieldset>

        <footer class="commands">
            <ul>
                <li class="align left"><a href="#">@T("Cancel")</a></li>
                <li class="align right"><button type="submit">@T("Next")</button></li>
            </ul>
        </footer>
    </article>
}

Styles/common.css (snippet):

article.form
{
    padding: 10px;
    background: #f6f6f6;
}
article.form input[type="text"], input[type="password"], input[type="email"], input[type="tel"]
{
    width: 250px;
}
article.form fieldset{
    margin-bottom: 20px;
}
article.form fieldset ul {
    list-style: none;
    margin: 0;
}
article.form fieldset ul li {
    margin-bottom: 3px;
}
article.form fieldset ul li:after {
    clear:both;
    height:0;
    content:".";
    display:block;
    visibility:hidden;
    zoom:1;
}
article.form fieldset ul li .field-label {
    float: left;
    width: 250px;
}
article.form fieldset ul li .field-editor {
    float: left;
}
article.form fieldset ul li .field-label label:after {
    content: ":";
}
article.form .checkbox-and-label:after {
    clear:both;
    height:0;
    content:".";
    display:block;
    visibility:hidden;
    zoom:1;
}
article.form .checkbox-and-label .checkbox {
    float: left;width: 25px;
}
article.form .checkbox-and-label .label {
    float: left;
}
article.form footer.commands {
    padding-top: 20px;
}
article.form footer.commands ul {
    list-style: none;
    margin: 0;
}
article.form footer.commands ul:after {
    clear:both;
    height:0;
    content:".";
    display:block;
    visibility:hidden;
    zoom:1;
}
article.form footer.commands ul button {
    margin: 0;
}

Controllers/CheckoutController.cs (snippet):

[HttpPost]
public ActionResult Signup(SignupViewModel signup)
{
    if (!ModelState.IsValid)
        return new ShapeResult(this, _services.New.Checkout_Signup(Signup: signup));

    // TODO: Create a new account for the customer
    return RedirectToAction("SelectAddress");
}

Services/ICustomerService.cs:

public interface ICustomerService : IDependency
{
    CustomerPart CreateCustomer(string email, string password);
}

Services/CustomerService.cs:

using Orchard;
using Orchard.ContentManagement;
using Orchard.Security;
using Orchard.Services;
using Orchard.Users.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TMinji.Shop.Models;

namespace TMinji.Shop.Services
{
    public class CustomerService : ICustomerService
    {
        private readonly IOrchardServices _orchardServices;
        private readonly IMembershipService _membershipService;
        private readonly IClock _clock;

        public CustomerService(IOrchardServices orchardServices, IMembershipService membershipService, IClock clock)
        {
            _orchardServices = orchardServices;
            _membershipService = membershipService;
            _clock = clock;
        }

        public CustomerPart CreateCustomer(string email, string password)
        {
            // New up a new content item of type "Customer"
            var customer = _orchardServices.ContentManager.New("Customer");

            // Cast the customer to a UserPart
            var userPart = customer.As<UserPart>();

            // Cast the customer to a CustomerPart
            var customerPart = customer.As<CustomerPart>();

            // Set some properties of the customer content item (via UserPart and CustomerPart)
            userPart.UserName = email;
            userPart.Email = email;
            userPart.NormalizedUserName = email.ToLowerInvariant();
            userPart.Record.HashAlgorithm = "SHA1";
            userPart.Record.RegistrationStatus = UserStatus.Approved;
            userPart.Record.EmailStatus = UserStatus.Approved;

            // Use IClock to get the current date instead of using DateTime.Now (see http://skywalkersoftwaredevelopment.net/orchard-development/api/iclock)
            customerPart.CreatedUtc = _clock.UtcNow;

            // Use Ochard's MembershipService to set the password of our new user
            _membershipService.SetPassword(userPart, password);

            // Store the new user into the database
            _orchardServices.ContentManager.Create(customer);

            return customerPart;
        }
    }

}

Controllers/CheckoutController.cs (snippet):

[HttpPost]
public ActionResult Signup(SignupViewModel signup)
{
    if (!ModelState.IsValid)
        return new ShapeResult(this, _services.New.Checkout_Signup(Signup: signup));

    var customer = _customerService.CreateCustomer(signup.Email, signup.Password);
    customer.FirstName = signup.FirstName;
    customer.LastName = signup.LastName;
    customer.Title = signup.Title;

    _authenticationService.SignIn(customer.User, true);

    return RedirectToAction("SelectAddress");
}

CustomerPart 加属性:

public IUser User {
    get { return this.As<UserPart>(); }
}

现在,注册页面看起来像这个样子:

image

 

3.1:完成登录

现在,让我们首先来完成登录。

ViewModels/LoginViewModel.cs:

public class LoginViewModel
{
    [Required]
    [DataType(DataType.EmailAddress)]
    public string Email { get; set; }

    [Required]
    [DataType(DataType.Password)]
    public string Password { get; set; }

    public bool CreatePersistentCookie { get; set; }
}

Views/Checkout.Login.cshtml:

@using Orchard.ContentManagement
@using Orchard.Core.Title.Models
@using TMinji.Shop.Models
@using TMinji.Shop.ViewModels
@{
    var login = (LoginViewModel)Model.Login;

    Style.Require("TMinji.Shop.Common");
}

<h2>@T("Returning customer")</h2>
<p>@T("Please login using the form below")</p>

@Html.ValidationSummary()

@using (Html.BeginFormAntiForgeryPost(Url.Action("Login", "Checkout", new { area = "TMinji.Shop" })))
{

    <article class="form">

        <fieldset>
            <ul>
                <li>
                    <div class="field-label">@Html.LabelFor(m => login.Email, T("Email"))</div>
                    <div class="field-editor">@Html.EditorFor(m => login.Email)</div>
                </li>
                <li>
                    <div class="field-label">@Html.LabelFor(m => login.Password, T("Password"))</div>
                    <div class="field-editor">@Html.EditorFor(m => login.Password)</div>
                </li>
            </ul>
        </fieldset>

        <fieldset>
            <ul>
                <li>
                    <div class="checkbox-and-label">
                        <div class="checkbox">@Html.CheckBoxFor(m => login.CreatePersistentCookie)</div>
                        <div class="label">@Html.LabelFor(m => login.CreatePersistentCookie, T("Remember me next time"))</div>
                    </div>
                </li>
            </ul>
        </fieldset>

        <footer class="commands">
            <ul>
                <li class="align left"><a href="#">@T("Cancel")</a></li>
                <li class="align right"><button type="submit">@T("Next")</button></li>
            </ul>
        </footer>

    </article>
}

登录页面看上去是这样的,

image

现在,让我们来处理 登录 请求(上一个当前完成的控制器代码):

using Orchard;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Web.Mvc;
using Orchard.Mvc;
using Orchard.Themes;
using Orchard.Localization;
using Orchard.Security;
using TMinji.Shop.ViewModels;
using TMinji.Shop.Services;

namespace TMinji.Shop.Controllers
{
    public class CheckoutController : Controller
    {
        private readonly IAuthenticationService _authenticationService;
        private readonly IOrchardServices _services;
        private readonly ICustomerService _customerService;
        private readonly IMembershipService _membershipService;
        private Localizer T { get; set; }

        public CheckoutController(
            IOrchardServices services,
            IAuthenticationService authenticationService,
            ICustomerService customerService,
            IMembershipService membershipService)
        {
            _authenticationService = authenticationService;
            _services = services;
            _customerService = customerService;
            _membershipService = membershipService;
            T = NullLocalizer.Instance;
        }

        [Themed]
        public ActionResult SignupOrLogin()
        {

            if (_authenticationService.GetAuthenticatedUser() != null)
                return RedirectToAction("SelectAddress");

            return new ShapeResult(this, _services.New.Checkout_SignupOrLogin());
        }

        [Themed]
        public ActionResult Signup()
        {
            var shape = _services.New.Checkout_Signup();
            return new ShapeResult(this, shape);
        }

        [HttpPost]
        public ActionResult Signup(SignupViewModel signup)
        {
            if (!ModelState.IsValid)
                return new ShapeResult(this, _services.New.Checkout_Signup(Signup: signup));

            var customer = _customerService.CreateCustomer(signup.Email, signup.Password);
            customer.FirstName = signup.FirstName;
            customer.LastName = signup.LastName;
            customer.Title = signup.Title;

            _authenticationService.SignIn(customer.User, true);

            return RedirectToAction("SelectAddress");
        }

        [Themed]
        public ActionResult Login()
        {
            var shape = _services.New.Checkout_Login();
            return new ShapeResult(this, shape);
        }

        [Themed, HttpPost]
        public ActionResult Login(LoginViewModel login)
        {
            // Validate the specified credentials
            var user = _membershipService.ValidateUser(login.Email, login.Password);

            // If no user was found, add a model error
            if (user == null)
            {
                ModelState.AddModelError("Email", T("Incorrect username/password combination").ToString());
            }

            // If there are any model errors, redisplay the login form
            if (!ModelState.IsValid)
            {
                var shape = _services.New.Checkout_Login(Login: login);
                return new ShapeResult(this, shape);
            }

            // Create a forms ticket for the user
            _authenticationService.SignIn(user, login.CreatePersistentCookie);

            // Redirect to the next step
            return RedirectToAction("SelectAddress");
        }

        [Themed]
        public ActionResult SelectAddress()
        {
            var shape = _services.New.Checkout_SelectAddress();
            return new ShapeResult(this, shape);
        }

        [Themed]
        public ActionResult Summary()
        {
            var shape = _services.New.Checkout_Summary();
            return new ShapeResult(this, shape);
        }

    }
}

3.2 完成 SelectAddress 页面

ViewModels/AddressViewModel.cs:

public class AddressViewModel
{
    [StringLength(50)]
    public string Name { get; set; }

    [StringLength(256)]
    public string AddressLine1 { get; set; }

    [StringLength(256)]
    public string AddressLine2 { get; set; }

    [StringLength(10)]
    public string Zipcode { get; set; }

    [StringLength(50)]
    public string City { get; set; }

    [StringLength(50)]
    public string Country { get; set; }
}

ViewModels/AddressesViewModel.cs:

public class AddressesViewModel : IValidatableObject
{

    [UIHint("Address")]
    public AddressViewModel InvoiceAddress { get; set; }

    [UIHint("Address")]
    public AddressViewModel ShippingAddress { get; set; }

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        var address = InvoiceAddress;

        if (string.IsNullOrWhiteSpace(address.AddressLine1))
            yield return new ValidationResult("Addressline 1 is a required field", new[] { "InvoiceAddress.AddressLine1" });

        if (string.IsNullOrWhiteSpace(address.Zipcode))
            yield return new ValidationResult("Zipcode is a required field", new[] { "InvoiceAddress.Zipcode" });

        if (string.IsNullOrWhiteSpace(address.City))
            yield return new ValidationResult("City is a required field", new[] { "InvoiceAddress.City" });

        if (string.IsNullOrWhiteSpace(address.Country))
            yield return new ValidationResult("Country is a required field", new[] { "InvoiceAddress.Country" });
    }
}

Views/Checkout.SelectAddress.cshtml:

@using TMinji.Shop.ViewModels
@{
    var addresses = (AddressesViewModel)Model.Addresses;

    Style.Require("TMinji.Shop.Common");
}
<h2>Address Details</h2>
<p>@T("Please provide us with your billing address and shipping address. If both addresses are the same, then you only need to provide us with your invoice address.")</p>

@using (Html.BeginFormAntiForgeryPost(Url.Action("SelectAddress", "Checkout", new { area = "TMinji.Shop" })))
{
    <article class="form">

        @Html.EditorFor(m => addresses.InvoiceAddress)
        @Html.EditorFor(m => addresses.ShippingAddress)

        <footer class="commands">
            <ul>
                <li class="align left"><a href="#">@T("Cancel")</a></li>
                <li class="align right"><button type="submit">@T("Next")</button></li>
            </ul>
        </footer>
    </article>
}

Views/EditorTemplates/Address.cshtml:

@using System.Web.Mvc.Html
@model TMinji.Shop.ViewModels.AddressViewModel

<fieldset>
    <legend>@ViewData.ModelMetadata.GetDisplayName()</legend>
    <ul>
        <li>
            <div class="field-label">@Html.LabelFor(m => m.Name, T("Name"))</div>
            <div class="field-editor">@Html.EditorFor(m => m.Name)</div>
        </li>
        <li>
            <div class="field-label">@Html.LabelFor(m => m.AddressLine1, T("Address line 1"))</div>
            <div class="field-editor">@Html.EditorFor(m => m.AddressLine1)</div>
        </li>
        <li>
            <div class="field-label">@Html.LabelFor(m => m.AddressLine2, T("Address line 2"))</div>
            <div class="field-editor">@Html.EditorFor(m => m.AddressLine2)</div>
        </li>
        <li>
            <div class="field-label">@Html.LabelFor(m => m.Zipcode, T("Zipcode"))</div>
            <div class="field-editor">@Html.EditorFor(m => m.Zipcode)</div>
        </li>
        <li>
            <div class="field-label">@Html.LabelFor(m => m.City, T("City"))</div>
            <div class="field-editor">@Html.EditorFor(m => m.City)</div>
        </li>
        <li>
            <div class="field-label">@Html.LabelFor(m => m.Country, T("Country"))</div>
            <div class="field-editor">@Html.EditorFor(m => m.Country)</div>
        </li>
    </ul>
</fieldset>

现在界面为:

image

Controllers/CheckoutController.cs (snippets):

[Themed]
public ActionResult SelectAddress()
{
    var currentUser = _authenticationService.GetAuthenticatedUser();

    if (currentUser == null)
        throw new OrchardSecurityException(T("Login required"));

    /* should add using Orchard.ContentManagement */
    var customer = currentUser.ContentItem.As<CustomerPart>();
    var invoiceAddress = _customerService.GetAddress(customer.Id, "InvoiceAddress");
    var shippingAddress = _customerService.GetAddress(customer.Id, "ShippingAddress");

    var addressesViewModel = new AddressesViewModel
    {
        InvoiceAddress = MapAddress(invoiceAddress),
        ShippingAddress = MapAddress(shippingAddress)
    };

    var shape = _services.New.Checkout_SelectAddress(Addresses: addressesViewModel);
    if (string.IsNullOrWhiteSpace(addressesViewModel.InvoiceAddress.Name))
        addressesViewModel.InvoiceAddress.Name = string.Format("{0} {1} {2}", customer.Title, customer.FirstName, customer.LastName);
    return new ShapeResult(this, shape);
}

private AddressViewModel MapAddress(AddressPart addressPart)
{
    dynamic address = addressPart;
    var addressViewModel = new AddressViewModel();

    if (addressPart != null)
    {
        addressViewModel.Name = address.Name.Value;
        addressViewModel.AddressLine1 = address.AddressLine1.Value;
        addressViewModel.AddressLine2 = address.AddressLine2.Value;
        addressViewModel.Zipcode = address.Zipcode.Value;
        addressViewModel.City = address.City.Value;
        addressViewModel.Country = address.Country.Value;
    }

    return addressViewModel;
}

然后,我们需要补齐,Services/ICustomerService.cs:

public interface ICustomerService : IDependency
{
    CustomerPart CreateCustomer(string email, string password);
    AddressPart GetAddress(int customerId, string addressType);
    AddressPart CreateAddress(int customerId, string addressType);

}

Services/CustomerService.cs (snippet):

public AddressPart GetAddress(int customerId, string addressType)
{
    return _orchardServices.ContentManager.Query<AddressPart, AddressPartRecord>().Where(x => x.CustomerId == customerId && x.Type == addressType).List().FirstOrDefault();
}

public AddressPart CreateAddress(int customerId, string addressType)
{
    return _orchardServices.ContentManager.Create<AddressPart>("Address", x =>
    {
        x.Type = addressType;
        x.CustomerId = customerId;
    });
}

Controllers/CheckoutController.cs (snippet):

[Themed, HttpPost]
public ActionResult SelectAddress(AddressesViewModel addresses)
{
    var currentUser = _authenticationService.GetAuthenticatedUser();

    if (currentUser == null)
        throw new OrchardSecurityException(T("Login required"));

    if (!ModelState.IsValid)
    {
        return new ShapeResult(this, _services.New.Checkout_SelectAddress(Addresses: addresses));
    }

    var customer = currentUser.ContentItem.As<CustomerPart>();
    MapAddress(addresses.InvoiceAddress, "InvoiceAddress", customer);
    MapAddress(addresses.ShippingAddress, "ShippingAddress", customer);

    return RedirectToAction("Summary");
}

private AddressPart MapAddress(AddressViewModel source, string addressType, CustomerPart customerPart)
{
    var addressPart = _customerService.GetAddress(customerPart.Id, addressType) ?? _customerService.CreateAddress(customerPart.Id, addressType);
    dynamic address = addressPart;

    address.Name.Value = source.Name.TrimSafe();
    address.AddressLine1.Value = source.AddressLine1.TrimSafe();
    address.AddressLine2.Value = source.AddressLine2.TrimSafe();
    address.Zipcode.Value = source.Zipcode.TrimSafe();
    address.City.Value = source.City.TrimSafe();
    address.Country.Value = source.Country.TrimSafe();

    return addressPart;
}

Helpers/StringExtensions.cs:

public static class StringExtensions
{
    public static string TrimSafe(this string s)
    {
        return s == null ? string.Empty : s.Trim();
    }
}

现在,我们就可以注册成功了。

 

3.3 完成 checkout summary 页面

Views/Checkout.Summary.cshtml:

@using Orchard.ContentManagement
@using TMinji.Shop.Models
@{
    Style.Require("TMinji.Shop.Checkout.Summary");
    var shoppingCart = Model.ShoppingCart;
    var invoiceAddress = Model.InvoiceAddress;
    var shippingAddress = Model.ShippingAddress;
    var items = (IList<dynamic>)shoppingCart.ShopItems;
    var subtotal = (decimal)shoppingCart.Subtotal;
    var vat = (decimal)shoppingCart.Vat;
    var total = (decimal)shoppingCart.Total;
}
@if (!items.Any())
{
    <p>You don't have any items in your shopping cart.</p>
    <a class="button" href="#">Continue shopping</a>
}
else
{

    <article class="shoppingcart">
        <h2>Review your order</h2>
        <p>Please review the information below. Hit the Place Order button to proceed.</p>
        <table>
            <thead>
                <tr>
                    <td>Article</td>
                    <td class="numeric">Unit Price</td>
                    <td class="numeric">Quantity</td>
                    <td class="numeric">Total Price</td>
                </tr>
            </thead>
            <tbody>
                @for (var i = 0; i < items.Count; i++)
                {
                    var item = items[i];
                    var product = (ProductPart)item.Product;
                    var contentItem = (ContentItem)item.ContentItem;
                    var title = item.Title;
                    var quantity = (int)item.Quantity;
                    var unitPrice = product.UnitPrice;
                    var totalPrice = quantity * unitPrice;
                    <tr>
                        <td>@title</td>
                        <td class="numeric">@unitPrice.ToString("c")</td>
                        <td class="numeric">@quantity</td>
                        <td class="numeric">@totalPrice.ToString("c")</td>
                    </tr>
                }

            </tbody>
            <tfoot>
                <tr class="separator"><td colspan="4">&nbsp;</td></tr>
                <tr>
                    <td class="numeric label" colspan="2">Subtotal:</td>
                    <td class="numeric">@subtotal.ToString("c")</td>
                    <td></td>
                </tr>
                <tr>
                    <td class="numeric label" colspan="2">VAT (19%):</td>
                    <td class="numeric">@vat.ToString("c")</td>
                    <td></td>
                </tr>
                <tr>
                    <td class="numeric label" colspan="3">Total:</td>
                    <td class="numeric">@total.ToString("c")</td>
                    <td></td>
                </tr>
            </tfoot>
        </table>
    </article>

    <article class="addresses form">
        <div class="invoice-address">
            <h2>Invoice Address</h2>
            <ul class="address-fields">
                <li>@invoiceAddress.Name.Value</li>
                <li>@invoiceAddress.AddressLine1.Value</li>
                <li>@invoiceAddress.AddressLine2.Value</li>
                <li>@invoiceAddress.Zipcode.Value</li>
                <li>@invoiceAddress.City.Value</li>
                <li>@invoiceAddress.Country.Value</li>
            </ul>
        </div>
        <div class="shipping-address">
            <h2>Shipping Address</h2>
            <ul class="address-fields">
                <li>@shippingAddress.Name.Value</li>
                <li>@shippingAddress.AddressLine1.Value</li>
                <li>@shippingAddress.AddressLine2.Value</li>
                <li>@shippingAddress.Zipcode.Value</li>
                <li>@shippingAddress.City.Value</li>
                <li>@shippingAddress.Country.Value</li>
            </ul>
        </div>
    </article>

    <article>
        <div class="group">
            <div class="align left"><a href="#">Cancel</a></div>
            <div class="align right"><button type="submit" name="command" value="CreateOrder">Place Order</button></div>
        </div>
    </article>
}

ResourceManifest.cs:

manifest.DefineStyle("TMinji.Shop.Checkout.Summary").SetUrl("checkout-summary.css").SetDependencies("TMinji.Shop.Common");

Styles/checkout-summary.css:

article.shoppingcart {
    width: 100%;
}
article.shoppingcart table {
    width: 100%;  
}
article.shoppingcart td {
    padding: 7px 3px 4px 4px;
    vertical-align: middle;
}
article.shoppingcart table thead td {
    background: #f6f6f6;
    font-weight: bold;
}
article.shoppingcart table tfoot tr.separator td {
    border-bottom: 1px solid #ccc;
}
article.shoppingcart table tfoot td {
    font-weight: bold;
}
article.shoppingcart footer {
    margin-top: 20px;
}
article.shoppingcart td.numeric {
    width: 75px;
    text-align: right;
}
article.addresses {
    margin: 10px 0 10px 0;
    padding: 0 40px 10px 20px;
}
article.addresses:after {
    clear:both;
    height:0;
    content:".";
    display:block;
    visibility:hidden;
    zoom:1;
}
article.addresses .invoice-address{
    float: left;
}
article.addresses .shipping-address{
    float: right;
}
ul.address-fields {
    margin: 0;
    list-style: none;  
}

Controllers/CheckoutController.cs (snippet):

[Themed]
public ActionResult Summary()
{
    var user = _authenticationService.GetAuthenticatedUser();

    if (user == null)
        throw new OrchardSecurityException(T("Login required"));

    dynamic invoiceAddress = _customerService.GetAddress(user.Id, "InvoiceAddress");
    dynamic shippingAddress = _customerService.GetAddress(user.Id, "ShippingAddress");
    dynamic shoppingCartShape = _services.New.ShoppingCart();

    var query = _shoppingCart.GetProducts().Select(x => _services.New.ShoppingCartItem(
        Product: x.ProductPart,
        Quantity: x.Quantity,
        Title: _services.ContentManager.GetItemMetadata(x.ProductPart).DisplayText
    ));

    shoppingCartShape.ShopItems = query.ToArray();
    shoppingCartShape.Total = _shoppingCart.Total();
    shoppingCartShape.Subtotal = _shoppingCart.Subtotal();
    shoppingCartShape.Vat = _shoppingCart.Vat();

    return new ShapeResult(this, _services.New.Checkout_Summary(
        ShoppingCart: shoppingCartShape,
        InvoiceAddress: invoiceAddress,
        ShippingAddress: shippingAddress
    ));
}

现在,界面是这样的:

image

至此,大功告成。

 

参考:http://skywalkersoftwaredevelopment.net/blog/writing-an-orchard-webshop-module-from-scratch-part-8

posted @ 2014-07-23 11:50  陆敏技  阅读(3647)  评论(0编辑  收藏  举报
Web Counter
Coupon for Contacts