我们都知道 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>
运行之,可以看到创建了数据库表:
二:前台准备
控制器 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 下:
然后,输入 URL:http://localhost:30321/OrchardLocal/tminji.shop/Checkout/SignupOrLogin,得到:
现在,修改 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");
}
}}
注意,引用:
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>(); }
}
现在,注册页面看起来像这个样子:
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>
}
登录页面看上去是这样的,
现在,让我们来处理 登录 请求(上一个当前完成的控制器代码):
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>
现在界面为:
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"> </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
));
}
现在,界面是这样的:
至此,大功告成。
参考:http://skywalkersoftwaredevelopment.net/blog/writing-an-orchard-webshop-module-from-scratch-part-8