zoukankan      html  css  js  c++  java
  • 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"]
    {
        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;
        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; 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 {
        100%;
    }
    article.shoppingcart table {
        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 {
        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

  • 相关阅读:
    Vue响应式原理
    vue 与 react
    vuex
    受控组件( controlled component )与不受控制的组件( uncontrolled component )区别
    redux学习语录
    Flux
    MVC和MVVM 架构模式/设计思想
    immutable
    JS中一些兼容性问题
    @芥末的糖 ---------- node连接数据库两种方式mysql和moogoDB
  • 原文地址:https://www.cnblogs.com/luminji/p/3861025.html
Copyright © 2011-2022 走看看