添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

下载 Wingtip Toys 示例项目 (C#) 下载电子书 (PDF)

本教程系列将介绍如何使用 ASP.NET 4.5 和 Microsoft Visual Studio Express 2013 for Web 生成 ASP.NET Web Forms应用程序。 包含 C# 源代码 的Visual Studio 2013项目随本教程系列一起提供。

本教程介绍如何修改 Wingtip Toys 示例应用程序,以使用PayPal包括用户授权、注册和付款。 只有登录的用户才有权购买产品。 ASP.NET 4.5 Web Forms项目模板的内置用户注册功能已包含所需的大部分内容。 你将添加PayPal快速签出功能。 在本教程中,你将使用PayPal开发人员测试环境,因此不会转移实际资金。 在本教程结束时,你将通过选择要添加到购物车的产品、单击结帐按钮并将数据传输到PayPal测试网站来测试应用程序。 在PayPal测试网站上,你将确认发货和付款信息,然后返回到本地 Wingtip Toys 示例应用程序,确认并完成购买。

有几个经验丰富的第三方支付处理器专门从事在线购物,以解决可伸缩性和安全性问题。 ASP.NET 开发人员在实施购物和购买解决方案之前,应考虑利用第三方支付解决方案的优势。

Wingtip Toys 示例应用程序旨在显示可供 ASP.NET Web 开发人员使用的特定 ASP.NET 概念和功能。 此示例应用程序未针对可伸缩性和安全性的所有可能情况进行优化。

学习内容:

  • 如何限制对文件夹中特定页面的访问。
  • 如何从匿名购物车创建已知的购物车。
  • 如何为项目启用 SSL。
  • 如何将 OAuth 提供程序添加到项目。
  • 如何使用PayPal通过PayPal测试环境购买产品。
  • 如何在 DetailsView 控件中显示PayPal的详细信息。
  • 如何使用从PayPal获取的详细信息更新 Wingtip Toys 应用程序的数据库。
  • 添加订单跟踪

    在本教程中,你将创建两个新类来跟踪用户创建的顺序中的数据。 这些类将跟踪有关发货信息、购买总额和付款确认的数据。

    添加 Order 和 OrderDetail 模型类

    在本教程系列前面的部分中,通过创建 Models 文件夹中的 Category Product CartItem 类别、产品和购物车项定义类别、产品和购物车项的架构。 现在,你将添加两个新类来定义产品订单的架构和订单的详细信息。

  • Models 文件夹中,添加名为 Order.cs 的新类。
    新类文件显示在编辑器中。

  • 将默认代码替换为以下内容:

    using System;
    using System.ComponentModel.DataAnnotations;
    using System.Collections.Generic;
    using System.ComponentModel;
    namespace WingtipToys.Models
      public class Order
        public int OrderId { get; set; }
        public DateTime OrderDate { get; set; }
        public string Username { get; set; }
        [Required(ErrorMessage = "First Name is required")]
        [DisplayName("First Name")]
        [StringLength(160)]
        public string FirstName { get; set; }
        [Required(ErrorMessage = "Last Name is required")]
        [DisplayName("Last Name")]
        [StringLength(160)]
        public string LastName { get; set; }
        [Required(ErrorMessage = "Address is required")]
        [StringLength(70)]
        public string Address { get; set; }
        [Required(ErrorMessage = "City is required")]
        [StringLength(40)]
        public string City { get; set; }
        [Required(ErrorMessage = "State is required")]
        [StringLength(40)]
        public string State { get; set; }
        [Required(ErrorMessage = "Postal Code is required")]
        [DisplayName("Postal Code")]
        [StringLength(10)]
        public string PostalCode { get; set; }
        [Required(ErrorMessage = "Country is required")]
        [StringLength(40)]
        public string Country { get; set; }
        [StringLength(24)]
        public string Phone { get; set; }
        [Required(ErrorMessage = "Email Address is required")]
        [DisplayName("Email Address")]
        [RegularExpression(@"[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}",
            ErrorMessage = "Email is is not valid.")]
        [DataType(DataType.EmailAddress)]
        public string Email { get; set; }
        [ScaffoldColumn(false)]
        public decimal Total { get; set; }
        [ScaffoldColumn(false)]
        public string PaymentTransactionId { get; set; }
        [ScaffoldColumn(false)]
        public bool HasBeenShipped { get; set; }
        public List<OrderDetail> OrderDetails { get; set; }
    
  • OrderDetail.cs 类添加到 Models 文件夹。

  • 将默认代码替换为以下代码:

    using System.ComponentModel.DataAnnotations;
    namespace WingtipToys.Models
        public class OrderDetail
            public int OrderDetailId { get; set; }
            public int OrderId { get; set; }
            public string Username { get; set; }
            public int ProductId { get; set; }
            public int Quantity { get; set; }
            public double? UnitPrice { get; set; }
    

    OrderOrderDetail包含用于定义购买和发货的订单信息的架构。

    此外,还需要更新管理实体类以及提供对数据库的数据访问的数据库上下文类。 为此,需将新创建的 Order 和 OrderDetail 模型类添加到 ProductContext 类。

  • 解决方案资源管理器中,查找并打开 ProductContext.cs 文件。

  • 将突出显示的代码添加到 ProductContext.cs 文件,如下所示:

    using System.Data.Entity;
    namespace WingtipToys.Models
      public class ProductContext : DbContext
        public ProductContext()
          : base("WingtipToys")
        public DbSet<Category> Categories { get; set; }
        public DbSet<Product> Products { get; set; }
        public DbSet<CartItem> ShoppingCartItems { get; set; }
        public DbSet<Order> Orders { get; set; }
        public DbSet<OrderDetail> OrderDetails { get; set; }
    

    如本教程系列中所述, ProductContext.cs 文件中的代码会添加 System.Data.Entity 命名空间,以便你有权访问 Entity Framework 的所有核心功能。 此功能包括使用强类型对象查询、插入、更新和删除数据的功能。 类中的 ProductContext 上述代码将实体框架访问权限添加到新添加 Order 的类和 OrderDetail 类。

    添加签出访问权限

    Wingtip Toys 示例应用程序允许匿名用户查看产品并将其添加到购物车。 但是,当匿名用户选择购买他们添加到购物车的产品时,他们必须登录到该网站。 登录后,可以访问处理结帐和购买过程的 Web 应用程序的受限页面。 这些受限页面包含在应用程序的 “签出 ”文件夹中。

    添加签出文件夹和页面

    现在,你将创建 签出 文件夹及其中的页面,客户将在结帐过程中看到这些页面。 本教程稍后将更新这些页面。

  • 右键单击解决方案资源管理器中 (Wingtip Toys) 的项目名称,然后选择“添加新文件夹”。

  • 将新文件夹命名 为“签出”。

  • 右键单击“签出”文件夹,然后选择“>添加新项”。

  • 随即出现“添加新项”对话框。

  • 选择左侧的 Visual C# ->Web 模板组。 然后,从中间窗格中,选择 包含母版页的 Web 窗体,并将其命名为 CheckoutStart.aspx

  • 如前所述,选择 Site.Master 文件作为母版页。

  • 使用上述相同步骤将以下其他页面添加到 Checkout 文件夹:

  • CheckoutReview.aspx
  • CheckoutComplete.aspx
  • CheckoutCancel.aspx
  • CheckoutError.aspx
  • 添加Web.config文件

    通过将新的 Web.config 文件添加到 “签出 ”文件夹,可以限制对文件夹中包含的所有页面的访问。

  • 右键单击“签出”文件夹,然后选择“添加新>”。
    随即出现“添加新项”对话框。

  • 选择左侧的 Visual C# ->Web 模板组。 然后,从中间窗格中选择 “Web 配置文件”,接受 默认名称Web.config,然后选择“ 添加”。

  • Web.config 文件中的现有 XML 内容替换为以下内容:

    <?xml version="1.0"?>
    <configuration>
      <system.web>
        <authorization>
          <deny users="?"/>
        </authorization>
      </system.web>
    </configuration>
    
  • 保存 Web.config 文件。

    Web.config文件指定必须拒绝 Web 应用程序的所有未知用户访问签出文件夹中包含的页面。 但是,如果用户已注册帐户并登录,则它们将成为已知用户,并且有权访问 Checkout 文件夹中的页面。

    请务必注意,ASP.NET 配置遵循层次结构,其中每个 Web.config 文件将配置设置应用于它所在的文件夹及其下的所有子目录。

    为项目启用 SSL

    安全套接字层 (SSL) 是一种协议,定义为允许 Web 服务器与 Web 客户端通过使用加密以更安全的方式通信。 如果不使用 SSL,在客户端与服务器之间发送数据时,对网络具有实际访问权限的任何人都可以探查数据包。 此外,通过一般 HTTP 进行的几种常见身份验证方案也是不安全的。 尤其是,基本身份验证和窗体身份验证会发送未加密的凭据。 为确保安全,这些身份验证方案必须使用 SSL。

  • 解决方案资源管理器中,单击 WingtipToys 项目,然后按 F4 显示“属性”窗口。
  • 启用 SSL 更改为 true.
  • 复制 SSL URL ,以便稍后使用它。
    除非之前创建了 SSL 网站 (,否则 SSL URL 将 https://localhost:44300/ 如下所示) 。
  • 解决方案资源管理器中,右键单击 WingtipToys 项目,然后单击“属性”。
  • 在左侧选项卡中,单击 “Web”。
  • 更改 项目 URL 以使用之前保存的 SSL URL
  • Ctrl+S 保存页面。
  • 按“Ctrl+F5” 运行应用程序。 Visual Studio 将显示一个选项用于避免 SSL 警告。
  • 单击“”信任IIS Express SSL 证书并继续。
    此时将显示一条安全警告。
  • 单击“ ”将证书安装到 localhost。
    此时将显示浏览器窗口。
  • 现在,可以使用 SSL 轻松在本地测试 Web 应用程序。

    添加 OAuth 2.0 提供程序

    ASP.NET Web 窗体为成员资格和身份验证提供了增强的选项。 这些增强功能包括 OAuth。 OAuth 是一种开放协议,允许以一种简单而标准的方法从 Web、移动和桌面应用程序进行安全授权。 ASP.NET Web Forms模板使用 OAuth 将 Facebook、Twitter、Google 和 Microsoft 公开为身份验证提供程序。 虽然本教程仅使用 Google 作为身份验证提供程序,但你可轻松修改代码以使用任何提供程序。 实施其他提供程序的步骤与你将在本教程中看到的步骤非常类似。

    除了身份验证外,本教程还将使用角色实施授权。 只有添加到角色 canEdit 的用户才能 (创建、编辑或删除联系人) 更改数据。

    Windows Live 应用程序仅接受工作网站的实时 URL,因此不能使用本地网站 URL 测试登录名。

    可以执行以下步骤来添加 Google 身份验证提供程序。

  • 打开 App_Start\Startup.Auth.cs 文件。

  • app.UseGoogleAuthentication() 方法中删除注释字符,使该方法如下所示:

    app.UseGoogleAuthentication(new GoogleOAuth2AuthenticationOptions()
        ClientId = "",
        ClientSecret = ""
    
  • 导航到 Google 开发人员控制台。 你还需要使用 Google 开发人员电子邮件帐户 (gmail.com) 登录。 如果没有 Google 帐户,请选择 “创建帐户 ”链接。
    接下来,你将看到 Google 开发人员控制台

  • 单击“ 创建项目 ”按钮,输入项目名称和 ID (可以使用默认值) 。 然后,单击 协议复选框“创建 ”按钮。

    几秒钟后,将会创建新项目,并且浏览器将显示新项目页。

  • 在左侧选项卡中,单击 API & 身份验证,然后单击 “凭据”。

  • 单击 OAuth 下的“创建新客户端 ID”。
    将显示“ 创建客户端 ID ”对话框。

  • “创建客户端 ID ”对话框中,保留应用程序类型的默认 Web 应用程序

  • 除非已创建其他 SSL 项目) ,否则请将 授权 JavaScript 源 设置为本教程前面使用的 SSL URL (https://localhost:44300/
    此 URL 是应用程序的来源。 对于此示例,你只需输入 localhost 测试 URL。 但是,可以输入多个 URL 来考虑 localhost 和生产。

  • 授权重定向 URI 设置为以下内容:

    https://localhost:44300/signin-google
    

    此值是 ASP.NET OAuth 用户与 Google OAuth 服务器通信时使用的 URI。 请记住上面使用的 SSL URL ( https://localhost:44300/ ,除非已创建其他 SSL 项目) 。

  • 单击“ 创建客户端 ID ”按钮。

  • 在 Google 开发人员控制台的左侧菜单中,单击 “同意”屏幕 菜单项,然后设置电子邮件地址和产品名称。 完成表单后,单击“ 保存”。

  • 单击 API 菜单项,向下滚动并单击 Google+ API 旁边的关闭按钮。
    接受此选项将启用 Google+ API。

  • 还必须将 Microsoft.Owin NuGet 包更新到版本 3.0.0。
    “工具” 菜单中,选择 “NuGet 包管理器 ”,然后选择“ 管理解决方案的 NuGet 包”。
    “管理 NuGet 包 ”窗口中,查找 Microsoft.Owin 包并将其更新为版本 3.0.0。

  • 在 Visual Studio 中,通过将客户端 ID客户端密码复制粘贴到方法中来更新 UseGoogleAuthenticationStartup.Auth.cs 页的方法。 下面显示的 客户端 ID客户端机密 值是示例,不起作用。

    using System;
    using Microsoft.AspNet.Identity;
    using Microsoft.AspNet.Identity.EntityFramework;
    using Microsoft.AspNet.Identity.Owin;
    using Microsoft.Owin;
    using Microsoft.Owin.Security.Cookies;
    using Microsoft.Owin.Security.DataProtection;
    using Microsoft.Owin.Security.Google;
    using Owin;
    using WingtipToys.Models;
    namespace WingtipToys
        public partial class Startup {
            // For more information on configuring authentication, please visit https://go.microsoft.com/fwlink/?LinkId=301883
            public void ConfigureAuth(IAppBuilder app)
                // Configure the db context, user manager and signin manager to use a single instance per request
                app.CreatePerOwinContext(ApplicationDbContext.Create);
                app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
                app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
                // Enable the application to use a cookie to store information for the signed in user
                // and to use a cookie to temporarily store information about a user logging in with a third party login provider
                // Configure the sign in cookie
                app.UseCookieAuthentication(new CookieAuthenticationOptions
                    AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
                    LoginPath = new PathString("/Account/Login"),
                    Provider = new CookieAuthenticationProvider
                        OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
                            validateInterval: TimeSpan.FromMinutes(30),
                            regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
                // Use a cookie to temporarily store information about a user logging in with a third party login provider
                app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
                // Enables the application to temporarily store user information when they are verifying the second factor in the two-factor authentication process.
                app.UseTwoFactorSignInCookie(DefaultAuthenticationTypes.TwoFactorCookie, TimeSpan.FromMinutes(5));
                // Enables the application to remember the second login verification factor such as phone or email.
                // Once you check this option, your second step of verification during the login process will be remembered on the device where you logged in from.
                // This is similar to the RememberMe option when you log in.
                app.UseTwoFactorRememberBrowserCookie(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie);
                // Uncomment the following lines to enable logging in with third party login providers
                //app.UseMicrosoftAccountAuthentication(
                //    clientId: "",
                //    clientSecret: "");
                //app.UseTwitterAuthentication(
                //   consumerKey: "",
                //   consumerSecret: "");
                //app.UseFacebookAuthentication(
                //   appId: "",
                //   appSecret: "");
                app.UseGoogleAuthentication(new GoogleOAuth2AuthenticationOptions()
                  ClientId = "000000000000.apps.googleusercontent.com",
                  ClientSecret = "00000000000"
    
  • Ctrl+F5 生成并运行应用程序。 单击 “登录 ”链接。

  • “使用其他服务登录”下,单击 “Google”。

  • 如果你需要输入你的凭据,你将重定向到 google 站点将在此输入你的凭据。

  • 输入凭据后,系统会提示你向刚创建的 Web 应用程序授予权限。

  • 单击“接受”。 现在,你将重定向回 WingtipToys 应用程序的“注册”页,你可以在其中注册 Google 帐户。

  • 你可以选择更改 Gmail 帐户使用的本地电子邮件注册名称,但是,你通常会保留默认电子邮件别名(即,用于身份验证的名称)。 单击 “登录 ”,如下所示。

    修改登录功能

    如本教程系列中所述,默认情况下,ASP.NET Web Forms模板中包含了大部分用户注册功能。 现在,你将修改默认 的 Login.aspxRegister.aspx 页面以调用 MigrateCart 该方法。 该方法 MigrateCart 将新登录的用户与匿名购物车相关联。 通过将用户和购物车关联,Wingtip Toys 示例应用程序将能够维护用户访问之间的购物车。

  • 解决方案资源管理器中,找到并打开“帐户”文件夹。

  • 修改名为 Login 的代码 隐藏页.aspx.cs以黄色突出显示的代码,使其如下所示:

    using System;
    using System.Web;
    using System.Web.UI;
    using Microsoft.AspNet.Identity;
    using Microsoft.AspNet.Identity.Owin;
    using Owin;
    using WingtipToys.Models;
    namespace WingtipToys.Account
        public partial class Login : Page
            protected void Page_Load(object sender, EventArgs e)
                RegisterHyperLink.NavigateUrl = "Register";
                // Enable this once you have account confirmation enabled for password reset functionality
                //ForgotPasswordHyperLink.NavigateUrl = "Forgot";
                OpenAuthLogin.ReturnUrl = Request.QueryString["ReturnUrl"];
                var returnUrl = HttpUtility.UrlEncode(Request.QueryString["ReturnUrl"]);
                if (!String.IsNullOrEmpty(returnUrl))
                    RegisterHyperLink.NavigateUrl += "?ReturnUrl=" + returnUrl;
            protected void LogIn(object sender, EventArgs e)
                if (IsValid)
                    // Validate the user password
                    var manager = Context.GetOwinContext().GetUserManager<ApplicationUserManager>();
                    var signinManager = Context.GetOwinContext().GetUserManager<ApplicationSignInManager>();
                    // This doen't count login failures towards account lockout
                    // To enable password failures to trigger lockout, change to shouldLockout: true
                    var result = signinManager.PasswordSignIn(Email.Text, Password.Text, RememberMe.Checked, shouldLockout: false);
                    switch (result)
                        case SignInStatus.Success:
                            WingtipToys.Logic.ShoppingCartActions usersShoppingCart = new WingtipToys.Logic.ShoppingCartActions();
                            String cartId = usersShoppingCart.GetCartId();
                            usersShoppingCart.MigrateCart(cartId, Email.Text);
                            IdentityHelper.RedirectToReturnUrl(Request.QueryString["ReturnUrl"], Response);
                            break;
                        case SignInStatus.LockedOut:
                            Response.Redirect("/Account/Lockout");
                            break;
                        case SignInStatus.RequiresVerification:
                            Response.Redirect(String.Format("/Account/TwoFactorAuthenticationSignIn?ReturnUrl={0}&RememberMe={1}", 
                                                            Request.QueryString["ReturnUrl"],
                                                            RememberMe.Checked),
                                              true);
                            break;
                        case SignInStatus.Failure:
                        default:
                            FailureText.Text = "Invalid login attempt";
                            ErrorMessage.Visible = true;
                            break;
    
  • 保存 登录名.aspx.cs 文件。

    目前,可以忽略没有方法定义的 MigrateCart 警告。 稍后将在本教程中添加它。

    Login.aspx.cs代码隐藏文件支持 LogIn 方法。 通过检查登录名.aspx页,你将看到此页面包含一个“登录”按钮,当单击触发 LogIn 代码隐藏上的处理程序时。

    Login调用 Login 上的方法.aspx.cs时,将创建名为usersShoppingCart的购物车的新实例。 (GUID) 检索购物车的 ID 并将其设置为 cartId 变量。 然后,调用该方法, MigrateCart 同时将登录用户的名称和名称传递给 cartId 此方法。 迁移购物车后,用于标识匿名购物车的 GUID 将替换为用户名。

    除了修改 登录名.aspx.cs 代码隐藏文件以在用户登录时迁移购物车,还必须修改 Register.aspx.cs代码隐藏文件 ,以在用户创建新帐户并登录时迁移购物车。

  • “帐户” 文件夹中,打开名为 Register.aspx.cs 的代码隐藏文件。

  • 通过以黄色方式包括代码来修改代码隐藏文件,使其如下所示:

    using System;
    using System.Linq;
    using System.Web;
    using System.Web.UI;
    using Microsoft.AspNet.Identity;
    using Microsoft.AspNet.Identity.Owin;
    using Owin;
    using WingtipToys.Models;
    namespace WingtipToys.Account
        public partial class Register : Page
            protected void CreateUser_Click(object sender, EventArgs e)
                var manager = Context.GetOwinContext().GetUserManager<ApplicationUserManager>();
                var user = new ApplicationUser() { UserName = Email.Text, Email = Email.Text };
                IdentityResult result = manager.Create(user, Password.Text);
                if (result.Succeeded)
                    // For more information on how to enable account confirmation and password reset please visit https://go.microsoft.com/fwlink/?LinkID=320771
                    //string code = manager.GenerateEmailConfirmationToken(user.Id);
                    //string callbackUrl = IdentityHelper.GetUserConfirmationRedirectUrl(code, user.Id, Request);
                    //manager.SendEmail(user.Id, "Confirm your account", "Please confirm your account by clicking <a href=\"" + callbackUrl + "\">here</a>.");
                    IdentityHelper.SignIn(manager, user, isPersistent: false);
                    using (WingtipToys.Logic.ShoppingCartActions usersShoppingCart = new WingtipToys.Logic.ShoppingCartActions())
                      String cartId = usersShoppingCart.GetCartId();
                      usersShoppingCart.MigrateCart(cartId, user.Id);
                    IdentityHelper.RedirectToReturnUrl(Request.QueryString["ReturnUrl"], Response);
                    ErrorMessage.Text = result.Errors.FirstOrDefault();
    
  • 保存 Register.aspx.cs 文件。 再次忽略有关该方法的 MigrateCart 警告。

    请注意,在事件处理程序中使用的 CreateUser_Click 代码与方法中使用的 LogIn 代码非常相似。 当用户注册或登录到站点时,将调用该方法 MigrateCart

    迁移购物车

    现在,已更新登录和注册过程,可以添加代码以使用 MigrateCart 该方法迁移购物车。

  • 解决方案资源管理器中,找到逻辑文件夹并打开 ShoppingCartActions.cs类文件。

  • 将黄色突出显示的代码添加到 ShoppingCartActions.cs 文件中的现有代码中,以便 ShoppingCartActions 中的代码如下所示.cs文件显示:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using WingtipToys.Models;
    namespace WingtipToys.Logic
      public class ShoppingCartActions : IDisposable
        public string ShoppingCartId { get; set; }
        private ProductContext _db = new ProductContext();
        public const string CartSessionKey = "CartId";
        public void AddToCart(int id)
          // Retrieve the product from the database.           
          ShoppingCartId = GetCartId();
          var cartItem = _db.ShoppingCartItems.SingleOrDefault(
              c => c.CartId == ShoppingCartId
              && c.ProductId == id);
          if (cartItem == null)
            // Create a new cart item if no cart item exists.                 
            cartItem = new CartItem
              ItemId = Guid.NewGuid().ToString(),
              ProductId = id,
              CartId = ShoppingCartId,
              Product = _db.Products.SingleOrDefault(
               p => p.ProductID == id),
              Quantity = 1,
              DateCreated = DateTime.Now
            _db.ShoppingCartItems.Add(cartItem);
            // If the item does exist in the cart,                  
            // then add one to the quantity.                 
            cartItem.Quantity++;
          _db.SaveChanges();
        public void Dispose()
          if (_db != null)
            _db.Dispose();
            _db = null;
        public string GetCartId()
          if (HttpContext.Current.Session[CartSessionKey] == null)
            if (!string.IsNullOrWhiteSpace(HttpContext.Current.User.Identity.Name))
              HttpContext.Current.Session[CartSessionKey] = HttpContext.Current.User.Identity.Name;
              // Generate a new random GUID using System.Guid class.     
              Guid tempCartId = Guid.NewGuid();
              HttpContext.Current.Session[CartSessionKey] = tempCartId.ToString();
          return HttpContext.Current.Session[CartSessionKey].ToString();
        public List<CartItem> GetCartItems()
          ShoppingCartId = GetCartId();
          return _db.ShoppingCartItems.Where(
              c => c.CartId == ShoppingCartId).ToList();
        public decimal GetTotal()
          ShoppingCartId = GetCartId();
          // Multiply product price by quantity of that product to get        
          // the current price for each of those products in the cart.  
          // Sum all product price totals to get the cart total.   
          decimal? total = decimal.Zero;
          total = (decimal?)(from cartItems in _db.ShoppingCartItems
                             where cartItems.CartId == ShoppingCartId
                             select (int?)cartItems.Quantity *
                             cartItems.Product.UnitPrice).Sum();
          return total ?? decimal.Zero;
        public ShoppingCartActions GetCart(HttpContext context)
          using (var cart = new ShoppingCartActions())
            cart.ShoppingCartId = cart.GetCartId();
            return cart;
        public void UpdateShoppingCartDatabase(String cartId, ShoppingCartUpdates[] CartItemUpdates)
          using (var db = new WingtipToys.Models.ProductContext())
              int CartItemCount = CartItemUpdates.Count();
              List<CartItem> myCart = GetCartItems();
              foreach (var cartItem in myCart)
                // Iterate through all rows within shopping cart list
                for (int i = 0; i < CartItemCount; i++)
                  if (cartItem.Product.ProductID == CartItemUpdates[i].ProductId)
                    if (CartItemUpdates[i].PurchaseQuantity < 1 || CartItemUpdates[i].RemoveItem == true)
                      RemoveItem(cartId, cartItem.ProductId);
                      UpdateItem(cartId, cartItem.ProductId, CartItemUpdates[i].PurchaseQuantity);
            catch (Exception exp)
              throw new Exception("ERROR: Unable to Update Cart Database - " + exp.Message.ToString(), exp);
        public void RemoveItem(string removeCartID, int removeProductID)
          using (var _db = new WingtipToys.Models.ProductContext())
              var myItem = (from c in _db.ShoppingCartItems where c.CartId == removeCartID && c.Product.ProductID == removeProductID select c).FirstOrDefault();
              if (myItem != null)
                // Remove Item.
                _db.ShoppingCartItems.Remove(myItem);
                _db.SaveChanges();
            catch (Exception exp)
              throw new Exception("ERROR: Unable to Remove Cart Item - " + exp.Message.ToString(), exp);
        public void UpdateItem(string updateCartID, int updateProductID, int quantity)
          using (var _db = new WingtipToys.Models.ProductContext())
              var myItem = (from c in _db.ShoppingCartItems where c.CartId == updateCartID && c.Product.ProductID == updateProductID select c).FirstOrDefault();
              if (myItem != null)
                myItem.Quantity = quantity;
                _db.SaveChanges();
            catch (Exception exp)
              throw new Exception("ERROR: Unable to Update Cart Item - " + exp.Message.ToString(), exp);
        public void EmptyCart()
          ShoppingCartId = GetCartId();
          var cartItems = _db.ShoppingCartItems.Where(
              c => c.CartId == ShoppingCartId);
          foreach (var cartItem in cartItems)
            _db.ShoppingCartItems.Remove(cartItem);
          // Save changes.             
          _db.SaveChanges();
        public int GetCount()
          ShoppingCartId = GetCartId();
          // Get the count of each item in the cart and sum them up          
          int? count = (from cartItems in _db.ShoppingCartItems
                        where cartItems.CartId == ShoppingCartId
                        select (int?)cartItems.Quantity).Sum();
          // Return 0 if all entries are null         
          return count ?? 0;
        public struct ShoppingCartUpdates
          public int ProductId;
          public int PurchaseQuantity;
          public bool RemoveItem;
        public void MigrateCart(string cartId, string userName)
          var shoppingCart = _db.ShoppingCartItems.Where(c => c.CartId == cartId);
          foreach (CartItem item in shoppingCart)
            item.CartId = userName;
          HttpContext.Current.Session[CartSessionKey] = userName;
          _db.SaveChanges();
    

    该方法 MigrateCart 使用现有 cartId 查找用户的购物车。 接下来,代码遍历所有购物车项,并将架构 (指定的CartItem属性) 替换为CartId登录用户名。

    更新数据库连接

    如果使用 预生成的 Wingtip Toys 示例应用程序遵循本教程,则必须重新创建默认成员身份数据库。 通过修改默认连接字符串,将在下次运行应用程序时创建成员身份数据库。

  • 在项目的根目录中打开 Web.config 文件。

  • 更新默认连接字符串,使其如下所示:

    <add name="DefaultConnection" connectionString="Data Source=(LocalDb)\MSSQLLocalDB;Initial Catalog=aspnet-WingtipToys;Integrated Security=True" providerName="System.Data.SqlClient" />
    

    集成PayPal

    PayPal是一个基于 Web 的计费平台,可接受在线商家的付款。 本教程接下来介绍如何将PayPal的快速签出功能集成到应用程序中。 快速结帐允许客户使用PayPal支付他们添加到购物车的项目的费用。

    创建PayPal测试帐户

    若要使用PayPal测试环境,必须创建并验证开发人员测试帐户。 你将使用开发人员测试帐户创建买家测试帐户和卖方测试帐户。 开发人员测试帐户凭据还将允许 Wingtip Toys 示例应用程序访问PayPal测试环境。

  • 在浏览器中,导航到PayPal开发人员测试站点:
    https://developer.paypal.com

  • 如果没有PayPal开发人员帐户,请单击 “注册”并按照注册步骤创建新帐户。 如果已有PayPal开发人员帐户,请单击“ 登录”登录。 本教程稍后需要PayPal开发人员帐户来测试 Wingtip Toys 示例应用程序。

  • 如果刚刚注册了PayPal开发人员帐户,可能需要使用 PayPal 验证PayPal开发人员帐户。 可以按照PayPal发送到电子邮件帐户的步骤验证帐户。 验证PayPal开发人员帐户后,请重新登录到PayPal开发人员测试站点。

  • 使用PayPal开发人员帐户登录到PayPal开发人员网站后,如果还没有PayPal购买者测试帐户,则需要创建一个PayPal买家测试帐户。 若要创建买家测试帐户,请在PayPal网站上单击“ 应用程序 ”选项卡,然后单击 “沙盒帐户”。
    将显示 “沙盒测试帐户 ”页。

    PayPal开发人员网站已提供商家测试帐户。

  • 在“沙盒测试帐户”页上,单击“ 创建帐户”。

  • “创建测试帐户 ”页上,选择所选的买家测试帐户电子邮件和密码。

    在本教程结束时,需要购买者的电子邮件地址和密码来测试 Wingtip Toys 示例应用程序。

  • 通过单击“ 创建帐户” 按钮创建买家测试帐户。
    将显示 “沙盒测试帐户 ”页。

  • “沙盒测试帐户 ”页上,单击 主持人 电子邮件帐户。
    将显示配置文件通知选项。

  • 选择 “配置文件 ”选项,然后单击 “API 凭据 ”以查看商家测试帐户的 API 凭据。

  • 将测试 API 凭据复制到记事本。

    你将需要显示的经典测试 API 凭据 (用户名、密码和签名) 从 Wingtip Toys 示例应用程序到PayPal测试环境进行 API 调用。 在下一步中将添加凭据。

    添加PayPal类和 API 凭据

    将大部分PayPal代码放入单个类中。 此类包含用于与PayPal通信的方法。 此外,还将PayPal凭据添加到此类。

  • 在 Visual Studio 中的 Wingtip Toys 示例应用程序中,右键单击“逻辑”文件夹,然后选择“添加新>”。
    随即出现“添加新项”对话框。

  • 在左侧“已安装”窗格中的“Visual C#”下,选择“代码”。

  • 在中间窗格中,选择 “类”。 将此新类 命名为 PayPalFunctions.cs

  • 单击“添加”。
    新类文件显示在编辑器中。

  • 将默认代码替换为以下代码:

    using System;
    using System.Collections;
    using System.Collections.Specialized;
    using System.IO;
    using System.Net;
    using System.Text;
    using System.Data;
    using System.Configuration;
    using System.Web;
    using WingtipToys;
    using WingtipToys.Models;
    using System.Collections.Generic;
    using System.Linq;
    public class NVPAPICaller
      //Flag that determines the PayPal environment (live or sandbox)
      private const bool bSandbox = true;
      private const string CVV2 = "CVV2";
      // Live strings.
      private string pEndPointURL = "https://api-3t.paypal.com/nvp";
      private string host = "www.paypal.com";
      // Sandbox strings.
      private string pEndPointURL_SB = "https://api-3t.sandbox.paypal.com/nvp";
      private string host_SB = "www.sandbox.paypal.com";
      private const string SIGNATURE = "SIGNATURE";
      private const string PWD = "PWD";
      private const string ACCT = "ACCT";
      //Replace <Your API Username> with your API Username
      //Replace <Your API Password> with your API Password
      //Replace <Your Signature> with your Signature
      public string APIUsername = "<Your API Username>";
      private string APIPassword = "<Your API Password>";
      private string APISignature = "<Your Signature>";
      private string Subject = "";
      private string BNCode = "PP-ECWizard";
      //HttpWebRequest Timeout specified in milliseconds 
      private const int Timeout = 15000;
      private static readonly string[] SECURED_NVPS = new string[] { ACCT, CVV2, SIGNATURE, PWD };
      public void SetCredentials(string Userid, string Pwd, string Signature)
        APIUsername = Userid;
        APIPassword = Pwd;
        APISignature = Signature;
      public bool ShortcutExpressCheckout(string amt, ref string token, ref string retMsg)
        if (bSandbox)
          pEndPointURL = pEndPointURL_SB;
          host = host_SB;
        string returnURL = "https://localhost:44300/Checkout/CheckoutReview.aspx";
        string cancelURL = "https://localhost:44300/Checkout/CheckoutCancel.aspx";
        NVPCodec encoder = new NVPCodec();
        encoder["METHOD"] = "SetExpressCheckout";
        encoder["RETURNURL"] = returnURL;
        encoder["CANCELURL"] = cancelURL;
        encoder["BRANDNAME"] = "Wingtip Toys Sample Application";
        encoder["PAYMENTREQUEST_0_AMT"] = amt;
        encoder["PAYMENTREQUEST_0_ITEMAMT"] = amt;
        encoder["PAYMENTREQUEST_0_PAYMENTACTION"] = "Sale";
        encoder["PAYMENTREQUEST_0_CURRENCYCODE"] = "USD";
        // Get the Shopping Cart Products
        using (WingtipToys.Logic.ShoppingCartActions myCartOrders = new WingtipToys.Logic.ShoppingCartActions())
          List<CartItem> myOrderList = myCartOrders.GetCartItems();
          for (int i = 0; i < myOrderList.Count; i++)
            encoder["L_PAYMENTREQUEST_0_NAME" + i] = myOrderList[i].Product.ProductName.ToString();
            encoder["L_PAYMENTREQUEST_0_AMT" + i] = myOrderList[i].Product.UnitPrice.ToString();
            encoder["L_PAYMENTREQUEST_0_QTY" + i] = myOrderList[i].Quantity.ToString();
        string pStrrequestforNvp = encoder.Encode();
        string pStresponsenvp = HttpCall(pStrrequestforNvp);
        NVPCodec decoder = new NVPCodec();
        decoder.Decode(pStresponsenvp);
        string strAck = decoder["ACK"].ToLower();
        if (strAck != null && (strAck == "success" || strAck == "successwithwarning"))
          token = decoder["TOKEN"];
          string ECURL = "https://" + host + "/cgi-bin/webscr?cmd=_express-checkout" + "&token=" + token;
          retMsg = ECURL;
          return true;
          retMsg = "ErrorCode=" + decoder["L_ERRORCODE0"] + "&" +
              "Desc=" + decoder["L_SHORTMESSAGE0"] + "&" +
              "Desc2=" + decoder["L_LONGMESSAGE0"];
          return false;
      public bool GetCheckoutDetails(string token, ref string PayerID, ref NVPCodec decoder, ref string retMsg)
        if (bSandbox)
          pEndPointURL = pEndPointURL_SB;
        NVPCodec encoder = new NVPCodec();
        encoder["METHOD"] = "GetExpressCheckoutDetails";
        encoder["TOKEN"] = token;
        string pStrrequestforNvp = encoder.Encode();
        string pStresponsenvp = HttpCall(pStrrequestforNvp);
        decoder = new NVPCodec();
        decoder.Decode(pStresponsenvp);
        string strAck = decoder["ACK"].ToLower();
        if (strAck != null && (strAck == "success" || strAck == "successwithwarning"))
          PayerID = decoder["PAYERID"];
          return true;
          retMsg = "ErrorCode=" + decoder["L_ERRORCODE0"] + "&" +
              "Desc=" + decoder["L_SHORTMESSAGE0"] + "&" +
              "Desc2=" + decoder["L_LONGMESSAGE0"];
          return false;
      public bool DoCheckoutPayment(string finalPaymentAmount, string token, string PayerID, ref NVPCodec decoder, ref string retMsg)
        if (bSandbox)
          pEndPointURL = pEndPointURL_SB;
        NVPCodec encoder = new NVPCodec();
        encoder["METHOD"] = "DoExpressCheckoutPayment";
        encoder["TOKEN"] = token;
        encoder["PAYERID"] = PayerID;
        encoder["PAYMENTREQUEST_0_AMT"] = finalPaymentAmount;
        encoder["PAYMENTREQUEST_0_CURRENCYCODE"] = "USD";
        encoder["PAYMENTREQUEST_0_PAYMENTACTION"] = "Sale";
        string pStrrequestforNvp = encoder.Encode();
        string pStresponsenvp = HttpCall(pStrrequestforNvp);
        decoder = new NVPCodec();
        decoder.Decode(pStresponsenvp);
        string strAck = decoder["ACK"].ToLower();
        if (strAck != null && (strAck == "success" || strAck == "successwithwarning"))
          return true;
          retMsg = "ErrorCode=" + decoder["L_ERRORCODE0"] + "&" +
              "Desc=" + decoder["L_SHORTMESSAGE0"] + "&" +
              "Desc2=" + decoder["L_LONGMESSAGE0"];
          return false;
      public string HttpCall(string NvpRequest)
        string url = pEndPointURL;
        string strPost = NvpRequest + "&" + buildCredentialsNVPString();
        strPost = strPost + "&BUTTONSOURCE=" + HttpUtility.UrlEncode(BNCode);
        HttpWebRequest objRequest = (HttpWebRequest)WebRequest.Create(url);
        objRequest.Timeout = Timeout;
        objRequest.Method = "POST";
        objRequest.ContentLength = strPost.Length;
          using (StreamWriter myWriter = new StreamWriter(objRequest.GetRequestStream()))
            myWriter.Write(strPost);
        catch (Exception)
          // No logging for this tutorial.
        //Retrieve the Response returned from the NVP API call to PayPal.
        HttpWebResponse objResponse = (HttpWebResponse)objRequest.GetResponse();
        string result;
        using (StreamReader sr = new StreamReader(objResponse.GetResponseStream()))
          result = sr.ReadToEnd();
        return result;
      private string buildCredentialsNVPString()
        NVPCodec codec = new NVPCodec();
        if (!IsEmpty(APIUsername))
          codec["USER"] = APIUsername;
        if (!IsEmpty(APIPassword))
          codec[PWD] = APIPassword;
        if (!IsEmpty(APISignature))
          codec[SIGNATURE] = APISignature;
        if (!IsEmpty(Subject))
          codec["SUBJECT"] = Subject;
        codec["VERSION"] = "88.0";
        return codec.Encode();
      public static bool IsEmpty(string s)
        return s == null || s.Trim() == string.Empty;
    public sealed class NVPCodec : NameValueCollection
      private const string AMPERSAND = "&";
      private const string EQUALS = "=";
      private static readonly char[] AMPERSAND_CHAR_ARRAY = AMPERSAND.ToCharArray();
      private static readonly char[] EQUALS_CHAR_ARRAY = EQUALS.ToCharArray();
      public string Encode()
        StringBuilder sb = new StringBuilder();
        bool firstPair = true;
        foreach (string kv in AllKeys)
          string name = HttpUtility.UrlEncode(kv);
          string value = HttpUtility.UrlEncode(this[kv]);
          if (!firstPair)
            sb.Append(AMPERSAND);
          sb.Append(name).Append(EQUALS).Append(value);
          firstPair = false;
        return sb.ToString();
      public void Decode(string nvpstring)
        Clear();
        foreach (string nvp in nvpstring.Split(AMPERSAND_CHAR_ARRAY))
          string[] tokens = nvp.Split(EQUALS_CHAR_ARRAY);
          if (tokens.Length >= 2)
            string name = HttpUtility.UrlDecode(tokens[0]);
            string value = HttpUtility.UrlDecode(tokens[1]);
            Add(name, value);
      public void Add(string name, string value, int index)
        this.Add(GetArrayName(index, name), value);
      public void Remove(string arrayName, int index)
        this.Remove(GetArrayName(index, arrayName));
      public string this[string name, int index]
          return this[GetArrayName(index, name)];
          this[GetArrayName(index, name)] = value;
      private static string GetArrayName(int index, string name)
        if (index < 0)
          throw new ArgumentOutOfRangeException("index", "index cannot be negative : " + index);
        return name + index;
    
  • 在本教程前面显示的用户名、密码和签名) 添加商家 API (凭据,以便你可以对PayPal测试环境进行函数调用。

    public string APIUsername = "<Your API Username>";
    private string APIPassword = "<Your API Password>";
    private string APISignature = "<Your Signature>";
    

    在此示例应用程序中,只需将凭据添加到 C# 文件 (.cs) 。 但是,在实现的解决方案中,应考虑在配置文件中加密凭据。

    NVPAPICaller 类包含大部分PayPal功能。 类中的代码提供了从PayPal测试环境进行测试购买所需的方法。 以下三个PayPal函数用于进行购买:

  • SetExpressCheckout 函数
  • GetExpressCheckoutDetails 函数
  • DoExpressCheckoutPayment 函数
  • 该方法 ShortcutExpressCheckout 从购物车收集测试购买信息和产品详细信息,并调用 SetExpressCheckout PayPal函数。 该方法 GetCheckoutDetails 在进行测试购买之前确认购买详细信息并调用 GetExpressCheckoutDetails PayPal函数。 该方法 DoCheckoutPayment 通过调用 DoExpressCheckoutPayment PayPal 函数从测试环境中完成测试购买。 其余代码支持PayPal方法和进程,例如编码字符串、解码字符串、处理数组和确定凭据。

    PayPal允许你根据 PayPal的 API 规范包括可选的购买详细信息。 通过在 Wingtip Toys 示例应用程序中扩展代码,可以包括本地化详细信息、产品说明、税务、客户服务编号以及许多其他可选字段。

    请注意, 在 ShortcutExpressCheckout 方法中指定的返回和取消 URL 使用端口号。

    string returnURL = "https://localhost:44300/Checkout/CheckoutReview.aspx";
           string cancelURL = "https://localhost:44300/Checkout/CheckoutCancel.aspx";
    

    当 Visual Web Developer 使用 SSL 运行 Web 项目时,通常使用端口 44300 用于 Web 服务器。 如上所述,端口号为 44300。 运行应用程序时,可以看到不同的端口号。 需要在代码中正确设置端口号,以便在本教程结束时成功运行 Wingtip Toys 示例应用程序。 本教程的下一部分介绍如何检索本地主机端口号并更新PayPal类。

    更新 PayPal 类中的 LocalHost 端口号

    Wingtip Toys 示例应用程序通过导航到PayPal测试站点并返回到 Wingtip Toys 示例应用程序的本地实例来购买产品。 若要PayPal返回到正确的 URL,需要在上述PayPal代码中指定本地运行的示例应用程序的端口号。

  • 右键单击解决方案资源管理器 (WingtipToys) 的项目名称,然后选择属性”。

  • 在左列中,选择 “Web ”选项卡。

  • “项目 URL ”框中检索端口号。

  • 如果需要,请在 PayPalFunctions () 的 PayPal 类NVPAPICaller中更新returnURLcancelURL更新.cs文件以使用 Web 应用程序的端口号:

    string returnURL = "https://localhost:<Your Port Number>/Checkout/CheckoutReview.aspx";
    string cancelURL = "https://localhost:<Your Port Number>/Checkout/CheckoutCancel.aspx";
    

    现在,添加的代码将与本地 Web 应用程序的预期端口匹配。 PayPal将能够返回到本地计算机上的正确 URL。

    添加PayPal签出按钮

    将主要PayPal函数添加到示例应用程序后,可以开始添加调用这些函数所需的标记和代码。 首先,必须添加用户将在购物车页面上看到的结帐按钮。

  • 打开 ShoppingCart.aspx 文件。

  • 滚动到文件的底部,找到 <!--Checkout Placeholder --> 批注。

  • 将注释替换为控件 ImageButton ,以便按如下所示替换标记:

    <asp:ImageButton ID="CheckoutImageBtn" runat="server" 
                          ImageUrl="https://www.paypal.com/en_US/i/btn/btn_xpressCheckout.gif" 
                          Width="145" AlternateText="Check out with PayPal" 
                          OnClick="CheckoutBtn_Click" 
                          BackColor="Transparent" BorderWidth="0" />
    
  • ShoppingCart.aspx.cs 文件中,在 UpdateBtn_Click 文件末尾附近的事件处理程序后面,添加 CheckOutBtn_Click 事件处理程序:

    protected void CheckoutBtn_Click(object sender, ImageClickEventArgs e)
        using (ShoppingCartActions usersShoppingCart = new ShoppingCartActions())
            Session["payment_amt"] = usersShoppingCart.GetTotal();
        Response.Redirect("Checkout/CheckoutStart.aspx");
    
  • 此外,在 ShoppingCart.aspx.cs 文件中,添加对 的 CheckoutBtn引用,以便按如下所示引用新图像按钮:

    protected void Page_Load(object sender, EventArgs e)
        using (ShoppingCartActions usersShoppingCart = new ShoppingCartActions())
            decimal cartTotal = 0;
            cartTotal = usersShoppingCart.GetTotal();
            if (cartTotal > 0)
                // Display Total.
                lblTotal.Text = String.Format("{0:c}", cartTotal);
                LabelTotalText.Text = "";
                lblTotal.Text = "";
                ShoppingCartTitle.InnerText = "Shopping Cart is Empty";
                UpdateBtn.Visible = false;
                CheckoutImageBtn.Visible = false;
    
  • 将更改保存到 ShoppingCart.aspx 文件和 ShoppingCart.aspx.cs 文件。

  • 从菜单中选择 “调试->生成 WingtipToys”。
    该项目将使用新添加的 ImageButton 控件重新生成。

    将购买详细信息发送到PayPal

    当用户单击购物车页上的“ 结帐 ”按钮 (ShoppingCart.aspx) 时,他们将开始购买过程。 以下代码调用购买产品所需的第一个PayPal函数。

  • Checkout 文件夹中,打开名为 CheckoutStart 的代码隐藏文件.aspx.cs。
    请务必打开代码隐藏文件。

  • 将现有代码替换为以下代码:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Web.UI;
    using System.Web.UI.WebControls;
    namespace WingtipToys.Checkout
        public partial class CheckoutStart : System.Web.UI.Page
            protected void Page_Load(object sender, EventArgs e)
                NVPAPICaller payPalCaller = new NVPAPICaller();
                string retMsg = "";
                string token = "";
                if (Session["payment_amt"] != null)
                    string amt = Session["payment_amt"].ToString();
                    bool ret = payPalCaller.ShortcutExpressCheckout(amt, ref token, ref retMsg);
                    if (ret)
                        Session["token"] = token;
                        Response.Redirect(retMsg);
                        Response.Redirect("CheckoutError.aspx?" + retMsg);
                    Response.Redirect("CheckoutError.aspx?ErrorCode=AmtMissing");
    

    当应用程序的用户单击购物车页上的 “结帐 ”按钮时,浏览器将导航到 CheckoutStart.aspx 页面。 当 CheckoutStart.aspx 页面加载时,ShortcutExpressCheckout将调用该方法。 此时,用户将传输到PayPal测试网站。 在PayPal网站上,用户输入其PayPal凭据,查看购买详细信息,接受PayPal协议,并返回到方法完成的 ShortcutExpressCheckout Wingtip Toys 示例应用程序。 方法ShortcutExpressCheckout完成后,它会将用户重定向到该方法中指定的 ShortcutExpressCheckoutCheckoutReview.aspx 页。 这允许用户从 Wingtip Toys 示例应用程序中查看订单详细信息。

    查看订单详细信息

    从PayPal返回后,Wingtip Toys 示例应用程序的 CheckoutReview.aspx 页将显示订单详细信息。 此页面允许用户在购买产品之前查看订单详细信息。 必须按如下所示创建 CheckoutReview.aspx 页:

  • “签出 ”文件夹中,打开名为 CheckoutReview 的页面.aspx

  • 将现有标记替换为以下内容:

    <%@ Page Title="" Language="C#" MasterPageFile="~/Site.Master" AutoEventWireup="true" CodeBehind="CheckoutReview.aspx.cs" Inherits="WingtipToys.Checkout.CheckoutReview" %>
    <asp:Content ID="Content1" ContentPlaceHolderID="MainContent" runat="server">
        <h1>Order Review</h1>
        <h3 style="padding-left: 33px">Products:</h3>
        <asp:GridView ID="OrderItemList" runat="server" AutoGenerateColumns="False" GridLines="Both" CellPadding="10" Width="500" BorderColor="#efeeef" BorderWidth="33">              
            <Columns>
                <asp:BoundField DataField="ProductId" HeaderText=" Product ID" />        
                <asp:BoundField DataField="Product.ProductName" HeaderText=" Product Name" />        
                <asp:BoundField DataField="Product.UnitPrice" HeaderText="Price (each)" DataFormatString="{0:c}"/>     
                <asp:BoundField DataField="Quantity" HeaderText="Quantity" />        
            </Columns>    
        </asp:GridView>
        <asp:DetailsView ID="ShipInfo" runat="server" AutoGenerateRows="false" GridLines="None" CellPadding="10" BorderStyle="None" CommandRowStyle-BorderStyle="None">
            <Fields>
            <asp:TemplateField>
                <ItemTemplate>
                    <h3>Shipping Address:</h3>
                    <asp:Label ID="FirstName" runat="server" Text='<%#: Eval("FirstName") %>'></asp:Label>  
                    <asp:Label ID="LastName" runat="server" Text='<%#: Eval("LastName") %>'></asp:Label>
                    <asp:Label ID="Address" runat="server" Text='<%#: Eval("Address") %>'></asp:Label>
                    <asp:Label ID="City" runat="server" Text='<%#: Eval("City") %>'></asp:Label>
                    <asp:Label ID="State" runat="server" Text='<%#: Eval("State") %>'></asp:Label>
                    <asp:Label ID="PostalCode" runat="server" Text='<%#: Eval("PostalCode") %>'></asp:Label>
                    <h3>Order Total:</h3>
                    <asp:Label ID="Total" runat="server" Text='<%#: Eval("Total", "{0:C}") %>'></asp:Label>
                </ItemTemplate>
                <ItemStyle HorizontalAlign="Left" />
            </asp:TemplateField>
              </Fields>
        </asp:DetailsView>
        <asp:Button ID="CheckoutConfirm" runat="server" Text="Complete Order" OnClick="CheckoutConfirm_Click" />
    </asp:Content>
    
  • 打开名为 CheckoutReview 的代码 隐藏页.aspx.cs并将现有代码替换为以下内容:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Web.UI;
    using System.Web.UI.WebControls;
    using WingtipToys.Models;
    namespace WingtipToys.Checkout
      public partial class CheckoutReview : System.Web.UI.Page
        protected void Page_Load(object sender, EventArgs e)
          if (!IsPostBack)
            NVPAPICaller payPalCaller = new NVPAPICaller();
            string retMsg = "";
            string token = "";
            string PayerID = "";
            NVPCodec decoder = new NVPCodec();
            token = Session["token"].ToString();
            bool ret = payPalCaller.GetCheckoutDetails(token, ref PayerID, ref decoder, ref retMsg);
            if (ret)
              Session["payerId"] = PayerID;
              var myOrder = new Order();
              myOrder.OrderDate = Convert.ToDateTime(decoder["TIMESTAMP"].ToString());
              myOrder.Username = User.Identity.Name;
              myOrder.FirstName = decoder["FIRSTNAME"].ToString();
              myOrder.LastName = decoder["LASTNAME"].ToString();
              myOrder.Address = decoder["SHIPTOSTREET"].ToString();
              myOrder.City = decoder["SHIPTOCITY"].ToString();
              myOrder.State = decoder["SHIPTOSTATE"].ToString();
              myOrder.PostalCode = decoder["SHIPTOZIP"].ToString();
              myOrder.Country = decoder["SHIPTOCOUNTRYCODE"].ToString();
              myOrder.Email = decoder["EMAIL"].ToString();
              myOrder.Total = Convert.ToDecimal(decoder["AMT"].ToString());
              // Verify total payment amount as set on CheckoutStart.aspx.
                decimal paymentAmountOnCheckout = Convert.ToDecimal(Session["payment_amt"].ToString());
                decimal paymentAmoutFromPayPal = Convert.ToDecimal(decoder["AMT"].ToString());
                if (paymentAmountOnCheckout != paymentAmoutFromPayPal)
                  Response.Redirect("CheckoutError.aspx?" + "Desc=Amount%20total%20mismatch.");
              catch (Exception)
                Response.Redirect("CheckoutError.aspx?" + "Desc=Amount%20total%20mismatch.");
              // Get DB context.
              ProductContext _db = new ProductContext();
              // Add order to DB.
              _db.Orders.Add(myOrder);
              _db.SaveChanges();
              // Get the shopping cart items and process them.
              using (WingtipToys.Logic.ShoppingCartActions usersShoppingCart = new WingtipToys.Logic.ShoppingCartActions())
                List<CartItem> myOrderList = usersShoppingCart.GetCartItems();
                // Add OrderDetail information to the DB for each product purchased.
                for (int i = 0; i < myOrderList.Count; i++)
                  // Create a new OrderDetail object.
                  var myOrderDetail = new OrderDetail();
                  myOrderDetail.OrderId = myOrder.OrderId;
                  myOrderDetail.Username = User.Identity.Name;
                  myOrderDetail.ProductId = myOrderList[i].ProductId;
                  myOrderDetail.Quantity = myOrderList[i].Quantity;
                  myOrderDetail.UnitPrice = myOrderList[i].Product.UnitPrice;
                  // Add OrderDetail to DB.
                  _db.OrderDetails.Add(myOrderDetail);
                  _db.SaveChanges();
                // Set OrderId.
                Session["currentOrderId"] = myOrder.OrderId;
                // Display Order information.
                List<Order> orderList = new List<Order>();
                orderList.Add(myOrder);
                ShipInfo.DataSource = orderList;
                ShipInfo.DataBind();
                // Display OrderDetails.
                OrderItemList.DataSource = myOrderList;
                OrderItemList.DataBind();
              Response.Redirect("CheckoutError.aspx?" + retMsg);
        protected void CheckoutConfirm_Click(object sender, EventArgs e)
          Session["userCheckoutCompleted"] = "true";
          Response.Redirect("~/Checkout/CheckoutComplete.aspx");
    

    DetailsView 控件用于显示已从PayPal返回的顺序详细信息。 此外,上述代码将订单详细信息作为 OrderDetail 对象保存到 Wingtip Toys 数据库。 当用户单击“ 完成订单 ”按钮时,会重定向到 CheckoutComplete.aspx 页面。

    CheckoutReview 的标记中.aspx 页中,请注意,该 <ItemStyle> 标记用于更改页面底部附近 DetailsView 控件中项的样式。 通过在 Visual Studio () 左下角选择“设计,然后选择“DetailsView”控件,然后选择“智能标记” (控件右上角的箭头图标) ,即可看到 DetailsView 任务

    通过选择 “编辑字段”,将显示“ 字段 ”对话框。 在此对话框中,可以轻松控制 DetailsView 控件的 Visual 属性,如 ItemStyle

    CheckoutComplete.aspx 页面从PayPal进行购买。 如上所述,用户必须单击“ 完成订单 ”按钮,然后应用程序才能导航到 CheckoutComplete.aspx 页面。

  • “签出 ”文件夹中,打开名为 CheckoutComplete 的页面.aspx

  • 将现有标记替换为以下内容:

    <%@ Page Title="" Language="C#" MasterPageFile="~/Site.Master" AutoEventWireup="true" CodeBehind="CheckoutComplete.aspx.cs" Inherits="WingtipToys.Checkout.CheckoutComplete" %>
    <asp:Content ID="Content1" ContentPlaceHolderID="MainContent" runat="server">
        <h1>Checkout Complete</h1>
        <h3>Payment Transaction ID:</h3> <asp:Label ID="TransactionId" runat="server"></asp:Label>
        <h3>Thank You!</h3>
        <asp:Button ID="Continue" runat="server" Text="Continue Shopping" OnClick="Continue_Click" />
    </asp:Content>
    
  • 打开名为 CheckoutComplete 的代码 隐藏页.aspx.cs并将现有代码替换为以下内容:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Web.UI;
    using System.Web.UI.WebControls;
    using WingtipToys.Models;
    namespace WingtipToys.Checkout
      public partial class CheckoutComplete : System.Web.UI.Page
        protected void Page_Load(object sender, EventArgs e)
          if (!IsPostBack)
            // Verify user has completed the checkout process.
            if ((string)Session["userCheckoutCompleted"] != "true")
              Session["userCheckoutCompleted"] = string.Empty;
              Response.Redirect("CheckoutError.aspx?" + "Desc=Unvalidated%20Checkout.");
            NVPAPICaller payPalCaller = new NVPAPICaller();
            string retMsg = "";
            string token = "";
            string finalPaymentAmount = "";
            string PayerID = "";
            NVPCodec decoder = new NVPCodec();
            token = Session["token"].ToString();
            PayerID = Session["payerId"].ToString();
            finalPaymentAmount = Session["payment_amt"].ToString();
            bool ret = payPalCaller.DoCheckoutPayment(finalPaymentAmount, token, PayerID, ref decoder, ref retMsg);
            if (ret)
              // Retrieve PayPal confirmation value.
              string PaymentConfirmation = decoder["PAYMENTINFO_0_TRANSACTIONID"].ToString();
              TransactionId.Text = PaymentConfirmation;
              ProductContext _db = new ProductContext();
              // Get the current order id.
              int currentOrderId = -1;
              if (Session["currentOrderId"] != string.Empty)
                currentOrderId = Convert.ToInt32(Session["currentOrderID"]);
              Order myCurrentOrder;
              if (currentOrderId >= 0)
                // Get the order based on order id.
                myCurrentOrder = _db.Orders.Single(o => o.OrderId == currentOrderId);
                // Update the order to reflect payment has been completed.
                myCurrentOrder.PaymentTransactionId = PaymentConfirmation;
                // Save to DB.
                _db.SaveChanges();
              // Clear shopping cart.
              using (WingtipToys.Logic.ShoppingCartActions usersShoppingCart =
                  new WingtipToys.Logic.ShoppingCartActions())
                usersShoppingCart.EmptyCart();
              // Clear order id.
              Session["currentOrderId"] = string.Empty;
              Response.Redirect("CheckoutError.aspx?" + retMsg);
        protected void Continue_Click(object sender, EventArgs e)
          Response.Redirect("~/Default.aspx");
    

    加载 CheckoutComplete.aspx 页面时, DoCheckoutPayment 将调用该方法。 如前所述,该方法 DoCheckoutPayment 从PayPal测试环境完成购买。 一旦PayPal完成订单的购买, 结帐完成.aspx 页将显示向购买者付款交易 ID

    处理取消购买

    如果用户决定取消购买,他们将被定向到 CheckoutCancel.aspx 页面,他们将看到订单已取消。

  • “签出”文件夹中打开名为 CheckoutCancel.aspx的页面。

  • 将现有标记替换为以下内容:

    <%@ Page Title="" Language="C#" MasterPageFile="~/Site.Master" AutoEventWireup="true" CodeBehind="CheckoutCancel.aspx.cs" Inherits="WingtipToys.Checkout.CheckoutCancel" %>
    <asp:Content ID="Content1" ContentPlaceHolderID="MainContent" runat="server">
        <h1>Checkout Cancelled</h1>
        <h3>Your purchase has been cancelled.</h3>
    </asp:Content>
    

    处理购买错误

    购买过程中的错误将由 CheckoutError.aspx 页面处理。 CheckoutStart.aspx 页面、CheckoutReview.aspx 页和 CheckoutComplete.aspx 页的代码隐藏将分别重定向到 CheckoutError.aspx 页面(如果发生错误)。

  • “签出”文件夹中打开名为 CheckoutError.aspx的页面。

  • 将现有标记替换为以下内容:

    <%@ Page Title="" Language="C#" MasterPageFile="~/Site.Master" AutoEventWireup="true" CodeBehind="CheckoutError.aspx.cs" Inherits="WingtipToys.Checkout.CheckoutError" %>
    <asp:Content ID="Content1" ContentPlaceHolderID="MainContent" runat="server">
        <h1>Checkout Error</h1>
    <table id="ErrorTable">
            <td class="field"></td>
            <td><%=Request.QueryString.Get("ErrorCode")%></td>
            <td class="field"></td>
            <td><%=Request.QueryString.Get("Desc")%></td>
            <td class="field"></td>
            <td><%=Request.QueryString.Get("Desc2")%></td>
    </table>
    </asp:Content>
    

    在签出过程中发生错误时, “CheckoutError.aspx ”页会显示错误详细信息。

    运行应用程序

    运行应用程序以查看如何购买产品。 请注意,你将在PayPal测试环境中运行。 没有真正的钱被交换。

  • 确保所有文件都保存在 Visual Studio 中。

  • 打开 Web 浏览器并导航到 https://developer.paypal.com

  • 使用本教程前面创建的PayPal开发人员帐户登录。
    对于PayPal的开发人员沙盒,需要登录 https://developer.paypal.com 以测试快速签出。 这仅适用于PayPal的沙盒测试,不适用于PayPal的实时环境。

  • 在 Visual Studio 中,按 F5 运行 Wingtip Toys 示例应用程序。
    数据库重新生成后,浏览器将打开并显示 Default.aspx 页。

  • 通过选择产品类别(如“汽车”),然后单击每个产品旁边的 “添加到购物车 ”,将三种不同的产品添加到购物车。
    购物车将显示所选产品。

  • 单击 PayPal 按钮进行签出。

    签出需要你拥有 Wingtip Toys 示例应用程序的用户帐户。

  • 单击页面右侧的 Google 链接,使用现有 gmail.com 电子邮件帐户登录。
    如果没有 gmail.com 帐户,可以在 www.gmail.com 创建一个用于测试目的的帐户。 还可以通过单击“注册”来使用标准本地帐户。

  • 使用 gmail 帐户和密码登录。

  • 单击“ 登录 ”按钮,将 gmail 帐户注册到 Wingtip Toys 示例应用程序用户名。

  • 在PayPal测试站点上,添加本教程前面创建的 买家 电子邮件地址和密码,然后单击“ 登录 ”按钮。

  • 同意PayPal策略,然后单击“ 同意并继续 ”按钮。
    请注意,此页仅在首次使用此PayPal帐户时显示。 再次请注意,这是一个测试帐户,没有真正的钱被交换。

  • 查看PayPal测试环境评审页上的订单信息,然后单击“ 继续”。

  • CheckoutReview.aspx 页上,验证订单金额并查看生成的发货地址。 然后单击“ 完成订单 ”按钮。

  • CheckoutComplete.aspx 页面显示有付款交易 ID。

    查看数据库

    运行应用程序后,查看 Wingtip Toys 示例应用程序数据库中更新的数据,可以看到应用程序已成功记录产品的购买。

    可以使用 Visual) Studio 中的“数据库资源管理器”窗口 (“服务器资源管理器”窗口来检查 Wingtiptoys.mdf数据库文件中包含的数据,就像本教程系列中之前所做的那样。

  • 如果浏览器窗口仍处于打开状态,请关闭浏览器窗口。

  • 在 Visual Studio 中,选择解决方案资源管理器顶部的“显示所有文件”图标,以便展开App_Data文件夹。

  • 展开 App_Data 文件夹。
    可能需要为文件夹选择 “显示所有文件” 图标。

  • 右键单击 Wingtiptoys.mdf 数据库文件,然后选择“ 打开”。
    将显示服务器资源管理器

  • 展开 “表” 文件夹。

  • 右键单击 “订单”表,然后选择“ 显示表数据”。
    将显示 Orders 表。

  • 查看 PaymentTransactionID 列以确认交易成功。

  • 关闭 “订单 表”窗口。

  • 在服务器资源管理器中,右键单击 OrderDetails 表,然后选择 “显示表数据”。

  • OrderId查看 OrderDetails 表中的值和Username值。 请注意,这些值与 OrderIdOrders 表中包括的值匹配Username

  • 关闭 OrderDetails 表窗口。

  • 右键单击 Wingtip Toys 数据库文件 (Wingtiptoys.mdf) 并选择“ 关闭连接”。

  • 如果未看到解决方案资源管理器窗口,请单击服务器资源管理器窗口底部的解决方案资源管理器,再次显示解决方案资源管理器

    在本教程中,添加了订单和订单详细信息架构来跟踪产品的购买。 还将PayPal功能集成到 Wingtip Toys 示例应用程序中。

    ASP.NET 配置概述
    使用成员身份、OAuth 和SQL 数据库部署安全 ASP.NET Web Forms应用以Azure 应用服务
    Microsoft Azure - 免费试用版

    本教程包含示例代码。 此类示例代码“按原样”提供,没有任何保证。 因此,Microsoft 不保证示例代码的准确性、完整性或质量。 你同意自行使用示例代码。 在任何情况下,Microsoft 都对任何示例代码、内容(包括但不限于任何示例代码、内容或因使用任何示例代码而造成的任何损失或损坏)以任何方式对你负责。 因此,你会收到通知,并同意赔偿、保存和保存 Microsoft 免受任何和所有损失、损失索赔、伤害或损害任何类型的损害,包括但不限于你发布、传输、使用或依赖的材料造成的损害,包括但不限于其中表达的观点。

    上一页下一页

  •